Difficulty: Hard · Puzzle 6 of 6 · Topic: Reset Puzzles
The testbench waits for the clock edge at t=15 and then releases reset with a blocking assignment. The DUT is a synchronous-reset counter. What value of cnt does the print at t=46 show? Is the result defined? Fix the testbench so that every simulator agrees.
`timescale 1ns/1ns
module tb;
reg clk = 0, rst_n = 0;
reg [3:0] cnt;
always #5 clk = ~clk; // posedges at 5, 15, 25, 35, 45
always @(posedge clk)
if (!rst_n) cnt <= 4'd0; else cnt <= cnt + 1;
initial begin
repeat (2) @(posedge clk); // wake up at the edge at t=15
rst_n = 1; // blocking: same time step as the DUT's read of rst_n
end
initial begin
#46 $display("t=%0d cnt=%0d", $time, cnt);
$finish;
end
endmoduleTwo active-region processes read and write the same variable in the same time step.
1) At t=15 two processes become runnable in the same active region: the DUT's always block (reads rst_n) and the initial block (writes rst_n = 1).
2) The language does not define their order. If the DUT runs first it sees rst_n=0, resets, and the counter first counts at t=25: edges 25, 35, 45 give cnt=3.
3) If the initial block runs first the DUT sees rst_n=1 at t=15 and counts at 15, 25, 35, 45: cnt=4.
4) So the print is 3 or 4 depending on scheduling order: the code has a race and the result is not defined.
5) Fix: drive the release a small delay after the edge (#1), or from a clocking block. The DUT then reads the old, still-asserted value at t=15, and cnt=3 in every tool.Answer: It is a race: cnt prints 3 or 4 depending on which process the simulator runs first, and the language defines neither. Driven a small delay after the edge (or from a clocking block), the DUT reads the old value at t=15 and every tool prints cnt=3.
Checked by running the code in Icarus Verilog 13 and Verilator 5; the printed output supports the answer above. Icarus, fixed testbench (release 1 ns after the edge): deterministic cnt=3. Verilator, fixed testbench: the same value, as the fixed version has no race. The blocking version is deliberately not run: its result is undefined by the language.
Reporting whatever number your simulator printed. A single tool's order is an implementation detail and can change with a version, a switch or a seed. The only acceptable answer is that the result is undefined; the check below therefore runs only the fixed testbench.
Why does driving through a clocking block remove the race, and what does the DUT see on the first edge after release? Model answer: the clocking block drives its outputs a defined skew after the edge (in a later region), so the DUT reads the old, still-asserted reset at the edge; it sees the release at the next edge, exactly as with the #1 delay. A non-blocking assignment in a testbench process is often suggested too, but its ordering against the DUT is tool-dependent in some simulators, so prefer a clocking block or an explicit delay.