General
- Name of function to be made top entity:
topEntity
Domain
: synthesis domain- techmap, I guess
System
: Useful for simulation
- Convert to VHDL:
clash --vhdl MAC.hs
Signal
values represent synchronous values. ie, values that change with respect to a clock.- Functions without signals are purely combinatorial.
- Combinational circuits: (recursive) functions
- Sequential circuits: composition of functions together with a finite state machine like a Mealy machine.
- Clock, Reset, Enable signals are implicit by default when using
HiddenClockResetEnable
.- These signals can be exposed with functions like
exposeClockResetEnable
.
- These signals can be exposed with functions like
NFDataX
To be synthesizable, data type needs to be an instance of NFDataX
, a typeclass defined in clash-prelude.
It's like NFData
. NFData
says that the type has a normal form (ie, is fully evaluatable and doesn't diverge). (I guess NF stands for 'normal form').
This makes the evaluation eager?
NFDataX
is like NFData
from deepseq package, but with support for undefined values.
Simulating a Mealy machine
(cf: from here)
+---------------+
Input --->-| |------> Output
| Combinational |
| logic |
+---| |---+
| +---------------+ |
Current ^ V Next
state | +---------------+ | state
| | | |
+-<-| Memory |-<-+
| elements |
+---------------+
–
-- | Function to run a mealy machine
= [] -- no input means no output
run _ _ [] :inputs) = output: outputs
run func state (inputwhere
= func input state
(output, state′) = run func state′ inputs
outputs
-- | Transition function for a mealy machine
func :: InputSignals → State a → (OutputSignals, State a)
= (output, state′)
func input state where
...
...
–
Type:
mealy
:: (KnownDomain dom,
GHC.Classes.IP (Clash.Signal.HiddenClockName dom) (Clock dom),
GHC.Classes.IP (Clash.Signal.HiddenEnableName dom) (Enable dom),
GHC.Classes.IP (Clash.Signal.HiddenResetName dom) (Reset dom),
NFDataX s) =>
(s -> i -> (s, o)) -> s -> Signal dom i -> Signal dom o
Transition function of the mealy machine:
(s -- ^ current state
-> i -- ^ next input
-> (s, o)) -- ^ next state, next output (produced /during/ the transition)
s -- ^ initial state of mealy machine
-> Signal dom i -- ^ input signal
-> Signal dom o -- ^ output signal
Types
From base
itself:
- Int8, Int16, Int32, Int64
- Word8, Word16, Word32, Word64
From clash-prelude
:
- Bit
- Signed, Unsigned
Assign names to signals
à la ports in VHDL, I guess.
Use :::
. An example: https://github.com/clash-lang/clash-compiler/pull/2584/files
Annotations
Control name of ports in generated HDL
Example:
{-# ANN topEntity
(Synthesize
{ t_name = "topEntity"
, t_inputs = [ PortName "clk"
, PortName "rst"
, PortName "en"
, PortName "inp" ]
, t_output = PortName "res"
}) #-}
blackbox
- Used to say that a particular definition is not handled by clash and it is to be treated as a blackbox.
- HDL code will plugged in instead
CLASHOPAQUE
Becomes:
- NOINLINE in GHC<9.4
- OPAQUE in newer GHC
- https://github.com/clash-lang/clash-compiler/pull/2511
Testbenches
- Function name has to be
testBench
testBench
has to be in root module
https://hackage.haskell.org/package/clash-prelude-1.8.1/docs/Clash-Tutorial.html
outputVerifier
- Returns
True
when all test cases have been tested - Otherwise keeps returning
False
- Also writes error message to stderr when output is not as expected
- Returns
Simulate
simulate
: give aSignal a -> Signal b
and get a[a] -> [b]
simulate_lazy
: lazy version ofsimulate
sample
: Convert aSignal
into an infinite listL.take 5 $ sample 10 top
sampleN
: get n samples of aSignal
as a listsampleN @System 5 $ register 8 $ fromList [1,2,3,4]
fromList
: convert a list to aSignal
Bundling signals
https://hackage.haskell.org/package/clash-prelude-1.6.3/docs/Clash-Signal-Bundle.html
bundle
takes a tuple of signals and makes a signal of tuples. Something like: (Signal dom a, Signal dom b)
becoming Signal dom (a, b)
.
Similarly unbundle
does it in the other direction. Signal of tuples becomes tuple of signals. Something like: Signal dom (a, b)
becoming (Signal dom a, Signal dom b)
.
bundle :: Unbundled dom a -> Signal dom a
-- Example:
bundle :: (Signal dom a, Signal dom b) -> Signal dom (a,b)
unbundle :: Signal dom a -> Unbundled dom a
-- Example:
unbundle :: Signal dom (a,b) -> (Signal dom a, Signal dom b)
BitVector
- BitVector indexing:
!
- Index is descending. Right-most index is 0 (ie, LSB is right-most)
bar :: BitVector 8 -> (BitVector 2, BitVector 3, BitVector 3)
bar bv = let (zyx, cba, qp) = unpack bv in (qp, cba, zyx)
-- Another option: Using `slice`
koo :: BitVector 8 -> (BitVector 2, BitVector 3, BitVector 3)
koo bv = (slice d1 d0 bv, slice d4 d2 bv, slice d7 d5 bv)
-- Convoluted
foo :: BitVector 8 -> (BitVector 2, BitVector 3, BitVector 3)
foo bv = case bv of
$(bitPattern "zyxc_baqp") -> (pack (q ++# p), pack (c ++# b ++# a), pack (z ++# y ++# x))
Packing/unpacking:
λ> pack (Left () :: Either () Bool)
0b0.
λ> pack (Right False :: Either () Bool)
0b10
λ> pack (Right True :: Either () Bool)
0b11
-- The . is a don't care
λ> pack (((), True) :: ((), Bool))
0b1
λ> pack (((), False) :: ((), Bool))
0b0
Misc
- DDR signals: https://hackage.haskell.org/package/clash-prelude-1.6.6/docs/Clash-Explicit-DDR.html
- Special signals. Looks like they are made such that they are updated twice in a cycle when compared to normal
Signal
type.
- Special signals. Looks like they are made such that they are updated twice in a cycle when compared to normal
- Lion: A RISC-V CPU?? made with Clash
register
register
:: (HiddenClockResetEnable dom, NFDataX a)
=> a -> Signal dom a -> Signal dom a
Clash.Prelude> sampleN @System 5 $ register 8 $ fromList [1,2,3,4]
[8,8,2,3,4]
Bitvectors
Conversion between Bitvector and Int:
Clash.Prelude> pack (59 :: Unsigned 8)
0b0011_1011
Clash.Prelude> :t pack (59 :: Unsigned 8)
pack (59 :: Unsigned 8) :: BitVector 8
Clash.Prelude> :t pack (59 :: Unsigned 9)
pack (59 :: Unsigned 9) :: BitVector 9
Clash.Prelude> unpack 0b101 ::Signed 3
-3
Clash.Prelude> unpack 0b101 ::Unsigned 3
5
—
Some operations:
-- Bitwise OR
Clash.Prelude Prelude> (Clash.Prelude..|.) 0b1001 0b1010
11
—
bLit:
Clash.Prelude> import qualified Data.List as List
Clash.Prelude List> $(bLit (List.replicate 4 '0'))
0b1111
7 segment with clash
a | b | c | d | g | e | f | g | |
---|---|---|---|---|---|---|---|---|
0 | T | T | T | T | T | T | T | |
1 | T | T | ||||||
2 | T | T | T | T | T | |||
3 | T | T | T | T |
import Data.Char
import qualified Data.Map
a :: [(Char, Vec 7 Bool)]
-- a b c d e f g
= [('0', True :> True :> True :> True :> True :> True :> True :> Nil),
a '1', False :> True :> True :> False :> False :> False :> False :> Nil),
('?', True :> True :> False :> False :> True :> False :> True :> Nil)]
(= Data.Map.fromList a b
Constructing Signal
manually
Not synthesizable. Shouldn't be used in production code!!
> import Clash.Signal.Internal
λ> :t (1 :- 2)
λ1 :- 2) :: Num a => Signal dom a
(
> 1 :: Signal System Int
λ1 1 1 1 1 ...............
More
- Lion: Formally verified, pipelined RISC-V processor
- Slides from a clash talk: https://github.com/fp-syd/meetings/blob/master/2024/2024-11-27-Clash_Talk-Alex_Mason.pdf