Difficulty: Hard · Puzzle 6 of 6 · Topic: Clocking Block Puzzles
Identify which line introduces race potential and why the DUT's view of req can change between tools or after a source reorder.
`timescale 1ns/1ns
interface ifc(input bit clk);
logic req, grant;
clocking cb @(posedge clk);
default input #1step output #0;
input grant;
output req;
endclocking
endinterface
module tb;
bit clk = 0;
ifc vif(clk);
always #5 clk = ~clk;
always @(posedge clk) vif.grant <= vif.req;
initial begin
vif.req = 0;
@(posedge clk);
vif.req = 1; // direct blocking drive in Active
@(posedge clk);
vif.cb.req <= 0; // clocking-block drive
repeat (2) @(posedge clk);
$finish;
end
endmoduleA blocking write in Active at the clock edge competes with every Active-region reader of that edge.
1) `vif.req = 1` runs in the Active region of t=5, the same region and edge in which the DUT executes vif.grant <= vif.req. Which runs first is not defined, so the DUT captures 0 or 1 at t=5.
2) Demonstrated with a plain version of the same pattern (no clocking block): in Icarus swapping the source order of the initial and the always changes the value seen at t=15 (grant=1 vs grant=0); Verilator happened to give 1 both times.
3) `vif.cb.req <= 0` is a Re-NBA drive, strictly after all Active reads of that edge, so it is deterministic.
4) Not verified by execution: Verilator's clocking-block model did not apply the cb drive, so the cb half is reasoning from the LRM.
5) Rule: one driver discipline per signal; all drives through the clocking block.Answer: The direct `vif.req = 1` is the racy line. It writes in Active at the edge the DUT samples, so the DUT may see 0 or 1 on that edge; the clocking-block drive does not race.
Checked by running the code in Icarus Verilog 13 and Verilator 5; the printed output supports the answer above. Plain-signal version of the race: with the drive written before the always block, Icarus captures req=0 at t=5. Same code with the two blocks swapped: Icarus now captures req=1 at t=5, so the order of Active-region processes changes the DUT result. Verilator happens to pick one order for both layouts; the LRM defines neither, so never depend on it.
Mixing styles is the trap: a helper task that bypasses the clocking block reintroduces exactly the race the clocking block was added to remove.
How would you enforce cb-only driving at compile time in your interface API? Model answer: expose the interface to the testbench through a modport that lists only the clocking block (modport drv (clocking cb);), so vif.req cannot be named and a direct write is a compile error. (Reasoning from the LRM; not tool-verified here.)
The racy line is the direct write to req, because it runs in the Active region of the same edge where the DUT reads it, and their order is not defined. So the DUT may see 0 or 1, and the answer can change between tools or after a source reorder. The clocking-block drive lands after all the Active reads, so it does not race. The trap is mixing the two styles, for example a helper task that bypasses the clocking block; I would check that every drive of the signal goes through it.