Each simulation time is one time slot. Inside it the scheduler works through a fixed list of regions and loops back to Active while new events keep appearing. Time advances only when every region is empty.
A #0 delay moves a statement to the Inactive region, which wins against Active-region code exactly once. When both sides add #0, the race returns one region later, so stacked #0 fixes decay into ordering folklore.
Watch d against q. The drive already shows the post-edge value while the sampler still holds the pre-edge one.
Slot 0: d is 1 while q is still 0; the sampler read the pre-edge 0.
Slot 1: d is 0 while q is 1; each lane mirrors the previous slot of the other.
Slot 2: d is 1 while q is 0.
Slot 3: d is 0 while q is 1.
Slot 4: d is 1 while q is 0.
Slot 5: d is 0 while q is 1.
q <= 5; and then $display(q) and $strobe(q). q was 0 before the edge. What do the two lines print?$display prints 0 and $strobe prints 5. $display runs in Active, before the NBA commit; $strobe waits for Postponed, after it.
Assertions sample in Preponed and evaluate in Observed, and program blocks run testbench code in Reactive. Both exist to keep testbench and design from racing; Scheduling Regions: the Full Map covers them once assertions and classes are familiar.
Every initial block starts at time zero in an undefined order, and each module instance contributes its own copies. Any cross-block dependency on that order is a latent race across simulators.
Order comes from structure instead: one orchestrator block, or event handshakes with wait (ev.triggered), which stays true for the whole slot and cannot lose a wakeup. A final block runs zero-time code on every exit path, which makes it the home for pass and open-transaction reports.
$display right at the clock edge and sees the old value. Is the flop broken?No. $display runs in Active, before the flop's nonblocking update commits in NBA. Read it a little later, or use $strobe, to see the new value.
#0 to order against each other. Why does the fix stop working?#0 hops exactly one region. Once both sides hop, execution is unordered again one region later.
Active-versus-NBA sampling, a lossless startup handshake with its final report, and a one-cycle request and grant checked a moment after the edge.
module tb_nba_demo;
timeunit 1ns; timeprecision 1ps;
logic clk = 0;
always #5 clk = ~clk;
int data = 0;
int q1 = -1, q2 = -1;
int errors = 0;
always @(posedge clk) data <= data + 1;
initial begin
@(posedge clk);
q1 = data;
@(posedge clk);
q2 = data;
if (q1 !== 0) errors++;
if (q2 !== 1) errors++;
if (data !== 1) errors++;
if (errors != 0) $fatal(1, "FAIL: sampled data does not match pre-edge values");
$display("PASS: Active-region reads see pre-edge data 0 then 1, never the pending NBA commit");
$finish;
end
endmodulePASS: Active-region reads see pre-edge data 0 then 1, never the pending NBA commitmodule tb_startup;
event cfg_done;
int cfg = 0;
int errors = 0;
initial begin
cfg = 42;
->cfg_done;
end
initial begin
wait (cfg_done.triggered);
if (cfg !== 42) errors++;
if (errors != 0) $fatal(1, "FAIL: consumer ran with unexpected cfg");
$display("PASS: triggered handshake delivers cfg 42 with no lost wakeup");
$finish;
end
final begin
$display("END: cfg=%0d", cfg);
end
endmodulePASS: triggered handshake delivers cfg 42 with no lost wakeup
END: cfg=42module tb_grant_window;
timeunit 1ns; timeprecision 1ps;
logic clk = 0;
always #5 clk = ~clk;
logic req = 0;
logic gnt = 0;
int errors = 0;
always @(posedge clk) gnt <= req;
initial begin
@(negedge clk); req = 1;
@(posedge clk); #1;
if (gnt !== 1'b1) errors++;
@(negedge clk); req = 0;
@(posedge clk); #1;
if (gnt !== 1'b0) errors++;
if (errors != 0) $fatal(1, "FAIL: grant window does not match the one-cycle pulse");
$display("PASS: one-cycle req pulse returns gnt for exactly one cycle");
$finish;
end
endmodulePASS: one-cycle req pulse returns gnt for exactly one cycleRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run the startup bench (no .triggered on events); the other two run on both.