Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
    • DPI-C: Calling C from SystemVerilog
    • bind: Attaching Verification to RTL
    • Macros, Defines & Compilation
    • Plusargs, Files & Runtime Configuration
    • Simulation Performance & TB Memory
    • RTL/TB Boundaries, Races & Timing
      • Anatomy of a TB/RTL Race
      • Clocking Blocks as the Cure
      • Scheduling Regions: the Full Map
      • Hierarchical References & Force/Release
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹RTL/TB Boundaries, Races & Timing
  1. Topics
  2. SystemVerilog
  3. Advanced Language & Integration
  4. RTL/TB Boundaries, Races & Timing
  5. Scheduling Regions: the Full Map

Part 7 · Advanced & Integration · Intermediate

Scheduling Regions: the Full Map

Preponed through Postponed: where RTL, testbench code, assertions and strobes run.

On this page

    One time step, fixed pipeline

    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.

    Diagram
    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 event

    Who runs where

    Each kind of code has a home region. The table below is the whole boundary discipline in one place.

    Code

    Region

    Notes

    RTL always blocks and continuous assigns

    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

    $display

    Wherever its caller runs

    Mid-step value, not the settled one

    $monitor and $strobe

    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.

    Three snapshots of one edge

    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.

    design.sv
    1
    2
    3
    4
    5
    6
    // 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.

    Common pitfall

    Stacking #0 delays until a test passes moves the ambiguity one region later without removing it. Triggering an event before any process waits on it loses the trigger silently.

    Interview

    Likely question: “Why does the assertion see a different value than the display?” Answer with regions: the assertion judges the Preponed sample while the display prints the mid-Active value. Senior answers name the region each observer reads.

    Check yourself A counter goes 4 to 5 at an edge. What do an in-block display, a concurrent assertion, and a strobe each report?

    The display prints 4, the assertion judges the Preponed sample 4, and the strobe prints the settled 5.

    Check yourself Two testbench processes both add #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.

    Testbench

    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.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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

    Related topics

    • Testbench anatomy

    Previous

    209 / 259

    Next: Hierarchical References & Force/Release

    Up next

    Up nextHierarchical References & Force/Release

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Anatomy of a TB/RTL Race