Skip to content

yuchangyuan/short_intro_to_clash

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A short introduction to Clash

a short self intro

  • digital IC design engineer
  • interested in FOSS, long time GNU/Linux user
  • familiar with functional programming

what is Clash

description

  • Clash is a functional hardware description language that borrows both its syntax and semantics from the functional programming language Haskell.
  • compile a subset of Haskell to synthesizable HDL(verilog/VHDL/systemverilog)

features

  • Strongly typed
  • Interactive REPL(clashi)
  • Low-level access(blackbox)

simplified intro for Haskell

ghc/ghci

  • ghc: Glasgow Haskell Compiler
  • ghci: REPL from ghc
    • :t, type
    • :i, info

pure

  • every function has no side effect

lazy evaluation

  • do not apply unless necessary
  • list & infinite list

high order function, function vs variable

  • function is variable
  • ordinary variable is function with 0 args
  • so, variable is immutable

curry

  • f x y = (f x) y
  • f x y z = (f x y) z = ((f x) y) z
  • let add = (+)
  • let f5 = add 5
  • f5 6 == 11

Functor/Applicative

Functor, fmap

  • container with some type
    • []
    • Maybe
  • fmap (+ 1) [1,2,3] == [2,3,4]
  • fmap (+ 1) $ Just 4 == Just 5
  • fmap (+ 1) None == None
  • fmap (fmap f5) [Just 5, Nothing, Just 3]

Applicative, <*>

  • container, can appy inside container
  • [(* 2)] <*> [1, 2, 3]
  • Just (+ 5) <*> Just 6
  • pure (+) <*> Just 5 <*> Just 6

try some code in ghci

reciprocal calculation

-- reciprocal calculate, 1/d
f d x = x * (2 - d * x)

f 0.7 1
(f 0.7) 1
f 0.7 $ 1

f 0.7 $ f 0.7 $ 1
f 0.7 $ f 0.7 $ f 0.7 $ 1


(f 0.7) . (f 0.7) . (f 0.7) $ 1

g = f 0.7
g . g . g $ 1

make a function

foldr (.) id [f 0.7, f 0.7, f 0.7] $ 1

foldr (.) id (replicate 3 $ f 0.7) $ 1

recipI n d = foldr (.) id (replicate n $ f d) $ 1

Signal in Clash

data Signal (dom :: Domain) a
  = a :- Signal dom a

head# :: Signal dom a -> a
head# (x' :- _ )  = x'

tail# :: Signal dom a -> Signal dom a
tail# (_  :- xs') = xs'
instance Functor (Signal dom)
instance Applicative (Signal dom)

compare to List

data [] a = [] | a : [a]

try in clashi

  • sampleN @System 5 (pure 4)
  • a = fromList [1..]
  • sampleN @System 10 a
  • b = fromList [2..]
  • sampleN @System 10 b
  • c = a * (pure 2)
  • sampleN @System 10 c
  • sampleN @System 10 $ b + c

combinational logic

for number signal, just apply normal operation

instance Num a => Num (Signal dom a)
instance Fractional a => Fractional (Signal dom a)

for other type, use fmap, pure & <*>, or helper function: .==., .<.

instance Functor (Signal dom)
instance Applicative (Signal dom)
  • sampleN @System 10 $ fmap not $ pure False
  • sampleN @System 10 $ not <$> pure False
  • sampleN @System 10 $ Just <$> fromList [1..]
  • sampleN @System 10 $ pure not <*> pure False
  • sampleN @System 10 $ pure 5 .>. fromList [1..]

sequential logic

register ::
  (HiddenClockResetEnable dom, NFDataX a) =>
  a -> Signal dom a -> Signal dom a
        -- Defined in ‘Clash.Signal’
infixr 3 `register`
  • a = fromList [1..]
  • b = register 8 a
  • sampleN @System 10 b – NOTE, first 1 power-up value, then reset value
  • simulateN @System 10 (register 8) [1..] – power-up value not included
  • x = register False $ not <$> x
  • sampleN @System 10 x

trival example, FIR Filter

fir coeffs x = dotp coeffs (window x)
  where
    dotp as bs = sum (zipWith (*) as bs)

-- inferred: Signal dom Int -> Signal dom Int
fir3int = fir (3 :> 4 :> 5 :> Nil)

-- inferred: Signal dom Float -> Signal dom Float
fir4float = fir (3.5 :> 4.2 :> 3.0 :> 6.1 :> Nil)

see ./src/FIR.hs

non trival example, reciprocal calculation for positive

function

see ./src/Recip.hs

import Data.List as L
import Prelude as P

recipStep d x = x * (2 - d * x)

recipI :: (Num a) => Int -> a -> a
recipI n d = L.foldr (.) id (L.replicate n $ recipStep d) $ 1

recipFull :: (Scalable a) => Int -> a -> a
recipFull k x = scaleReverse (recipI k y) n
  where
    (y, n) = unscale x

type family: Scalable a

class (RealFrac a, Integral (ScaleIndex a)) => Scalable a where
  type ScaleIndex a :: Type
  unscale :: a -> (a, ScaleIndex a)
  scale :: a -> (ScaleIndex a) -> a
  scaleReverse :: a -> (ScaleIndex a) -> a
  -- scale (fst $ unscale x) (snd $ unscale x) == x
  -- scale x n * scaleReverse x n == x * x
  • default implementation for Double, Float & Ratio
  • different implementation for UFixed, make synthesizable

some test

recipI 4 0.3 :: Double
recipI 6 0.3 :: Double
recipI 4 3 :: Double
recipI 6 3 :: Double
recipFull 6 0.7 :: Double
recipFull 6 0.7 :: UFixed 8 16
recipFull 6 (7 % 10)
recipFull 6 3 :: Double
recipFull 6 3 :: UFixed 8 16
recipFull 6 (3 % 1)

define recip4_ & recip4

{-# ANN recip4_
  (Synthesize
    { t_name   = "recip4_"
    , t_inputs = [PortName "x"]
    , t_output = PortName "y"
    }) #-}
recip4_ :: UFixed 8 24 -> UFixed 8 24
recip4_ = recipI 4

{-# ANN recip4
  (Synthesize
    { t_name   = "recip4"
    , t_inputs = [PortName "x"]
    , t_output = PortName "y"
    }) #-}
recip4 :: UFixed 8 24 -> UFixed 8 24
recip4 = recipFull 4

some test

L.map recip4_ [0.1, 0.3, 0.5, 0.7, 1.0, 1.8, 2.5, 3.0]

mapM_ (print . recip4_) [0.1, 0.3, 0.5, 0.7, 1.0, 1.8, 2.5, 3.0]

recipI 4 (2.5 :: Double)
recipI 1 (2.5 :: Double)
recipI 1 (2.5 :: UFixed 8 24)
(0.5 :: UFixed 8 24) - 1.0
mapM_ (print . recip4) [0.1, 0.3, 0.5, 0.7, 1.0, 1.8, 2.5, 3.0]

trival test for generated verilog

./tb/tb_recip.v

module tb;
   wire [31:0] out1, out2;
   reg [31:0]  in;

   localparam  SCALE = 'h100_0000;

   recip4_ dut1(.x(in), .y(out1));
   recip4  dut2(.x(in), .y(out2));

   task t(input real x);
      begin
         in = x * SCALE;
         #1;
         $display("%f:\t%.9f\t%.9f", x, 1.0 * out1 / SCALE, 1.0 * out2 / SCALE);
      end
   endtask

   initial begin
      $display("input:\t\trecip4_\t\trecip4");
      t(0.1);
      t(0.3);
      t(0.5);
      t(0.7);
      t(1.0);
      t(1.8);
      t(2.5);
      t(3.0);
   end
endmodule

sequential implementation

  • one iteration per cycle
  • register unscale step
  • register scale step
  • chisel DecoupledIO alike IO
    • in_data, in_esp, in_valid, in_ready(out)
    • out_data, out_cnt, out_valid, out_ready(in)

all state

  • y, unscale from in_data, when input ready
  • n, unscale from in_data, when input ready
  • esp, register when input ready
  • state , state of state machine
  • res, unscaled result
  • out_cnt
  • out_data, finally output, from res & n

state machine

  • state
    • IDLE
    • BUSY
      • out_cnt <= out_cnt + 1
      • res <= res * (2 - y * res) – res’
    • DONE
  • output
    • in_ready <= state == IDLE
    • out_valid <= state == DONE
  • transition
    • IDLE -> BUSY condition: in_ready & in_valid state: (y, n) <= unscale in_data, res <= 1.0, out_cnt <= 0
    • BUSY -> DONE condition: abs(res - res’) < esp -> DONE state: out_data <= scaleReverse res n
    • DONE -> IDLE condition: out_valid & out_ready

some test

see ./src/RecipSeq.hs for implementation

testRecipSeq :: (Scalable a, Num b) =>
                a -> a -> SS (Bool, a, b, Bool)

sampleN 10 $ testRecipSeq 0.7 (1e-5 :: Double)
sampleN 10 $ testRecipSeq 0.7 (1e-5 :: UFixed 8 24)
sampleN 10 $ testRecipSeq (7 % 10) 1e-5

sampleN 10 $ testRecipSeq 3 (1e-10 :: Double)

test for sequential implementation

see ./tb/tb_recip_seq_top.v

compare to other (HDL) language

(intentionally blank)

UFixed instance of Scalable a

see ./src/Recip.hs

refs

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published