Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Clash Language Tutorial

Welcome to the Clash Language Tutorial, part of the official documentation of the Clash Compiler. Clash is an open-source functional hardware description language (HDL) that borrows syntax and semantics from the Haskell programming language. To learn more about the language, we suggest reading the introduction.

The table of contents below (and in the sidebar) allows easy access to different pages in the documentation. You can also use the search function in the top left corner.

Note

The Clash Compiler and the Clash Language Tutorial are open-source efforts developed by QBayLogic B.V. and other volunteers. The Clash Team always appreciates feedback and contributions to the project to help improve the development experience.

If you do not understand something, or think something is missing or incorrect in the documentation you can open an issue or pull request in the GitHub repository.

Introduction

Clash is a functional hardware description language that borrows both its syntax and semantics from the functional programming language Haskell. It provides a familiar structural design approach to both combinational and synchronous sequential circuits.

Features of the Clash language:

  • Strongly typed, but with a very high degree of type inference, enabling both safe and fast prototyping using concise descriptions.
  • Interactive REPL: load your designs in an interpreter and easily test all your components without needing to set up a test bench.
  • Higher-order functions, in combination with type inference, result in designs that are fully parametric by default.
  • Synchronous sequential circuit design based on streams of values, called Signals, leads to natural descriptions of feedback loops.
  • Multiple clock domains, with type-safe clock domain crossing.

Although we say that Clash borrows the semantics of Haskell, that statement should be taken with a grain of salt. What we mean to say is that the Clash compiler views a circuit description as a structural description. This means, in an academically handwavy way, that every function denotes a component and every function application denotes an instantiation of said component. Now, this has consequences on how we view recursively defined functions: structurally, a recursively defined function would denote an infinitely deeply structured component, something that cannot be turned into an actual circuit.

On the other hand, Haskell's by-default non-strict evaluation works very well for the simulation of feedback loops, which are ubiquitous in digital circuits. That is, when we take a structural view on circuit descriptions, value recursion corresponds directly to a feedback loop:

counter = s
 where
  s = register 0 (s + 1)

The above definition, which uses value recursion, can be synthesized to a circuit by the Clash compiler.

Over time, you will get a better feeling for the consequences of taking a structural view on circuit descriptions. What is always important to remember is that every applied function results in an instantiated component, and also that the compiler will never infer/invent more logic than what is specified in the circuit description.

With that out of the way, let us continue with installing Clash and building our first circuit.

Installing Clash

For installation instructions, see clash-lang.org/install/

Working with this tutorial

This tutorial can be followed best whilst having the Clash interpreter running at the same time. If you followed the instructions to setup a starter project with Stack, you can also run clashi inside such a project. Change to the directory of the project, and invoke

stack run -- clashi

If you instead set up the starter project with GHC and Cabal, change to the directory of the project and invoke

cabal run -- clashi

If you instead followed the instructions under Run Clash on its own, you can start the Clash compiler in interpretive mode by:

stack exec --resolver lts-23.15 --package clash-ghc -- clashi

For those familiar with Haskell/GHC, this is indeed just GHCi, with three added commands (:vhdl, :verilog, and :systemverilog). You can load files into the interpreter using the :l <FILENAME> command. Now, depending on your choice in editor, the following edit-load-run cycle probably work best for you:

  • Commandline (e.g. emacs, vim):

    • You can run system commands using :!, for example :! touch <FILENAME>
    • Set the /editor/ mode to your favourite editor using: :set editor <EDITOR>
    • You can load files using :l as noted above.
    • You can go into /editor/ mode using: :e
    • Leave the editor mode by quitting the editor (e.g. :wq in vim)
  • GUI (e.g. SublimeText, Notepad++):

    • Just create new files in your editor.
    • Load the files using :l as noted above.
    • Once a file has been edited and saved, type :r to reload the files in the interpreter

You are of course free to deviate from these suggestions as you see fit :-). It is just recommended that you have the Clash interpreter open during this tutorial.

Your first circuit

The very first circuit that we will build is the "classic" multiply-and-accumulate (MAC) circuit. This circuit is as simple as it sounds, it multiplies its inputs and accumulates them. Before we describe any logic, we must first create the file we will be working on and input some preliminaries:

  • Create the file:

    MAC.hs
    
  • Write on the first line the module header:

    module MAC where
    

    Module names must always start with a Capital letter. Also make sure that the file name corresponds to the module name.

  • Add the import statement for the Clash prelude library:

    import Clash.Prelude
    

    This imports all the necessary functions and datatypes for circuit description.

We can now finally start describing the logic of our circuit, starting with just the multiplication and addition:

ma acc (x, y) = acc + x * y

The circuit we just wrote is a combinational circuit: no registers are inserted (you describe explicitly where Clash will insert registers, as we will later see). We usually refer to circuits as functions, similar to programming languages such as C, Python, or Haskell. In this case, the function we just defined is called ma. Its first argument is acc, its second is (x, y) - a composite type called a tuple. This component is "unpacked", and its first element is called x, its second y. Everything to the right of the equals symbol is ma's result. If you followed the instructions of running the interpreter side-by-side, you can already test this function:

>>> ma 4 (8, 9)
76
>>> ma 2 (3, 4)
14

We can also examine the inferred type of ma in the interpreter:

>>> :t ma
ma :: Num a => a -> (a, a) -> a

You should read this as follows:

  • ma ::, ma is of type…

  • Num a, there is some type called a that is a Num. Examples of instances of Num are Int, Signed 16, Index 32, or Float.

  • a, ma's first argument is of type a

  • (a, a), ma's second argument is of type (a, a)

  • a, ma's result is of type a

Note that ma therefore works on multiple types! The only condition we imposed is that a should be a Number type. In Clash this means it should support the operations Prelude.+, Prelude.-, Prelude.*, and some others. Indeed, this is why Clash adds the constraint in the first place: the definition of ma uses + and *. Whenever a function works over multiple types, we call it polymorphic ("poly" meaning "many", "morphic" meaning "forms"). While powerful, it is not clear how Clash should synthesize this as numbers come in a great variety of (bit)sizes. We will later see how to use this function in a monomorphic manner.

Talking about types also brings us to one of the most important parts of this tutorial: types and synchronous sequential logic. Especially how we can always determine, through the types of a specification, if it describes combinational logic or (synchronous) sequential logic. We do this by examining the definition of one of the sequential primitives, the register function:

register ::
  ( HiddenClockResetEnable dom
  , NFDataX a
  ) =>
  a ->
  Signal dom a ->
  Signal dom a
register i s = ...

Where we see that the second argument and the result are not just of the polymorphic a type, but of the type: Signal dom a. All (synchronous) sequential circuits work on values of type Signal dom a. Combinational circuits always work on values of, well, not of type Signal dom a. A Signal is an (infinite) list of samples, where the samples correspond to the values of the Signal at discrete, consecutive ticks of the clock. All (sequential) components in the circuit are synchronized to this global clock. For the rest of this tutorial, and probably at any moment where you will be working with Clash, you should probably not actively think about Signals as infinite lists of samples, but just as values that are manipulated by sequential circuits. To make this even easier, it is actually not possible to manipulate the underlying representation directly: you can only modify Signal values through a set of primitives such as the register function above.

Now, let us get back to the functionality of the register function: it is a simple flip-flop that only changes state at the tick of the global clock, and it has an initial value a which is its output at time 0. We can further examine the register function by taking a look at the first 4 samples of the register functions applied to a constant signal with the value 8:

>>> sampleN @System 4 (register 0 (pure (8 :: Signed 8)))
[0,0,8,8]

Where we see that the initial value of the signal is the specified 0 value, followed by 8's. You might be surprised to see two zeros instead of just a single zero. What happens is that in Clash you get to see the output of the circuit before the clock becomes active. In other words, in Clash you get to describe the powerup values of registers too. Whether this is a defined or unknown value depends on your hardware target, and can be configured by using a different synthesis Domain. The default synthesis domain, @System, assumes that registers do have a powerup value - as is true for most FPGA platforms in most contexts.

Sequential circuit

The register function is our primary sequential building block to capture state. It is used internally by one of the Clash.Prelude functions that we will use to describe our MAC circuit.

A principled way to describe a sequential circuit is to use one of the classic machine models. Within the Clash prelude library we offer a standard function to support the Mealy machine. To improve sharing, we will combine the transition function and output function into one. This gives rise to the following Mealy specification of the MAC circuit:

macT acc (x, y) = (acc', o)
 where
  acc' = ma acc (x, y)
  o = acc

Note that the where clause and explicit tuple are just for demonstrative purposes, without loss of sharing we could have also written:

macT acc inp = (ma acc inp, acc)

Going back to the original specification we note the following:

  • acc is the current state of the circuit.
  • (x, y) is its input.
  • acc' is the updated, or next, state.
  • o is the output.

When we examine the type of macT we see that is still completely combinational:

>>> :t macT
macT :: Num a => a -> (a, a) -> (a, a)

The Clash.Prelude library contains a function that creates a sequential circuit from a combinational circuit that has the same Mealy machine type/shape of macT:

mealy ::
  (HiddenClockResetEnable dom, NFDataX s) =>
  (s -> i -> (s, o)) ->
  s ->
  (Signal dom i -> Signal dom o)
mealy f initS = ...

The complete sequential MAC circuit can now be specified as:

mac inp = mealy macT 0 inp

Where the first argument of mealy is our macT function, and the second argument is the initial state, in this case 0. We can see it is functioning correctly in our interpreter:

>>> simulateN @System 4 mac [(1,1),(2,2),(3,3),(4,4)]
[0,1,5,14]

Where we simulate our sequential circuit over a list of input samples and take the first 4 output samples. We have now completed our first sequential circuit and have made an initial confirmation that it is working as expected.

Generating VHDL

We are now almost at the point that we can create actual hardware, in the form of a VHDL netlist, from our sequential circuit specification. The first thing we have to do is create a function called topEntity and ensure that it has a monomorphic type. In our case that means that we have to give it an explicit type annotation. It might not always be needed, you can always check the type with the :t command and see if the function is monomorphic:

topEntity ::
  Clock System ->
  Reset System ->
  Enable System ->
  Signal System (Signed 9, Signed 9) ->
  Signal System (Signed 9)
topEntity = exposeClockResetEnable mac

Which makes our circuit work on 9-bit signed integers. Including the above definition, our complete MAC.hs should now have the following content:

module MAC where

import Clash.Prelude

ma acc (x, y) = acc + x * y

macT acc (x, y) = (acc', o)
 where
  acc' = ma acc (x, y)
  o = acc

mac xy = mealy macT 0 xy

topEntity ::
  Clock System ->
  Reset System ->
  Enable System ->
  Signal System (Signed 9, Signed 9) ->
  Signal System (Signed 9)
topEntity = exposeClockResetEnable mac

The topEntity function is the starting point for the Clash compiler to transform your circuit description into a VHDL netlist. It must meet the following restrictions in order for the Clash compiler to work:

  • It must be completely monomorphic
  • It must be completely first-order
  • Although not strictly necessary, it is recommended to expose Hidden clock and reset arguments, as it makes user-controlled name assignment in the generated HDL easier to do.

Our topEntity meets those restrictions, and so we can convert it successfully to VHDL by executing the :vhdl command in the interpreter. This will create a directory called vhdl, which contains a directory called MAC, which ultimately contains all the generated VHDL files. You can now load these files into your favorite VHDL synthesis tool, marking topentity.vhdl as the file containing the top level entity.

Circuit test bench

There are multiple reasons as to why you might want to create a so-called test bench for the generated HDL:

  • You want to compare post-synthesis / post-place&route behavior to that of the behavior of the original generated HDL.
  • You need representative stimuli for your dynamic power calculations.
  • You want to verify that the HDL output of the Clash compiler has the same behavior as the Haskell/Clash specification.

For these purposes, you can have the Clash compiler generate a test bench. In order for the Clash compiler to do this you need to do one of the following:

  • Create a function called testBench in the root module.
  • Annotate your test bench function with a TestBench annotation.

For example, you can test the earlier defined topEntity by:

import Clash.Explicit.Testbench

topEntity ::
  Clock System ->
  Reset System ->
  Enable System ->
  Signal System (Signed 9, Signed 9) ->
  Signal System (Signed 9)
topEntity = exposeClockResetEnable mac

testBench :: Signal System Bool
testBench = done
 where
  testInput =
    stimuliGenerator
      clk
      rst
      $(listToVecTH [(1, 1) :: (Signed 9, Signed 9), (2, 2), (3, 3), (4, 4)])
  expectOutput =
    outputVerifier clk rst $(listToVecTH [0 :: Signed 9, 1, 5, 14, 14, 14, 14])
  done = expectOutput (topEntity clk rst en testInput)
  en = enableGen
  clk = tbSystemClockGen (not <$> done)
  rst = systemResetGen

This will create a stimulus generator that creates the same inputs as we used earlier for the simulation of the circuit, and creates an output verifier that compares against the results we got from our earlier simulation. We can even simulate the behavior of the testBench:

>>> sampleN 8 testBench
[False,False,False,False,False
cycle(<Clock: System>): 5, outputVerifier
expected value: 14, not equal to actual value: 30
,False
cycle(<Clock: System>): 6, outputVerifier
expected value: 14, not equal to actual value: 46
,False
cycle(<Clock: System>): 7, outputVerifier
expected value: 14, not equal to actual value: 62
,False]

We can see that for the first 4 samples, everything is working as expected, after which warnings are being reported. The reason is that stimuliGenerator will keep on producing the last sample, (4,4), while the outputVerifier' will keep on expecting the last sample, 14. In the VHDL test bench these errors will not show, as the global clock will be stopped after 4 ticks.

You should now again run :vhdl in the interpreter; this time the compiler will take a bit longer to generate all the circuits. Inside the ./vhdl/MAC directory you will now also find a testbench subdirectory containing all the vhdl files for the test bench.

After compilation is finished you load all the files in your favorite VHDL simulation tool. Once all files are loaded into the VHDL simulator, run the simulation on the testbench entity. On questasim / modelsim: doing a run -all will finish once the output verifier will assert its output to true. The generated test bench, modulo the clock signal generator(s), is completely synthesizable. This means that if you want to test your circuit on an FPGA, you will only have to replace the clock signal generator(s) by actual clock sources, such as an onboard PLL.

Generating Verilog and SystemVerilog

Aside from being able to generate VHDL, the Clash compiler can also generate Verilog and SystemVerilog. You can repeat the previous two parts of the tutorial, but instead of executing the :vhdl command, you execute the :verilog or :sytemverilog command in the interpreter. This will create a directory called verilog, respectively systemverilog, which contains a directory called MAC, which ultimately contains all the generated Verilog and SystemVerilog files. Verilog files end in the file extension .v, while SystemVerilog files end in the file extension .sv.

This concludes the tutorial for "Your first circuit".