Every simulation time step runs the same fixed pipeline of regions. Processes run in region order, looping back to Active while new events appear.
Knowing which region each statement executes in answers every why-did-I-see-that-value question at the boundary.
ONE SIMULATION TIME STEP - STRATIFIED SCHEDULER
+- PREPONED ------------------------------------------+
| assertion and clocking input sampling (#1step) |
| read-only: nothing may write here |
+----------------------+------------------------------+
+- ACTIVE -------------v------------------------------+
| module processes: always blocks, continuous |<--+
| assigns, blocking updates | |
+----------------------+------------------------------+ | iterate
+- INACTIVE -----------v------------------------------+ | while new
| #0-delayed module processes | | events
+----------------------+------------------------------+ |
+- NBA ----------------v------------------------------+ |
| nonblocking assignment UPDATES |--+
+----------------------+------------------------------+
+- OBSERVED -----------v------------------------------+
| concurrent assertions EVALUATE (on Preponed data) |
+----------------------+------------------------------+
+- REACTIVE -----------v------------------------------+ |
| program-block code and assertion action blocks |<--+ run after
+----------------------+------------------------------+ | design settles
+- RE-INACTIVE --------v------------------------------+
| #0-delayed reactive processes |
+----------------------+------------------------------+
+- RE-NBA -------------v------------------------------+
| reactive nonblocking updates, clocking OUTPUT |
| DRIVES land here |
+----------------------+------------------------------+
+- POSTPONED ----------v------------------------------+
| $monitor and $strobe print FINAL settled values |
| read-only: end-of-step snapshot |
+-----------------------------------------------------+
|
v advance simulation time to next eventEach kind of code has a home region. The table below is the whole boundary discipline in one place.
Code | Region | Notes |
|---|---|---|
RTL | Active reads, NBA updates | Read-all-then-update-all keeps flop code deterministic |
Concurrent assertions | Sample in Preponed, evaluate in Observed | They judge pre-edge values by design, like clocking inputs |
Program-block testbench code | Reactive | Runs after the design settles; its drives land in Re-NBA |
Class testbench in module context | Active | Modern UVM-era style: keep it race-free with clocking blocks or NBA drives |
Clocking inputs and outputs | Preponed samples, Re-NBA drives | The same split the cure lesson relies on |
| Wherever its caller runs | Mid-step value, not the settled one |
| Postponed | Settled end-of-step values |
A class testbench runs in Reactive only when it starts from program context. The common UVM-era layout lives in module context, so its code runs in Active like the design.
A program block moves where testbench code executes, while a clocking block moves where its signal accesses take effect. The full program-block debate lives in Testbench anatomy.
One counter stepping from 4 to 5 is observed three ways. An in-block display prints 4, a concurrent assertion judges the Preponed sample 4, and a strobe prints the settled 5.
All three are correct snapshots of different pipeline stages. Comparing a display against an assertion failure mistakes one snapshot for another.
A #0 delay only re-queues a process after the currently runnable same-strata processes. Two processes that both #0 to win are unordered again one region later.
Never rely on the order of two processes in one region.
Cross the boundary only through region separation: nonblocking RTL and skewed or reactive testbench timing.
Treat every #0 in a testbench as a fossilized race, and every ->e without a waiting process as a lost trigger.
// snippet: event handshake fragment (partial, not compiled)
// A ->e trigger is instantaneous, never queued: the waiter must
// already be blocked at @e, or the trigger is silently lost.
// initial forever begin @e; ... end // waiter first
// wait (e.triggered); // same-step-safe catch of e
// Robust alternative: wait on a level, or use a mailbox.The display prints 4, the assertion judges the Preponed sample 4, and the strobe prints the settled 5.
#0 to run last. Which one wins?Neither reliably. Their relative order in the Inactive region is unspecified, so the race survives one region later.
A counter observed on the edge where it advances from 4 to 5. The Active-region display and immediate check see 4, and a $strobe issued on the same edge prints 5 in Postponed. The strobe line in the log is the evidence for that part; the settled value is also checked half a cycle later.
timeunit 1ns; timeprecision 1ps;
module tb_region_snapshot;
logic clk = 0;
always #5 clk = ~clk;
logic [7:0] count = 8'h00;
logic [7:0] seen_active = 8'h00;
logic [7:0] seen_late = 8'h00;
int errors = 0;
always_ff @(posedge clk) count <= count + 8'd1;
initial begin
repeat (4) @(posedge clk); // count reaches 4
@(posedge clk); // the edge where count goes 4 -> 5
seen_active = count; // Active read: the pre-NBA value
if (!(count < 10)) errors++; // immediate check on the same snapshot
$display("display reads %0d", seen_active); // prints 4
$strobe("strobe reads %0d", count); // same edge: prints 5 in Postponed
@(negedge clk);
seen_late = count; // settled value after the NBA update
if (seen_active !== 8'h04) errors++;
if (seen_late !== 8'h05) errors++;
if (errors == 0) $display("PASS: display and check saw 4 on the edge and the settled read saw 5 where count advanced");
else $fatal(1, "FAIL: %0d bad snapshot checks", errors);
$finish;
end
endmodule