Skip to content

SystemVerilog · Module 17

Scheduling Semantics & Timing Regions

SystemVerilog scheduling semantics — the ordered event regions of one simulation time step (Preponed, Active, Inactive, NBA, Observed, Reactive, Re-Inactive, Re-NBA, Postponed). Why <= is race-free, how SVA sampling / clocking blocks / program blocks stay race-free, $display vs $strobe vs $monitor, and the practical rules that keep simulation deterministic across tools.

Module 17 · Page 17.6

Every race condition, every "passes on VCS, fails on Questa" mystery, and every reason <= exists traces back to one thing: what order events execute in within a single simulation time step. SystemVerilog does not collapse a time step into an instant — it processes pending events through a fixed sequence of regions (Preponed → Active → Inactive → NBA → Observed → Reactive → Re-Inactive → Re-NBA → Postponed), and only when all regions are empty does time advance. This ordering is why non-blocking assignments are race-free, why SVA sampling is deterministic, why clocking blocks and program blocks eliminate testbench races, and why $strobe shows a different value than $display. This lesson — the model that the whole language sits on, specified in IEEE 1800-2017 Chapter 4 — closes Module 17 and the SystemVerilog track.

1. Engineering Problem — Order Decides Correctness

Consider a register swap on a clock edge:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Initial: a=3, b=5
always @(posedge clk) begin
    a = b;   // a becomes 5 immediately
    b = a;   // b reads the NEW a → 5, not the old 3
end
// Result: a=5, b=5 — WRONG. Not a swap, a bug.

With blocking = the second statement reads the value the first just wrote. Worse, if two separate always blocks both use blocking assignments on shared signals, the answer depends on which block the simulator runs first — and that order is unspecified. VCS may pick one, Questa the other; your test passes on one tool and fails on the other. That is the textbook race condition.

The fix is not luck or #0 hacks — it is the language's event-region model. Non-blocking <= splits into "evaluate now, update later," so every clock-edge block reads pre-edge values regardless of execution order. SVA, clocking blocks, and program blocks each live in a region chosen so their reads happen on stable values. Understanding the regions turns "why does this race?" from a guess into a lookup.

2. Mental Model — A Time Step Is Ordered Layers, Not an Instant

The picture every engineer carries:

One simulation time does not happen all at once — it is processed as a fixed sequence of regions. Within a region, events run in unspecified order (so depending on intra-region order is a race); between regions the order is guaranteed. The design side runs Preponed → Active → Inactive → NBA → Observed; the testbench side then runs Reactive → Re-Inactive → Re-NBA → Postponed. If any region generates new events, the simulator loops back to Active before time advances. Race-free constructs work by putting their reads in a region that runs after the writes they care about.

Four invariants this picture preserves:

  • Active = blocking =, continuous assigns, gate eval, and NBA right-hand-side evaluation; NBA = the deferred <= left-hand-side updates. The split is what makes <= order-independent.
  • The testbench half runs after the design half. Reactive (program blocks) runs after NBA, so testbench code always reads fully-committed values.
  • SVA samples in Preponed (before any writes) and evaluates in Observed (after NBA). Sampling on pre-write values is why assertions are deterministic.
  • Postponed is final. $strobe/$monitor run there on stable values; $display runs in Active and can show pre-update (stale) values.

3. Visual Explanation — The Nine Regions of One Time Step

Regions execute top to bottom; only when all are empty does time advance.

Nine ordered regions in a SystemVerilog time step: Preponed, Active, Inactive, NBA, Observed, Reactive, Re-Inactive, Re-NBA, Postponed.One simulation time step — executed top to bottomOne simulation time step — executed top to bottom1 · PreponedSVA samples input signals (read-only, before any write)SVA samples input signals (read-only, before any write)2 · Activeblocking =, continuous assigns, gate eval, NBA RHS eval, $displayblocking =, continuous assigns, gate eval, NBA RHS eval, $display3 · Inactive#0 delays (avoid in good code)#0 delays (avoid in good code)4 · NBAnon-blocking <= left-hand-side updates applied togethernon-blocking <= left-hand-side updates applied together5 · ObservedSVA concurrent-assertion properties evaluated (post-NBA)SVA concurrent-assertion properties evaluated (post-NBA)6 · Reactiveprogram-block code — race-free testbench readsprogram-block code — race-free testbench reads7 · Re-Inactiveprogram-block #0 delaysprogram-block #0 delays8 · Re-NBAprogram-block <= updatesprogram-block <= updates9 · Postponed$strobe, $monitor — final stable values; time cannot advance$strobe, $monitor — final stable values; time cannot advance
Figure 1 — the ordered event regions of a single simulation time step. The design half (Preponed→Observed) runs before the testbench half (Reactive→Postponed). If Active/Inactive/NBA create new events, the simulator loops back to Active before time advances.

4. The Active Region — Where Races Live

The Active region does most of the work — and it processes everything in unspecified order. Depending on that order is, by definition, a race.

Runs in ActiveExample
Blocking assignmentsa = b + 1; c = d & e;
Continuous assignmentsassign y = a & b;
Gate-primitive evaluationand (y, a, b);
Non-blocking RHS evaluationb <= a + 1; — the a+1 is computed here
$display / $write$display("val=%0d", x);
Module input-port updatesa driving signal change reaches the port

5. The NBA Region — Why <= Is Race-Free

A non-blocking assignment executes in two phases, deliberately separating read from write:

  1. Active region: the right-hand side of every <= is evaluated and the result queued. The left-hand side is not yet changed.
  2. NBA region: after all Active events finish, every queued update is applied simultaneously. All left-hand sides change at the same instant.

So every always @(posedge clk) block reads values from before the clock edge, no matter what order the blocks run — they are fully decoupled.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Initial: a=3, b=5
always @(posedge clk) begin
    a <= b;   // Active: RHS = b = 5 → queue (a ← 5)
    b <= a;   // Active: RHS = a = 3 → queue (b ← 3)
end
// NBA: a := 5, b := 3 simultaneously → correct swap (a=5, b=3)
 
// Blocking version is the bug from §1:
always @(posedge clk) begin
    a = b;    // a = 5 now
    b = a;    // b reads NEW a = 5 → a=5, b=5 (wrong)
end

6. Preponed & Observed — How SVA Stays Race-Free

Concurrent assertions must sample stable, pre-update values at the clock edge — not values caught mid-write. Two regions handle this:

  • Preponed (region 1): before any process runs, assertions sample their input signals. These reads are locked in; no write that time step can contaminate them.
  • Observed (region 5): after NBA completes (all writes done), assertion properties are evaluated against the sampled values. Pass/fail is decided here, on fully-committed state.

This is also exactly how a clocking block's input skew works — #1step samples in the Preponed region, which is why clocking blocks give race-free testbench sampling:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
default clocking tb_cb @(posedge clk);
    input  #1step dout;   // sample dout in Preponed — before any Active write
    output #1     din;    // drive din 1 time unit after the edge
endclocking
 
initial begin
    ##1;                          // advance one clock cycle via the clocking block
    $display("dout=%b", tb_cb.dout);   // sampled in Preponed → guaranteed stable
end

7. The Reactive Region — Race-Free Testbench Code

Code inside a program block runs in the Reactive regionafter Active, Inactive, and NBA. So program code always reads values the NBA region has already committed; it is structurally impossible for it to race with design code.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ module TB — reads dout in Active, the design also writes it there → race
module tb_bad;
  always @(posedge clk)
    $display("dout=%b", dout);   // may see old or new value
endmodule
 
// ✅ program TB — reads dout in Reactive, after the design's NBA committed → race-free
program tb_good;
  initial begin
    @(posedge clk);
    $display("dout=%b", dout);   // always the stable, post-edge value
  end
endprogram

This is the scheduling-level explanation of why Module 15's program blocks and clocking blocks eliminate the testbench-DUT race: they move the testbench's reads into a region that runs after the design's writes.

8. The Postponed Region — $strobe vs $display vs $monitor

Postponed is the last region of a time step — once there, no further events can be scheduled at the current time, so all values are final. $strobe and $monitor run here; $display runs back in Active.

TaskRegionWhen it printsBest for
$displayActiveimmediately, where the statement executesmid-procedure state, progress messages
$strobePostponedend of time step, after all updatesfinal stable signal values
$monitorPostponedwhenever any argument changes (auto)watching signals across the whole run
$writeActivelike $display, no newlinebuilding a line piecewise
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    result <= temp | c;             // NBA — result updates in the NBA region
    $display("result=%b", result);  // Active — prints the OLD result (pre-NBA)
    $strobe ("result=%b", result);  // Postponed — prints the NEW result (post-NBA)
end

9. Practical Rules — What Engineers Actually Do

You don't need to memorise the algorithm; follow six rules and you will not write a race:

  1. Clocked RTL → always <=. Decouples evaluation (Active) from update (NBA); all clock-edge blocks become order-independent.
  2. Combinational logic → always =. always_comb wants immediate effect so dependent logic re-evaluates in the same Active pass.
  3. Never mix = and <= in one always block. It's a lint warning and a maintenance trap — pick one per block.
  4. Testbench → program block or clocking block. Reactive-region (program) or Preponed-sampling (clocking block) reads are race-free.
  5. Print signal values with $strobe, not $display. $display in an Active-region block can print pre-NBA (stale) values.
  6. Never use #0 as a fix. A #0 (Inactive region) deferral is a code smell — it hides a race instead of removing it. Fix the race.

10. Debugging Guide — The Mistakes Everyone Makes Once

scheduling semantics — bugs every engineer hits the first time

1. Blocking assignment in an always_ff block

Symptom: A clocked block produces a one-cycle-off or simulator-dependent result, and synthesis adds unexpected combinational gates (or rejects the block).

Cause: a = b + 1; c = a * 2; in a clocked block — the second statement reads the new a from the same time step, burying a combinational dependency inside clocked logic.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always_ff @(posedge clk) begin a = b + 1; c = a * 2; end   // ❌ c uses new a

Fix: use <= so both RHS read old values in Active and both LHS update in NBA. If c truly needs the new a, that's a second cycle or a separate always_comb.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always_ff @(posedge clk) begin a <= b + 1; c <= a * 2; end

Guardrail: <= everywhere in clocked logic — no exceptions.

2. Reading DUT outputs too early in a module TB

Symptom: Testbench checks see the previous cycle's value; intermittent, tool-dependent mismatches.

Cause: A module's always @(posedge clk) reads dout in the Active region, before the DUT's NBA update fires.

Fix: read in a region that runs after the design's NBA — a program block (Reactive), a clocking block with #1step input skew (Preponed), or $strobe.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
program tb; initial begin @(posedge clk); check(dout); end endprogram

Guardrail: testbench sampling belongs in Reactive/Preponed, never raw Active.

3. Using $display to print final values — seeing stale data

Symptom: $display in a clocked block prints last cycle's value of an <=-assigned signal.

Cause: $display runs in Active, before the NBA update to that signal is applied.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) $display("q=%b", q);   // ❌ Active — pre-NBA q

Fix: use $strobe (Postponed) for final stable values.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) $strobe("q=%b", q);    // ✅ post-NBA

Guardrail: $strobe for signal values, $display for control-flow/progress messages.

4. Assuming clocking-block ##1 equals #1

Symptom: Time advances by a full clock period where you expected one time unit (or vice-versa).

Cause: ##1 means "advance one clock cycle" (one clock period); #1 means "advance one time unit." In a 10 ns clock, ##1 is 10 ns.

Fix: use ##N for clock-cycle delays in clocking-block context and #T for absolute time — never interchange them.

Guardrail: ## counts cycles, # counts time — different units, never mix carelessly.

11. Q & A

12. Cross-References & What's Next

This lesson covered the event-region model — the ordered regions of a time step, why <= is race-free, how SVA/clocking/program blocks stay race-free, and the $display/$strobe/$monitor distinction. It closes Module 17 (Advanced Topics) and the SystemVerilog language track.

The regions explain the "why" behind earlier modules:

With the language track complete, the natural next step is methodology: What UVM Is — and What It Is Not — every UVM environment is built on the race-free constructs this model underpins.

13. Quick Reference

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── One simulation time step (executed top to bottom) ────────────────
//  1 PREPONED    — SVA signal sampling (read-only)
//  2 ACTIVE      — blocking (=), continuous assigns, gate eval, NBA RHS, $display
//  3 INACTIVE    — #0 delays (avoid)
//  4 NBA         — non-blocking (<=) LHS updates
//  5 OBSERVED    — SVA property evaluation
//  6 REACTIVE    — program-block code (race-free TB reads)
//  7 RE-INACTIVE — program #0
//  8 RE-NBA      — program <= updates
//  9 POSTPONED   — $strobe, $monitor (final stable values)
//  regions 2–8 may loop back to ACTIVE before time advances
 
always_ff @(posedge clk or negedge rst_n)   // Rule 1: clocked → <=
    if (!rst_n) q <= 0; else q <= d;
 
always_comb y = a & b | c;                   // Rule 2: combinational → =
 
$strobe ("t=%0t q=%b", $time, q);            // Postponed — always stable
$display("t=%0t q=%b", $time, q);            // Active — may be stale
 
program tb; initial begin @(posedge clk); check(dout); end endprogram  // Reactive — race-free
 
default clocking cb @(posedge clk);          // #1step → Preponed sampling
    input #1step dout;  output #1 din;
endclocking

14. Summary — Module 17 & Track Closer

A SystemVerilog time step is not an instant — it is a fixed sequence of regions: Preponed → Active → Inactive → NBA → Observed (design half), then Reactive → Re-Inactive → Re-NBA → Postponed (testbench half), looping back to Active if new events appear before time advances. Order within a region is unspecified (so depending on it is a race); order between regions is guaranteed (so race-free constructs work by reading in a region that runs after the writes they need).

This one model explains the rest of the language. <= is race-free because its RHS evaluates in Active but its LHS updates in NBA, decoupling clock-edge blocks. SVA is deterministic because it samples in Preponed (before writes) and evaluates in Observed (after NBA). Program and clocking blocks kill the testbench race by reading in Reactive/Preponed, after the design commits. $strobe/$monitor show final values in Postponed where $display (Active) can show stale ones. Follow the six rules — <= in clocked logic, = in combinational, never mix them, program/clocking blocks for testbenches, $strobe for values, no #0 workarounds — and your simulation stays deterministic across every tool.

This completes Module 17 and the SystemVerilog language curriculum. The race-free constructs grounded here — clocking blocks, program blocks, assertions — are the foundation every UVM verification environment is built on; methodology is the next track.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet