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 Foundations35
    • SystemVerilog Data Types
    • Arrays: Packed, Dynamic, Queues & Associative
    • Procedural Code & Scheduling
      • always_comb, always_ff, always_latch
      • Blocking vs Nonblocking Assignments
      • Tasks vs Functions
      • The Event Scheduler & Stratified Regions
      • initial, final & Startup Ordering
      • Loops & Flow Control
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
    • Interfaces, Modports & Clocking Blocks
  • Object-Oriented SystemVerilog for Verification35
  • Constraint Randomization — Interview-Critical Deep Dive66
  • SystemVerilog Assertions — Interview-Critical Deep Dive63
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog38
  • Advanced Language & Integration30
  • Senior & Interview Preparation34
  • Principal-Level Verification28
Loading lesson

‹Procedural Code & Scheduling
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Language Foundations
  4. Procedural Code & Scheduling
  5. The Event Scheduler & Stratified Regions

Part 1 · Language Foundations · Intermediate

The Event Scheduler & Stratified Regions

The regions in one time slot, #0, $display versus $strobe, and deterministic startup ordering.

On this page

    Why this matters

    Two initial blocks passed on one simulator and failed on another. Nothing in the language says which starts first, and the test depended on it.

    After this lesson you can: name the regions of a time slot and what runs in each, predict $display against $strobe, and order startup code without relying on luck.

    Regions in one time slot

    Each simulation time is one time slot. Inside it the scheduler works through a fixed list of regions and loops back to Active while new events keep appearing. Time advances only when every region is empty.

    One time slot. The four blue regions decide what ordinary procedural code sees.

    A #0 delay moves a statement to the Inactive region, which wins against Active-region code exactly once. When both sides add #0, the race returns one region later, so stacked #0 fixes decay into ordering folklore.

    Watch d against q. The drive already shows the post-edge value while the sampler still holds the pre-edge one.

    • Slot 0: d is 1 while q is still 0; the sampler read the pre-edge 0.

    • Slot 1: d is 0 while q is 1; each lane mirrors the previous slot of the other.

    • Slot 2: d is 1 while q is 0.

    • Slot 3: d is 0 while q is 1.

    • Slot 4: d is 1 while q is 0.

    • Slot 5: d is 0 while q is 1.

    Seeing the final values

    Predict first At a clock edge a block runs q <= 5; and then $display(q) and $strobe(q). q was 0 before the edge. What do the two lines print?

    $display prints 0 and $strobe prints 5. $display runs in Active, before the NBA commit; $strobe waits for Postponed, after it.

    Common pitfall

    Debugging NBA values with $display prints pre-commit data because it runs in Active. $strobe runs in Postponed and shows the final values of the slot.

    Assertions sample in Preponed and evaluate in Observed, and program blocks run testbench code in Reactive. Both exist to keep testbench and design from racing; Scheduling Regions: the Full Map covers them once assertions and classes are familiar.

    Startup with initial and final

    Every initial block starts at time zero in an undefined order, and each module instance contributes its own copies. Any cross-block dependency on that order is a latent race across simulators.

    Order comes from structure instead: one orchestrator block, or event handshakes with wait (ev.triggered), which stays true for the whole slot and cannot lose a wakeup. A final block runs zero-time code on every exit path, which makes it the home for pass and open-transaction reports.

    Common pitfall

    Waiting with @ev misses a trigger that fired before the waiter started. wait (ev.triggered) is level-sensitive across the slot, so either order works.

    Interview

    “Two initial blocks pass on one simulator and fail on another. Why?”

    Their start order at time zero is undefined, and one depends on the other having run first. Make the order explicit with one orchestrating block or an event handshake.

    Check yourself A testbench reads a flop's output with $display right at the clock edge and sees the old value. Is the flop broken?

    No. $display runs in Active, before the flop's nonblocking update commits in NBA. Read it a little later, or use $strobe, to see the new value.

    Check yourself Two processes both use #0 to order against each other. Why does the fix stop working?

    #0 hops exactly one region. Once both sides hop, execution is unordered again one region later.

    Testbench

    Active-versus-NBA sampling, a lossless startup handshake with its final report, and a one-cycle request and grant checked a moment after the edge.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    module tb_nba_demo;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      int data = 0;
      int q1 = -1, q2 = -1;
      int errors = 0;
      always @(posedge clk) data <= data + 1;
      initial begin
        @(posedge clk);
        q1 = data;
        @(posedge clk);
        q2 = data;
        if (q1 !== 0) errors++;
        if (q2 !== 1) errors++;
        if (data !== 1) errors++;
        if (errors != 0) $fatal(1, "FAIL: sampled data does not match pre-edge values");
        $display("PASS: Active-region reads see pre-edge data 0 then 1, never the pending NBA commit");
        $finish;
      end
    endmodule
    Terminal
    PASS: Active-region reads see pre-edge data 0 then 1, never the pending NBA commit
    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module tb_startup;
      event cfg_done;
      int cfg = 0;
      int errors = 0;
      initial begin
        cfg = 42;
        ->cfg_done;
      end
      initial begin
        wait (cfg_done.triggered);
        if (cfg !== 42) errors++;
        if (errors != 0) $fatal(1, "FAIL: consumer ran with unexpected cfg");
        $display("PASS: triggered handshake delivers cfg 42 with no lost wakeup");
        $finish;
      end
      final begin
        $display("END: cfg=%0d", cfg);
      end
    endmodule
    Terminal
    PASS: triggered handshake delivers cfg 42 with no lost wakeup
    END: cfg=42
    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    module tb_grant_window;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      logic req = 0;
      logic gnt = 0;
      int errors = 0;
      always @(posedge clk) gnt <= req;
      initial begin
        @(negedge clk); req = 1;
        @(posedge clk); #1;
        if (gnt !== 1'b1) errors++;
        @(negedge clk); req = 0;
        @(posedge clk); #1;
        if (gnt !== 1'b0) errors++;
        if (errors != 0) $fatal(1, "FAIL: grant window does not match the one-cycle pulse");
        $display("PASS: one-cycle req pulse returns gnt for exactly one cycle");
        $finish;
      end
    endmodule
    Terminal
    PASS: one-cycle req pulse returns gnt for exactly one cycle

    Remember

    • Active runs blocking code and $display; NBA commits <=; Postponed runs $strobe.

    • #0 moves code one region later, once; stacking it only moves the race.

    • initial blocks start in an undefined order: order them with one orchestrator or a handshake.

    Rule of thumb: if two blocks must run in order, make the order part of the code.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run the startup bench (no .triggered on events); the other two run on both.

    Related topics

    • Next: Loops & Flow Control

    Previous

    22 / 363

    Next: initial, final & Startup Ordering

    Up next

    Up nextinitial, final & Startup Ordering

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • always_comb, always_ff, always_latch
    • Blocking vs Nonblocking Assignments
    • Loops & Flow Control
    clkdq
    An upstream flop beside its sampler across six edges