Clocking blocks and sampled views
Use a clocking block to sample DUT outputs from just before the edge and to drive inputs just after it, and never mix raw signals with sampled ones.
- Lesson
- 6.3
- Time
- 25 min
- Runs on
- Verilator
- Check
- 5 questions
A monitor waits with @(bus.cb) but then reads the raw signal bus.rdata instead of the sampled bus.cb.rdata. The DUT updates rdata with a nonblocking assignment at the same edge, so by the time the monitor runs, the raw signal already holds the next beat. Every recorded transfer pairs this beat's handshake with the next beat's data, and the scoreboard reports a mismatch on every read.
The clocking event fires after the DUT's nonblocking update, so the raw signal has already moved on.
A clocking block fixes when to sample and when to drive
A clocking block names a clock event and a set of signals, and states when each signal is sampled or driven relative to that event. It usually sits inside the interface, next to the signals it times.
clocking cb @(posedge clk); default input #1step output #2; input rdata; output rvalid; endclocking declares one. Inputs are signals the testbench reads, outputs are signals it drives, and the skews say how far from the edge each happens.
Each signal in the block becomes a clockvar, reached as bus.cb.rdata. Reading a clockvar gives the sampled value; writing one, always with <=, schedules a drive.
Input skew: the value from just before the edge
An input skew of #1step samples the signal in the Preponed region of the clocking event's time step. That is the value the signal held just before anything happened at the edge, which is exactly the value the DUT's flip-flops capture.
#1step means one step of the simulation's time precision, not one clock cycle. The sample therefore stays the same however many processes run at the edge and in whatever order.
The clocking event itself, which @(bus.cb) waits for, is triggered in the Observed region, after the NBA region. By then the DUT's registers have their new values, and only the clockvars still hold the pre-edge view.
After @(bus.cb) at an edge | Reads | Matches what the DUT captured? |
|---|---|---|
bus.cb.rdata | value from just before the edge | yes |
bus.rdata | value after the edge's nonblocking updates | no: it is the next value |
Two views of one signal in the same time step.
Output skew: drive just after the edge
Writing bus.cb.rvalid <= 1; does not change the pin at once. The drive happens at the output skew after the clocking event: with output #2, two time units later, well clear of the edge.
With an output skew of #0, the drive happens in the Re-NBA region of the same time step. That is after the DUT has sampled its inputs at this edge, so the DUT sees the new value at the next edge, just as it would from a flip-flop.
Either way, a testbench that drives through clocking-block outputs cannot race the DUT's sampling, which removes the race shown in the previous lesson without any hand-placed delays.
Use one view per process
Within one driver or monitor, use the clocking block for everything: wait with @(bus.cb), read bus.cb.x, and drive bus.cb.y <= v. Mixing in raw reads or @(posedge clk) brings back the timing questions the clocking block was meant to settle.
Wait on the clocking event, read the raw signal
@(bus.cb);
if (bus.cb.rvalid && bus.cb.rready)
got.push_back(bus.rdata); // raw
The handshake is from before the edge, the data from after it: each beat is paired with the next one.
Read everything through the clocking block
@(bus.cb);
if (bus.cb.rvalid && bus.cb.rready)
got.push_back(bus.cb.rdata);
Handshake and data come from the same instant, just before the edge.
Separate clocking blocks for the driver and the monitor keep their roles apart. A modport can then export just the right block, as in modport source (clocking drv_cb);.
Choosing the skews
For inputs, #1step is almost always right: it gives exactly the values the DUT captured. A larger input skew, such as #2, samples two time units before the edge, which is useful only to model a real setup time.
For outputs, a small nonzero skew such as #2 makes waveforms easy to read, because testbench changes appear just after each edge instead of on top of it. A skew of #0 is equally correct and keeps the waveform tight.
Counting cycles with a default clocking
Declaring one clocking block as the default, as in default clocking cb @(posedge clk); ... endclocking, enables the cycle-delay operator. ##2 then waits for two clocking events, so drivers can count cycles instead of edges.
Cycle delays follow the clocking block's event, so they fit naturally with clockvar drives and samples. Mixing them with raw @(posedge clk) waits brings back the same timing questions as mixing raw and sampled reads.
Common slips
Reading a clockvar right after @(posedge clk) instead of @(bus.cb) can return the sample from the previous edge. Clockvars are updated only when the clocking event fires, later in the time step, so wait on the clocking block's own event.
Writing a clockvar with = is illegal; clocking-block drives always use <=. And driving the same signal both raw and through the clocking block makes two drivers fight, so pick one path for each signal.
Try it
At every clocking event the raw signal already showed the value the DUT had just written, while the clockvar showed the value from before the edge. The two differ by one beat at the same time.
The drive through the clocking block reached the pin two time units after the edge, as its output skew says. Change the skew to output #0 and predict both rvalid lines.
t=5 raw rdata=11, sampled bus.cb.rdata=00 t=15 raw rdata=22, sampled bus.cb.rdata=11 t=25 raw rdata=33, sampled bus.cb.rdata=22 t=26 one unit after the edge: rvalid=0 t=27 two units after the edge: rvalid=1Check yourself
Remember
- A clocking block states when each signal is sampled and driven relative to one clock event.
- With
#1step, a clockvar holds the value from just before the edge, which is what the DUT captured. @(bus.cb)resumes after the nonblocking updates, so raw signals already show post-edge values.- Clocking-block outputs are driven with
<=at the output skew; a#0skew drives in the Re-NBA region. - In one driver or monitor, read and write only through the clocking block.
Practice
- The monitor that read the next beat Fix · Easy