Difficulty: Medium · Puzzle 1 of 6 · Topic: Debugging Riddles
A monitor 'appears one cycle stale' on a few seeds while the DUT waveform looks right. Before blaming the DUT, predict exactly what each of the three monitors below prints (module with `timescale 1ns/1ns, clk period 10, rising edges at 5, 15, 25, and the run ends with initial #27 $finish so the last print is at 26). Then say which one is a true race and how a clocking block fixes it.
`timescale 1ns/1ns
logic clk = 0; logic [3:0] q = 0;
always #5 clk = ~clk;
always @(posedge clk) q <= q + 1; // DUT: nonblocking
always @(posedge clk) $display("%0t M1 edge q=%0d", $time, q);
always @(posedge clk) begin #1; $display("%0t M2 edge+1 q=%0d", $time, q); end
always @(negedge clk) $display("%0t M3 negedge q=%0d", $time, q);
initial #27 $finish;
// Variant (separate run): DUT written with a blocking assignment
int b = 0;
always @(posedge clk) b = b + 1; // blocking
always @(posedge clk) $display("%0t mon b=%0d", $time, b);NBA updates happen after every active-region read at the same time step.
1) The DUT flop uses q <= q+1: the new value is committed in the NBA region, after all active-region code of that time step has run.
2) M1 reads in the active region at the edge, so it sees the pre-edge value: 5 q=0, 15 q=1, 25 q=2. This is deterministic, not a race. The 'stale' feel is that the monitor sees the same data a synchronous flop would see.
3) M2 waits 1 time unit, after the NBA update: 6 q=1, 16 q=2 (26 q=3, the last print before $finish at 27). M3 reads at the falling edge: 10 q=1, 20 q=2. Both see the post-edge value. (Icarus and Verilator printed exactly this.)
4) So decide what the scoreboard expects. If the reference model is cycle-aligned with registered outputs, M1's pre-edge view is correct and the scoreboard should compare against the previous-cycle expectation; if it expects post-edge data, sampling must move later.
5) The true race is the blocking variant. Both always blocks wake at the same edge in arbitrary order, so mon may print b before or after the increment at each edge, so the output depends on the tool and on the order of the two blocks, and can change from edge to edge. In one trial of the same source (with int b = 0) Icarus printed 0, 2, 2 at 5, 15, 25 and Verilator 5.048 printed 0, 1, 2; swapping the two blocks changed Icarus to 1, 1, 3. Never rely on any of these.
6) Fix the race by never driving with blocking assignments from a clocked block that others sample, or by driving and sampling through a clocking block (default input #1step samples the pre-edge value, output skew drives after the edge). Note #1step is not a cure for stale data: it returns what M1 returns, only without the race.
7) The related bug of reusing one transaction handle for every sample is covered in the UVM analysis-port puzzle.Answer: M1 prints 0,1,2 (pre-edge, deterministic), M2 prints 1,2,3 and M3 prints 1,2; only the blocking-assignment variant is a real race.
Checked by running the code in Icarus Verilog 13 and Verilator 5; the printed output supports the answer above. Icarus: M1 samples the pre-edge value (0,1,2), M2 one unit later sees 1,2,3, M3 on the falling edge sees 1,2. Verilator 5.048 prints exactly the same lines: the NBA-driven case is deterministic across tools. Blocking-assignment variant in Icarus: the monitor reads 0, 2, 2 depending on which same-edge block runs first. Same variant with the two always blocks swapped: Icarus now prints 1, 1, 3, so the result depends on source order (a real race). The unswapped variant in Verilator prints 0, 1, 2: a different answer from Icarus on the same source.
Calling every one-cycle-late sample a race and reaching for #1step. A pre-edge sample of an NBA-driven signal is deterministic; the real defect is mixing blocking drives with same-edge sampling.
How would you prove in logs that the monitor sampled pre-NBA values? Model answer: print $time together with the DUT value after the edge (a $strobe or an #1 print) next to the monitor's sample: if the sample equals the value before the edge and the strobe shows the new one at the same time stamp, the monitor sampled pre-NBA. Or run with two different tools or seeds: identical results mean deterministic pre-edge sampling, differing results mean a real race.
Let me first work out what each monitor sees, because a flop updates in the NBA region, after all the active code at that edge. Monitor one samples at the edge in the active region, so it sees the pre-edge value and prints 0, 1, 2, which is deterministic and not a race. Monitor two waits one time unit and prints 1, 2, 3, and monitor three samples at the falling edge and prints 1, 2. The real race is the blocking-assignment variant, where two blocks wake on the same edge in arbitrary order, so the fix is nonblocking drives or a clocking block. The trap is calling every late sample a race and reaching for a one-step delay.