clash-lang / clash-compiler

Haskell to VHDL/Verilog/SystemVerilog compiler
https://clash-lang.org/
Other
1.42k stars 151 forks source link

Test bench needs `INLINE` when passing down top entity #2196

Open DigitalBrains1 opened 2 years ago

DigitalBrains1 commented 2 years ago

Normally, when a function recognised as a test bench makes use of a function recognised as a top entity, these two end up in separate directories (and VHDL libraries). However, when testBench passes the topEntity down to a called function, a new design unit inside the test bench output directory is created with a name based on topEntity, and testBench no longer uses the separate topEntity.

This code:

module TBInline where

import Clash.Explicit.Prelude
import Clash.Explicit.Testbench

topEntity ::
  Unsigned 8 ->
  Unsigned 8
topEntity = id
{-# NOINLINE topEntity #-}

tb0 ::
  ( Unsigned 8 ->
    Unsigned 8
  ) ->
  Signal System Bool
tb0 top = done
 where
  testInput = stimuliGenerator clk rst $(listToVecTH [0 :: Unsigned 8 .. 10])
  expectedOutput = outputVerifier' clk rst
                                   $(listToVecTH [0 :: Unsigned 8 .. 10])
  done = expectedOutput (top <$> testInput)
  clk = tbClockGen (not <$> done)
  rst = resetGen

testBench
  :: Signal System Bool
testBench = tb0 topEntity
{-# NOINLINE testBench #-}

gives the following directory structure:

$ ls -R vhdl
vhdl/:
TBInline.testBench  TBInline.topEntity

vhdl/TBInline.testBench:
clash-manifest.json                         testBench.vhdl
TBInline_testBench_types.vhdl               topEntity_0.vhdl
testBench_slv2string_2F58399B7F4E729C.vhdl

vhdl/TBInline.topEntity:
clash-manifest.json  TBInline_topEntity_types.vhdl  topEntity.vhdl

and testBench.vhdl only refers to vhdl/TBInline.testBench/topEntity_0.vhdl, there is no reference to vhdl/TBInline.topEntity/topEntity.vhdl.

topEntity_0.vhdl actually contains tb0, not just topEntity.

Adding INLINE on tb0 fixes the issue.

A certain minimum amount of complexity inside tb0 is needed to prevent automatic inlining, so I just wrote a bog-standard test bench to pass that bar.

The exact same thing happens when, instead of using functions named testBench and topEntity, we use functions with a defSyn and a TestBench annotation; it is not related to the magic names themselves.

DigitalBrains1 commented 2 years ago

Note that there is a functional difference. The following code:

Click to expand code block ```haskell module TBInline where import Clash.Explicit.Prelude import Clash.Explicit.Testbench type Top = Clock System -> Reset System -> Enable System -> Signal System (Unsigned 8) -> Signal System (Unsigned 8) myReg :: Top myReg clk rst en = register clk rst en 0 {-# NOINLINE myReg #-} top :: Top top = myReg {-# NOINLINE top #-} {-# ANN top (defSyn "top") #-} tb0 :: Top -> Signal System Bool tb0 top0 = done where testInput = stimuliGenerator clk rst $(listToVecTH [1 :: Unsigned 8 .. 10]) expectedOutput = outputVerifier' clk rst $(listToVecTH [0 :: Unsigned 8 .. 10]) done = expectedOutput (top0 clk rst en testInput) clk = tbClockGen (not <$> done) rst = resetGen en = enableGen tb :: Signal System Bool tb = tb0 top {-# NOINLINE tb #-} {-# ANN tb (TestBench 'top) #-} ```

will exercise the register primitive with ~ISACTIVEENABLE evaluating false because the enableGen is visible from myReg. But adding INLINE to tb0 so it uses the actual top entity will have ~ISACTIVEENABLE evaluate true because it is a proper enable line in that configuration. So the test bench exercises a different primitive code path than usual due to this issue.

(Note I chose annotations instead of special names in this variant just to have an example of that too).