2.2. Example: Multiply and Accumulate¶
2.2.1. Combinatorial MAC¶
With Clash installed, it is now possible to begin creating hardware designs.
To give a brief overview of Clash, we will define a simple
multiply-and-accumulate circuit. Make a new file called MAC.hs
, and enter
the following preamble:
module MAC where
import Clash.Prelude
import Clash.Explicit.Testbench
This declares the module and imports some useful modules from the Clash standard library. The standard library contains necessary functions and data types for writing circuit descriptions. As with Haskell, module identifiers in Clash must always start with a capital letter and correspond to the name of the file.
The logic of our circuit is expressed as a function which takes an accumulator and two extra inputs, and outputs the new value of the accumulator – which is the old value plus the product of the two other inputs.
mac :: (Num a) => a -> (a, a) -> a
mac acc (x, y) = acc + x * y
The type of the function is given after the ::
, and says that the type
a
is some numeric type (e.g. Int
, Signed 8
, Double
), the first
argument is a number, the second value is a pair of numbers, and the result is
a number.
2.2.2. Synchronous MAC¶
By adding another output parameter to this function, with the previous value of
the accumulator, we can define the function as a Mealy machine. This allows
us to use our combinatorial definition of mac
to create a synchronous
circuit (which we call macS
).
mac :: (Num a) => a -> (a, a) -> (a, a)
mac acc (x, y) = (acc + x * y, acc)
macS :: (HiddenClockResetEnable dom, Num a, NFDataX a) => Signal dom (a, a) -> Signal dom a
macS = mealy mac 0
The input and output of macS
are values of the Signal
type. This type
represents synchronous values (functions without signals are combinatorial).
There is also an additional dom
type, for synthesis domain, and a
constraint HiddenClockResetEnable
– which says the synthesis domain has a
clock, reset and enable line. These are implicit, although they can be exposed
using the exposeClockResetEnable
funcion.
2.2.3. HDL Generation and Testing¶
To generate HDL from a synchronous circuit, a function needs to be marked as
a topEntity
. The simplest way to achieve this is to create a function with
this name, as Clash will use this definition automatically (similar to how
main
is a special function in other languages).
topEntity
:: Clock System
-> Reset System
-> Enable System
-> Signal System (Int, Int)
-> Signal System Int
topEntity = exposeClockResetEnable macS
It is now possible to generate HDL for this circuit description, by running
either clash --HDL
from the command line, or running :HDL
in clashi
(where HDL
is either vhdl
, verilog
or systemverilog
). This
will generate the HDL in a subdirectory named after the HDL being output.
Warning
Any function used to generate HDL from must have a monomorphic type. This
means there can be no type variables in the type signature (i.e. for the
circuit defined so far you need to specify both dom
and a
.
We can test that this circuit works as expected by defining a test bench. This allows an input to be used and the actual output to be compared against an expected output.
testBench :: Signal System Bool
testBench = done
where
testInput = stimuliGenerator clk rst ((1,1) :> (2,2) :> (3,3) :> (4,4) :> Nil)
expectOutput = outputVerifier' clk rst (0 :> 1 :> 5 :> 14 :> 30 :> 46 :> 62 :> Nil)
done = expectOutput (topEntity clk rst en testInput)
en = enableGen
clk = tbSystemClockGen (fmap not done)
rst = systemResetGen
From clashi
it is possible to sample this test bench, using the sampleN
function, which takes in the number of samples to draw and the signal which
generates samples.
>>> sampleN 8 testBench
[False, False, False, False, False, False, False, False]