dramforever

a row of my life

Stack-based Clash environment

2019-08-05

This post and the repo has been updated on 2019-09-08 to use Clash 1.0.0

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

TL;DR

$ stack repl --with-ghc clash

… with a bit of configuration in package.yaml.

Background

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 have new ones like :verilog [<module>] that compiles a Haskell module to HDL.

Compilation may fail with something like the message below. I don't know what it is, but if you create the directory manually it works fine:

Missing directory: .../.stack-work/dist/x86_64-linux-tinfo6/Cabal-2.4.0.1/build/global-autogen

Now we can generate HDL like this:

ghci> :verilog PlayClash.Adder
GHC: Parsing and optimising modules took: 0.269s
GHC: Loading external modules from interface files took: 0.260s
GHC: Parsing annotations took: 0.000s
Clash: Parsing and compiling primitives took 1.753s
GHC+Clash: Loading modules cumulatively took 2.749s
Clash: Compiling PlayClash.Adder.fullAdder
Clash: Applied 3 transformations
Clash: Normalisation took 0.001s
Clash: Netlist generation took 0.000s
Clash: Compiling PlayClash.Adder.refAdder32
Clash: Applied 68 transformations
Clash: Normalisation took 0.009s
Clash: Netlist generation took 0.001s
Clash: Compiling PlayClash.Adder.chainAdder32
Clash: Applied 33 transformations
Clash: Normalisation took 0.006s
Clash: Netlist generation took 0.001s
Clash: Total compilation took 2.786s

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.

Result

A sample project is available at https://github.com/dramforever/clash-with-stack, where the code corresponding to this post is in tag blog-1.0.0. 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.