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. initial, final & Startup Ordering

Part 1 · Language Foundations · Intermediate

initial, final & Startup Ordering

Multiple initial blocks and order nondeterminism, final blocks for end-of-simulation reporting, and TB startup sequencing patterns.

On this page

    initial blocks: concurrent, once, in no particular order

    Every initial block starts a process at time zero that runs its body once. The detail that bites: when several initial blocks exist — across one module or many instances — the LRM says nothing about which starts first. They are concurrent processes, and the simulator may begin them in any order. Code that works because initial block A “happens to run” before initial block B is a latent race: it can break on a different simulator, a different version, or a different compile-time optimization level. The corollary for multi-instance designs: each instance of a module contributes its own copy of every initial block, all unordered with respect to each other.

    startup_race.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module startup_race;
      int cfg;
    
      initial begin            // block A: configure
        cfg = 42;
      end
    
      initial begin            // block B: consume — RACE!
        $display("cfg = %0d", cfg);   // 42 or 0, simulator's choice
      end
    
      // FIX 1: one block, explicit order
      // initial begin cfg = 42; $display("cfg=%0d", cfg); end
    
      // FIX 2: event handshake — order by synchronization, not luck
      event cfg_done;
      initial begin cfg = 42; ->cfg_done; end
      initial begin @cfg_done; $display("cfg = %0d", cfg); end
    endmodule

    final blocks: guaranteed last words

    A final block runs exactly once when simulation ends — whether by $finish, reaching the end of scheduled events, or a tool-initiated stop — and it executes in zero time: no delays, no event controls, function calls only. That makes it the reliable place for end-of-simulation accounting: printing pass/fail summaries, dumping statistics counters, flagging transactions still in flight. Unlike a report task you must remember to call before every $finish, a final block fires on every exit path, including an unexpected $finish buried in third-party code. Like initial blocks, multiple final blocks execute in an undefined order relative to each other — keep them independent.

    scoreboard_top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    module scoreboard_top;
      int match_count, mismatch_count;
      int outstanding;
    
      // ... checking logic increments the counters during the run ...
    
      final begin
        $display("==== END OF SIMULATION REPORT ====");
        $display("matches    : %0d", match_count);
        $display("mismatches : %0d", mismatch_count);
        if (outstanding != 0)
          $display("** %0d transactions never completed **", outstanding);
        if (mismatch_count == 0 && outstanding == 0 && match_count > 0)
          $display("TEST PASSED");
        else
          $display("TEST FAILED");
      end
    endmodule
    Diagram
    SIMULATION LIFECYCLE — WHERE initial AND final RUN
    
      time 0                                            end of sim
        │                                                    │
        ▼                                                    ▼
      ┌──────────────┐  ┌──────────────────────────┐  ┌─────────────┐
      │ all initial  │  │   main simulation:       │  │ all final   │
      │ blocks start │  │   always blocks, NBA,    │  │ blocks run  │
      │ (UNORDERED)  │  │   assertions, TB tasks   │  │ (UNORDERED, │
      └──────┬───────┘  └──────────────────────────┘  │  zero-time) │
             │                      ▲                  └─────────────┘
             │  reset → config →    │                        ▲
             └─ enable traffic ─────┘            $finish ────┘
                (handshake-ordered,              end-of-events
                 never luck-ordered)             tool stop — ALL paths

    Testbench startup sequencing patterns

    Real testbenches need a deterministic boot order: assert reset, configure interfaces and models, then release traffic. Since initial-block ordering can't provide it, you build it explicitly. The three standard patterns: single orchestrator — one initial block calls reset, configure, and run tasks in sequence, with fork/join_none for background services; event or flag handshakes — producers fire named events (->reset_done) that consumers wait on, scaling to cross-module ordering; and in methodology code, phasing — UVM's build/connect/run phases are exactly this idea hardened into a framework, which is why “how does UVM solve initial-block ordering?” is a common bridge question in interviews.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    module tb_top;
      event reset_done, config_done;
    
      // Single orchestrator owns the order
      initial begin
        do_reset();              // drive rst_n low N cycles, release
        ->reset_done;
        configure_dut();         // program registers via bus task
        ->config_done;
        fork
          run_traffic();         // foreground stimulus
          watchdog_timer();      // background service
        join_any
        $finish;
      end
    
      // Independent monitor: waits for the handshake, not for luck
      initial begin
        @config_done;
        monitor_bus();           // starts only after config is real
      end
    endmodule

    Key takeaways

    • Multiple initial blocks start in undefined order — any cross-block dependency is a latent race.

    • Order startup with one orchestrator block or event handshakes, never with assumed scheduling.

    • final blocks run zero-time code on every exit path — the home for pass/fail summaries and leak checks.

    • UVM phases are the methodology-scale answer to initial-block ordering — a frequent interview bridge.

    Common pitfalls

    • Reading configuration in one initial block that another initial block writes — works until the simulator changes.

    • Putting delays or @(event) waits in a final block — illegal; final code must complete in zero time.

    • Printing the pass/fail verdict from the end of a run task — skipped when an unexpected $finish fires elsewhere.

    • Forgetting that every module instance replicates its initial blocks — N instances, N unordered processes.

    Previous

    23 / 363

    Next: Loops & Flow Control

    Up next

    Up nextLoops & Flow Control

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • always_comb, always_ff, always_latch
    • Blocking vs Nonblocking Assignments
    • Tasks vs Functions