One of the things I love about reading open source is randomly finding unused features. There are many reasons a part of the code is not used, It could can experimental, untested or it is only there because the developer had too much fun writing it :) Anyway, It’s interesting when i find these bits..
In cocotb,I found built-in utility to generate wavedrom. It’s defined in cocotb/wavedrom.py
. From comments, I modified dff_simple_test
to dump dut.q
and dut.d
sampled on dut.clk
.
@cocotb.test()
async def dff_simple_test(dut):
"""Test that d propagates to q"""
with trace(dut.d, dut.q, clk=dut.clk) as waves:
clock = Clock(dut.clk, 10, units="us") # Create a 10us period clock on port clk
cocotb.start_soon(clock.start()) # Start the clock
await FallingEdge(dut.clk) # Synchronize with the clock
for i in range(10):
val = random.randint(0, 1)
dut.d.value = val # Assign the random value val to the input port d
await FallingEdge(dut.clk)
assert dut.q.value == val, f"output q was incorrect on the {i}th cycle"
# Dump to JSON format compatible with WaveDrom
j = waves.dumpj()
print(j)
And the generated wavedrom json is generated. Super cool, Right!
{
"signal": [
{
"name": "clock",
"wave": "p.........."
},
{
"name": "d",
"wave": "z010.10.101"
},
{
"name": "q",
"wave": "z010.10.101"
}
]
}
Deep dive Link to heading
The context manager registers coroutine to _monitor
162 def __enter__(self):
163 for sig in self._signals:
164 sig.clear()
165 self.enable()
166 self._coro = cocotb.start_soon(self._monitor())
167 return self
_monitor
awaits on _clock
and sample
each signal
139
140 async def _monitor(self):
141 self._clocks = 0
142 while True:
143 await RisingEdge(self._clock)
144 await ReadOnly()
145 if not self._enabled:
146 continue
147 self._clocks += 1
148 for sig in self._signals:
149 sig.sample()
150
sample
is defined in Wavedrom
not trace
. It detects the change of signal and sample it. If not changed, .
is used to indicate no-change in wavedrom.
42
43 def sample(self):
44 """Record a sample of the signal value at this point in time."""
45
46 def _lastval(samples):
47 for x in range(len(samples) - 1, -1, -1):
48 if samples[x] not in "=.|":
49 return samples[x]
50 return None
51
52 for name, hdl in self._hdls.items():
53 val = hdl.value
54 valstr = val.binstr.lower()
55
56 # Decide what character to use to represent this signal
57 if len(valstr) == 1:
58 char = valstr
59 elif "x" in valstr:
60 char = "x"
61 elif "u" in valstr:
62 char = "u"
63 elif "z" in valstr:
64 char = "z"
65 else:
66 if (
67 len(self._data[name])
68 and self._data[name][-1] == int(val)
69 and self._samples[name][-1] in "=."
70 ):
71 char = "."
72 else:
73 char = "="
74 self._data[name].append(int(val))
75
76 # Detect if this is unchanged
77 if len(valstr) == 1 and char == _lastval(self._samples[name]):
78 char = "."
79 self._samples[name].append(char)