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.)
$ 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
?
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
:
GHC.TypeLits.Normalise
from ghc-typelits-natnormalise
GHC.TypeLits.Extra.Solver
from ghc-typelits-extra
GHC.TypeLits.KnownNat.Solver
from ghc-typelits-knownnat
This is a bit weird, but we will come back to it in a while.
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"
.
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.
blog-1.0.0
. It also demonstrates that imports work.
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.