Clash Shockwaves simulation hang

I’m writing a testbench for the uart module provided by Retroclash to get more familiar with Shockwave. It’s as follow

{-# OPAQUE topEntity #-}
{-# ANN topEntity
  (Synthesize
    { t_name = "topEntity"
    , t_inputs =
      [ PortName "clk"
      , PortName "rst"
      , PortName "uart_rxd"
      ]
    , t_output = PortName "uart_txd"
    }) #-}
topEntity :: Clock System -> Reset System -> Signal System Bit -> Signal System Bit
topEntity clk rst rxD = withClockResetEnable clk rst enableGen $
  let
    input = rx @8 (SNat @115200) rxD
    buf = fifo input txReady
    (txD, txReady) = tx @8 (SNat @115200) buf
  in boolToBit <$> (not . bitToBool <$> txD)

testUART :: IO ()
testUART = do
  let out = topEntity clockGen resetGen (fromList $ [high, high, high, high] <> [low, high, low, high, low, low, low, low] <> Ls.repeat high)

  file <- dumpVCD (0, 10_000_000) (traceSignal "topEntity" out) []

  case file of
    Left msg -> error msg
    Right (vcd, meta) -> do
      writeFile "test.vcd" vcd
      writeFileJSON "test.json" meta

With the class derived as follow

data TxState n = TxIdle | TxBusy Word32 (TxBit n)
  deriving (Show, Eq, Generic, NFDataX)

data TxBit n = TxStartBit (BitVector n) | TxDataBit (BitVector n) (Index n) | TxStopBit
  deriving (Show, Eq, Generic, NFDataX)

data RxState n = RxIdle | RxBusy Word32 (Maybe Bit) (RxBit n)
  deriving (Show, Eq, Generic, NFDataX)

data RxBit n = RxStartBit | RxDataBit (BitVector n) (Index n) | RxStopBit (BitVector n)
  deriving (Show, Eq, Generic, NFDataX)

deriving instance (KnownNat n, 1 <= n) => BitPack (TxBit n)
deriving instance (KnownNat n, 1 <= n) => BitPack (TxState n)
deriving instance (KnownNat n, 1 <= n) => Waveform (TxBit n)
deriving instance (KnownNat n, 1 <= n) => Waveform (TxState n)

deriving instance (KnownNat n, 1 <= n) => BitPack (RxBit n)
deriving instance (KnownNat n, 1 <= n) => BitPack (RxState n)
deriving instance (KnownNat n, 1 <= n) => Waveform (RxBit n)
deriving instance (KnownNat n, 1 <= n) => Waveform (RxState n)

But this will just hang until my laptop run out of memory. Did I misunderstand something and what should I do to get it to work?

I cannot reproduce this myself if I use your definition + copied over retroclash-lib uart.. It finishes fine. Could you upload your project somewhere so I or someone else can check that out?

1 Like

I am noticing two weird things here:

  • First of all, dumpVCD’s list of trace names to wait for is empty. This should cause it to not evaluate anything, and then error when trying to produce a VCD without signals. I suspect, since it’s apparently not reproducible, something (a debug print statement maybe?) is causing your signal to be evaluated, and thus the trace is getting registered regardless.
  • Secondly, you are trying to trace for 10 million clock cycles. That’s not a timestamp in ps (which would have been 1000 cycles) - its unit is the GCD of the clock periods of all signals being traced (weird, I know, we’re working on completely rewriting dumpVCD and friends) which in a single-domain design amounts to the clock period.

Now, 10 million things should be fine in terms of memory normally, but the system really wasn’t designed for this. Bit in particular uses LUTs in Shockwaves 1.0, which, when evaluated 10 million times, probably takes up a lot of space in memory.

Note that Shockwaves 1.1 (1.1.1 has recently been released) introduces static LUTs i.e. not all values of a signal get translated - the LUT is fully predefined. I doubt you have switched yet, but if you’re creating a large number of Bits, you really should.

(and yes, Bit really doesn’t strictly require a LUT at all, but it’s the easiest way to display undefined as x while also keeping the option to add custom styles for 1 and 0)

Hope this helps!

1 Like

Thank you for testing it. I have rebooted my laptop and the test does run, though it consume a lot of ram (~15GB at peak). That’s why my laptop hang, not because of Shockwave itself.

Thank you for the very informative post.

The VCD output is always 10000 ps of nothing even though there was topEntity variable. I thought the signal being traced is the topEntity function provided as defined by out? How can I setup the testing for a top entity correctly?

No wonder why it take up so much ram…

Looking forward to future releases.

The dumpVCD function does not care that you have a topEntity. It simply evaluates the values of a signal, which causes signals to be registered as traceSignal gets evaluated, until it has found all the signals it is looking for.

The basic setup you have is correct: you take your DUT (topEntity), provide it with inputs, and feed the output to dumpVCD (out, wrapped in a traceSignal). You then still have to provide dumpVCD with a list of signals to look for: ["topEntity"]. It will keep evaluating the signal until it finds a signal labeled "topEntity", which should happen immediately since the traceSignal is the first thing evaluation encounters.

Also keep in mind that putting "topEntity" in there does not link it to topEntity in any way - it is simply the name you give to out.

1 Like

Thank you for the explanation, but I’m still confused.

From this tutorial it seems that if I left the signal list empty, dumpVCD would evaluate until the provided cycle was reached (10 million in my case). Whereas I’m always getting 10000 ps (which I assume is 1 clock cycle for 100 MHz clock) of 1. How can I let it run for say 1000 cycles?

Ah, I see. I will fix that tutorial - it really should have the trace names.

The time provided is for creating the VCD only, and is completely unrelated to the forced evaluation used to obtain the signals. The forced evaluation of the provided signal is used to obtain references to the (infinite) signals, and their values in the provided time slice are stored in the VCD.

If you want to display 1000 cycles, you should just use `dumpVCD (0,1000) …`. If you want to force evaluation of the provided signal for those 1000 cycles, that’s currently not possible (well, you could of course do it manually, but dumpVCD won’t do that). The rewrite we’re working on has better options for that.

I am rather puzzled by the “10000ps of nothing” - does the VCD contain a signal? It should error otherwise. If the signal is empty, try switching to a binary viewer to see the actual data if Shockwaves is displaying nothing.

Thank you I would love to see more up to date docs and tutorials.

It turns out this came from my misunderstanding of asynchronous communication… The time should be calculated from BAUD rate, not clock cycles… This works better

testUART :: IO ()
testUART = do
  let out = topEntity clockGen resetGen (fromList stretch)

  file <- dumpVCD (0, 1_000_000) (traceSignal "topEntity" out) []

  case file of
    Left msg -> error msg
    Right (vcd, meta) -> do
      writeFile "test.vcd" vcd
      writeFileJSON "test.json" meta
  where
    cyclesPerBit = 10417
    frame = [low]
     <> [low,high,low,high,low,low,high,high]
     <> [high]
    stretch = Ls.concatMap (Ls.replicate cyclesPerBit) frame <> Ls.repeat high

The output seems correct: wait for 10 bits until the data is received, then transmit them. But the last 3 high is cut off even if I crank it to 20 million cycles for some reason.

I currently do not have the time to go into what your design is actually, doing, but I suggest you trace both input and output, as well as any signals you can get to inside your topEntity:

(sorry, the code block fails to get formatted)

``` hs
testUART :: IO ()
testUART = do
let out = topEntity clockGen resetGen (traceSignal “input” $ fromList stretch)

file ← dumpVCD (0, 1_000_000) (traceSignal “output” out) [“input”,“output”]

case file of
Left msg → error msg
Right (vcd, meta) → do
writeFile “test.vcd” vcd
writeFileJSON “test.json” meta
where
cyclesPerBit = 10417
frame = [low]
<> [low,high,low,high,low,low,high,high]
<> [high]
stretch = Ls.concatMap (Ls.replicate cyclesPerBit) frame <> Ls.repeat high
```

Please let me know if there’s any particular documentation that’s missing or outdated. Shockwaves is still rather new, and not many people have used it yet. I have tried to write proper documentation but there might be gaps in there that I missed.

Oh it makes sense now… receive and transmit are the opposite of each other…

Thank you for helping me figure this out… Electronics engineering is sooooo complicated.