HN user

gergoerdi

237 karma

http://gergo.erdi.hu/

Posts6
Comments63
View on HN

Debugging via high-level simulation is something my book spends a lot of time on. If you look at the sample chapters, you can see that the same Clash code can also be compiled into software Haskell, which you can then interface with non-synthesizable test benches, such as an interactive SDL frontend. So you can take your HDL logic and run it directly, interactively in real time. You can play Pong by compiling the code as software.

One level lower, you can use Clash's signal-level simulator. Basically it gives you a synchronous stream of signal values, either as a lazy list (for "offline" simulation), or as an automaton that you can turn the crank on by feeding it the next clock cycle's inputs (for "online" simulation, i.e. where you want to do IO to compute the next input from the previous outputs). So at this level, you'd take your Pong circuit and use the automaton interface of the simulator to feed the virtual "pushbutton" states computed from e.g. keypresses, and then consume the output to do the rendering. Or simulate the whole circuit end-to-end and feed its output into a VGA interpreter, which you also get to write in Haskell.

If you need to debug at the Verilog level, you can use Clashilator (https://github.com/gergoerdi/clashilator) to automate FFI-ing into a Verilator-generated simulation.

Your website is of course referenced in the Compucolor II chapter of the book. It was invaluable in getting my Compucolor II implementation working. In fact, I even sent you a PR to fix the TMS 5501 chip's behaviour to match its datasheet (https://github.com/jtbattle/ccemu/pull/2/).

I chose the Compucolor II for its simplicity. Its original design goal of cheapness via very small number of components translates is a big win for my purposes because implementing many small special-purpose chips would bloat the book considerably, without adding too much extra value. With the Compucolor II, we can just take the Intel 8080 core from an earlier chapter, implement two custom chips, take the UART from another earlier chapter, and boom done.

In fact, I chose the Intel 8080 in the first place instead of the more widely used Z80 for the same reason: adding the Z80 extensions wouldn't bring anything new to the table, but would increase cruft. Turns out there's a small but reasonable number of 8080-based home computers (https://retrocomputing.stackexchange.com/q/11682/115).

In the book (see the sample chapter 8 at https://unsafeperform.io/retroclash/#samples) we create a proto-almost-game (just a bouncing ball) first by directly wiring together signals.

However, the resulting circuit description is much harder to understand and extend than a more structured approach, so we rewrite it in a more principled manner by decomposing it into two parts: a `Input -> State -> State` circuit used as a register transfer enabled by the start of the vblank, and a `State -> Coordinate -> RGB` circuit connected to the video output signal generator. This has the added benefit that we can compile the same description as software Haskell instead of hardware Clash, and so we can use high-level simulation to run the bouncing ball in an SDL window.

Sample chapter 9 then creates a Pong game by just changing these two (pure, Haskell) functions slightly. With minimal changes, we go from idle animation to playable game!

That's cool! I wanted to avoid having to build Rust and/or LLVM from source myself, hence the somewhat awkward "tell Cargo we're on default target, let Clang sort it out at link time" setup.

Did you look at chirp8-engine, or only chirp8-c64? The value add is not in the parts that interface with the C64 internals; probably using C for that would make for nicer code. But I wanted to push as much into Rust as I could in the short amount of time I spent on this.

The real advantage of using Rust is in the actual program logic. E.g. the instructions are decoded into an algebraic datatype (in https://github.com/gergoerdi/chirp8-engine/blob/7623353a8bf0...) and then that is consumed in the virtual CPU (https://github.com/gergoerdi/chirp8-engine/blob/7623353a8bf0...). Rust's case-of-case optimization takes care of avoiding the intermediate data representation at runtime.

They could put a tiny microcontroller that has a USB HID host and translates to PS/2 connected to the FPGA. So you have a USB socket on one side, you plug your normal USB keyboard or mouse into it, then that socket is connected to the microcontroller, and there are two wires (PS/2 clock and data) connecting the microcontroller to the FPGA.

Some hobbyist and educational FPGA dev boards do that, because handling PS/2 on the FPGA is so much easier than raw USB. Here is one example: https://reference.digilentinc.com/reference/programmable-log...

"The Auxiliary Function microcontroller (Microchip PIC24FJ128) provides the Nexys A7 with USB Embedded HID host capability. "

HoTT isn't a programming language, because there are non-value normal forms. That's the whole reason behind research into various formulations of Cubical Type Theory, which is a programming language.

Type inference 8 years ago

The problem with advanced type inference is that the exact algorithm needs to be part of the standard for compability reasons

I don't think that's true -- if you have principal types, you can just say in your language spec that the principal type is inferred.

One drawback of the Hindley-Milner type system is that its typecheckers are necessarily non-compositional. The basic idea here is that this can result in "non-symmetrical" error messages[1]: given something like

    MkPair :: a -> b -> Pair a b
    not :: Bool -> Bool
    succ :: Int -> Int

    foo x = MkPair (succ x) (not x)
the error message will either complain that `x` has type `Bool` but it should be `Int`, or it will complain that `x` has type `Int` but it should be `Bool` -- depending on what implementation you use! For example, in Haskell, Hugs 98 will emit the first error message, but GHC 7.10 will emit the second (see the full example in my slides[2]). And in some sense, neither is right (and certainly neither is as helpful for the programmer as it could be).

So next time you're implementing vanilla HM, maybe consider a compositional type system[3] instead, which can give the following, much more informative error message:

    Cannot unify 'Int' with 'Bool' when unifying 'x':
    Cannot unify 'Int' with 'Bool' in the following context:
    
           MkPair (succ x)        (not x)
       
           Bool -> Pair Int Bool  Bool
      x :: Int                    Bool
I have a simple implementation here[4], and am about to release a new version which works on `haskell-src-ext`'s AST to support more of Haskell 98 syntax (but still for vanilla HM only).

[1] https://gergo.erdi.hu/blog/2010-10-23-the_case_for_compositi... [2] https://gergo.erdi.hu/talks/2016-06-compty/CompTy.pdf [3] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.25.8... [4] https://github.com/gergoerdi/hm-compo/blob/master/src/Langua...

Non-Recursive Make is also Considered Harmful: https://news.ycombinator.com/item?id=11441719

In seriousness, the linked paper describes Shake, a Make replacement with two party tricks: one, it is implemented as a DSL embedded in Haskell, thus giving a nice way of programmatic rule generation; and two, it supports monadic dependencies, unlike Make's applicative-only ones.