How SystemVerilog simulation time is structured — the discrete event model, the eleven ordered time-slot regions, the reference simulation algorithm, and how PLI callbacks map to those regions.
A SystemVerilog description is a set of concurrent processes. Each process has state and responds to changes in its inputs to produce outputs. Processes include:
initial, always, always_comb, always_latch, always_ff blocksassign)Every change in state of a net or variable is an update event. Processes sensitive to that event are triggered to evaluate — this evaluation is itself an evaluation event. Evaluation events may produce further update events, forming a chain that runs until the time slot is quiescent.
SystemVerilog uses a discrete event simulation model. Simulation time advances in steps — a time slot is the collection of all events scheduled at a specific simulation time. The simulator never goes backward in time.
To provide predictable design-testbench interaction, each time slot is divided into eleven ordered regions. Events can only be scheduled forward — either into a later region in the same time slot (moving right in the diagram) or into a future time slot.
The scheduler works through the regions in order. When it finds a non-empty region, it executes all events there. Some executions may produce new events in the same or a later region of the same time slot, requiring another pass through the active regions. This is the iterative loop.
#0 delays in the current time step. Processed after all Active events complete.$monitor, $strobe, and read-only PLI. No new value changes allowed — time slot ends here.SV NEW = new regions added in SystemVerilog 3.1 • PLI = primary purpose is PLI callback support • ITERATIVE = part of the iterative loop
Most simulation activity happens here: blocking assignments, gate evaluation, continuous assignment updates. The order of execution within the Active region is non-deterministic — the simulator may process events in any order. New Active events can be created during Active processing, requiring re-entry into the Active region.
When code contains #0, the process is suspended and an event is placed in the Inactive region. It will resume after all currently-Active events finish. This is a way to explicitly “yield” to let other Active events run first.
// #0 schedules into Inactive — runs after all current Active events always @(posedge clk) begin #0; // yield: run after all posedge-clk Active events complete do_check(); end
All non-blocking assignment right-hand sides are sampled in the Active region when the statement executes. The actual update to the left-hand side variable is deferred to the NBA region. This guarantees that a block of non-blocking assignments all read the old values and all write the new values simultaneously — the cornerstone of correct flip-flop modelling.
// Both sampled in Active (old values), both written in NBA (simultaneously) always_ff @(posedge clk) begin a <= b; // RHS sampled: current b. Update deferred to NBA. b <= a; // RHS sampled: current a. Update deferred to NBA. // Effect: a and b swap — correct pipeline behaviour. end
After all RTL has settled (Active, Inactive, and NBA have all completed for this pass), property expressions are evaluated in the Observed region. Because the design is stable at this point, assertions always see clean, non-glitching values. This is the key reason assertions use the clocking block’s #1step sampling — it samples from the Preponed region (before any Active events) to capture the steady state before the clock edge.
Program block code and pass/fail actions from assertions execute here. The testbench sees the design in its post-assertion stable state. This ensures testbench stimulus and checks do not interfere with ongoing RTL evaluation — they always run after the design has settled.
This is the final region of the time slot. After the Postponed region, no more events can be scheduled for the current time — the slot ends. $monitor and $strobe display values here. Writing to any net or variable in the Postponed region is illegal.
The iterative regions — Active, Inactive, Pre-NBA, NBA, Post-NBA, Observed, Post-observed, and Reactive — can all produce new events that require re-processing. The scheduler loops through them repeatedly until all iterative regions are empty.
| Region | What can create events here | What new events go into |
|---|---|---|
| Active | Blocking assigns, gate/continuous assign, function calls | Active (more blocking), Inactive (#0), NBA (<=) |
| Inactive | #0 deferred events | Active (on next scan) |
| NBA | Non-blocking assignment updates | Active (triggers sensitive processes) |
| Observed | Property evaluation trigger (clocking block) | Reactive (pass/fail code) |
| Reactive | Program block code, assertion pass/fail actions | Active (if new events generated by testbench) |
The specification defines this pseudocode algorithm. All compliant simulators must produce user-visible results consistent with it (though they may use different internal algorithms for efficiency).
execute_simulation { T = 0; initialize values of all nets and variables; schedule all initialization events into time 0 slot; while (some time slot is non-empty) { move to next future non-empty time slot, set T; execute_time_slot(T); } } execute_time_slot { execute_region(Preponed); while (some iterative region is non-empty) { execute_region(Active); scan iterative regions in order { if (region is non-empty) { move events to Active; break; // restart the while loop } } } execute_region(Postponed); } execute_region { while (region is non-empty) { E = any event from region; remove E from region; if (E is an update event) { update the modified object; evaluate sensitive processes and schedule further events; } else { // evaluation event evaluate the process and schedule further events; } } }
The Programming Language Interface (PLI) allows external C/C++ code to interact with the simulator. PLI callbacks are registered as events associated with a specific (time, region) tuple. SystemVerilog maps existing Verilog PLI callbacks to explicit regions and adds new callback points.
| PLI Callback | Region | What it enables |
|---|---|---|
| cbAtStartOfSimTime cbNextSimTime cbAfterDelay | Pre-active | Read/write and create events before Active begins in the current time slot |
| tf_synchronize tf_isynchronize cbNBASynch | Pre-NBA | Read/write before non-blocking updates commit |
| cbReadWriteSynch | Post-NBA | Read/write after NBA updates have committed |
| tf_rosynchronize tf_irosynchronize cbReadOnlySynch cbAtEndOfSimTime | Postponed | Read-only access to final stable values in the time slot |
(time, region) point. Any region can receive an explicit PLI callback event.| # | Region | SV new? | Key role |
|---|---|---|---|
| 1 | Preponed | PLI read-only; #1step sampling conceptually here | |
| 2 | Pre-active | New | PLI read/write before Active |
| 3 | Active | RTL evaluation — blocking assigns, gates, continuous | |
| 4 | Inactive | #0 deferred events | |
| 5 | Pre-NBA | New | PLI read/write before NBA |
| 6 | NBA | Non-blocking assignment updates commit | |
| 7 | Post-NBA | New | PLI read/write after NBA |
| 8 | Observed | New | Property/assertion evaluation on stable design |
| 9 | Post-observed | New | PLI read-only after assertions |
| 10 | Reactive | New | Program block + assertion pass/fail code |
| 11 | Postponed | $monitor/$strobe; read-only; no new value changes |