Scheduling Semantics
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.
▶ Hardware Model Execution (§14.1)
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_ffblocks- Continuous assignments (
assign) - Gate primitives
- Tasks called asynchronously
- Program blocks and their threads
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.
🕐 Event Simulation (§14.2)
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.
- All events at time T form the time slot for T.
- The simulator processes the current time slot completely before advancing to the next non-empty time slot.
- Within a time slot, events are processed in region order (described below), not in an arbitrary global order.
- Processes within the same region can execute in any order — determinism is guaranteed only between regions.
📋 The Stratified Event Scheduler (§14.3)
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.
🔄 The Eleven Time-Slot Regions
#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
📌 Region Details
Active region — where RTL lives
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.
Inactive region — explicit #0
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
NBA region — where <= commits
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
Observed region — where assertions evaluate
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.
Reactive region — where testbench code runs
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.
Postponed region — monitoring only
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.
🔂 Iterative Regions and the Execution Loop
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.
What each region produces
| 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) |
📄 Reference Simulation Algorithm (§14.3.1)
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; } } }
🔗 PLI Callback Control Points (§14.4)
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.
📋 Quick Reference
Eleven regions at a glance
| # | 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 |
Rules to remember
- Preponed and Postponed run exactly once per time slot — outside the iterative loop.
- Active, Inactive, Pre-NBA, NBA, Post-NBA, Observed, Post-observed, and Reactive are iterative — they repeat until all are empty.
- Within any single region, event execution order is non-deterministic.
- Non-blocking assignment RHS is sampled in Active; the LHS update fires in NBA.
- Writing to any net or variable in the Postponed region is illegal.
- Program block code runs in Reactive — always after RTL has settled and assertions have fired.
- #1step sampling is conceptually equivalent to sampling in the Preponed region (the signal’s value just before the clock edge).
