(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
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
If you play around for a bit you will find that
clashi look just like modded
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
clash and we are probably good to go.
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
This is a bit weird, but we will come back to it in a while.
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
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.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.
blog. It also demonstrates that imports work.
The above depends on
clash being present. But where should we put the
build-depends is easy but are really not import anything from
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
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.