coding, thoughts and stuff irl

Stack-based Clash environment


(I thought someone must have done it before, but I could not find this documented anywhere, so here we are.)


$ stack repl --with-ghc clash

… with a bit of configuration in package.yaml.


Clash is a hardware description language (HDL) that happens to be a subset of Haskell. The compiler works as a modified GHC that simplifies Core and from that generates HDL code like VHDL or Verilog. This has some interesting implications, such as the fact that a Clash program run as plain Haskell, where it would be a regular function or a functional reactive program using a reduced set of reactive features, and the same program can also be compiled to conventional HDL code with matching semantics.

There is just a slight problem with all the tutorials I could find: They all run bare .hs files without using Cabal or Stack for project management. This includes the Clash FAQ and the tutorial on bitlog.it. This is not necessarily bad, but I thought we could use a bit of Stack and Stackage magic to set up a nice project environment with stack.yaml and whatnot for your Clash code. Why use stack exec -- clashi Foo.hs when you can just stack repl?

Fitting Clash into Stack

If you play around for a bit you will find that clash and clashi look just like modded ghc and ghci respectively. Indeed, the code of both are based on that of GHC and are essentially drop-in replacements with a bit more features like HDL generation. This means we just need to replace ghc with clash and we are probably good to go.

Fortunately stack repl has a flag --with-ghc. We can create a new Stack project, make it depend on clash-prelude for the functions, and run stack repl --with-ghc clash. It looks like Clash is no longer in the latest Stackage snapshots, but lts-12.26 has it and installs cleanly. Unfortunately it fails with this message:

<command line>: Could not find module ‘GHC.TypeLits.KnownNat.Solver’
Use -v to see a list of the files searched for.

Apparently we are missing some packages. Quick search on the missing module, add it as a dependency, repeat, and we get this list of packages that needs to be present as dependencies in package.yaml or *.cabal:

This is a bit weird, but we will come back to it in a while.

Give it a go

Run stack repl --with-ghc clash and sure enough, a prompt pops up, and in addition to the usual GHCi commands we can generate HDL like this:

ghci> :verilog PlayClash.Adder
Loading dependencies took 1.088728906s
Compiling: PlayClash.Circuit.refAdder32
Applied 550 transformations
Normalisation took 0.891433855s
Netlist generation took 0.003641186s
Compiling: PlayClash.Circuit.chainAdder32
Applied 32 transformations
Normalisation took 0.083207021s
Netlist generation took 0.004778602s
Total compilation took 2.079329679s

If you want to pass flags to clash, just use something like --ghci-options="-fclash-debug DebugName".

Compiling Clash code with regular GHC

As said at the very beginning, Clash code is also perfectly valid Haskell and can be run ('simulated' from the HDL perspective) as such. But Clash and GHC have differing default flags. Therefore if we want stack repl to work with Clash code we need to figure out the differences and handle them.

As documented in the… other (?) Clash FAQ (Look for -fplugin), Clash loads three compiler plugins that help with working with types, namely GHC.TypeLits.Normalise, GHC.TypeLits.Extra.Solver and GHC.TypeLits.KnownNat.Solver. Unsurprisingly, the modules that contained the plugins are exactly the missing modules appeared above when we first tried to start Clash. Now we know that Clash adds the three plugins by module name, so they appear to be required dependencies of our code. As documented adding the packages as dependencies and adding a -fplugin MODULE for each module brings regular GHC up to speed with types.

Clash also turns on and off some GHC extensions, because type-level programming is used somewhat extensively in Clash so stuff like DataKinds are automatically enabled. The list of wanted and unwanted extensions can be seen in the source for clash (Look for wantedLanguageExtensions :: if the function moved). For 'wanted' extensions we just add them to default-extensions, and for 'unwanted' ones we prepend No and add them.

The final configuration looks something like this. Now regular stack repl works with our Clash programs. Of course, regular stack repl cannot generate HDL, but that is beside the point.


A sample project is available at https://github.com/dramforever/clash-with-stack, where the code corresponding to this post is in tag blog. It also demonstrates that imports work.

Extra: Where to put clash-ghc?

The above depends on clash being present. But where should we put the clash-ghc dependency? build-depends is easy but are really not import anything from clash-ghc. build-tool-depends could work because it conveys that we only ever uses the binaries, but if we are writing a library, our downstream libraries are forced to pull clash-ghc in. We can also not put it anywhere and rely on the user to install it for the compiler version used, which for Stack means not running stack install but just stack build (a bit like Intero), so that Clash for multiple compiler versions can coexist. That is not exactly the best experience.

My take is that if you are writing 'final' Clash code intended to just compile to HDL, then put clash-ghc in build-tool-depends, and otherwise if you want your code to be imported by others leave it out. I chose the former in the sample project.