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 Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
    • The Phase System
    • UVM Phases: Build, Connect, Elaborate and Run
      • Why build_phase Is Top-Down
      • config_db Gets and Conditional Construction
      • Connect Phase: Bottom-Up TLM Wiring
      • Elaboration, Start and the Pre-Time-Zero Contract
      • The Run Phase: Parallel Execution and Duration
      • Runtime Sub-Phases: Reset, Configure, Main and Shutdown
      • What super.build_phase() Really Does
      • run_test, Timeouts and How a Simulation Ends
    • Objections Deep Dive: Drain Time and Propagation
    • Phase Debugging and End-of-Test Signoff
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹UVM Phases: Build, Connect, Elaborate and Run
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. UVM Phases: Build, Connect, Elaborate and Run
  5. Runtime Sub-Phases: Reset, Configure, Main and Shutdown

Part 2 · Phases & Lifecycle · Intermediate

Runtime Sub-Phases: Reset, Configure, Main and Shutdown

The twelve runtime phases, what they are good for, and why drivers and monitors stay in run_phase.

On this page

    A VIP team rewrote their driver to use reset_phase and main_phase, "to be phase-aware". Most tests passed. A few hung forever at the end of main, with the sequencer waiting for an item_done that never came. The driver code was correct line by line. It was in the wrong kind of phase.

    The idea: named stages on a parallel track

    The twelve runtime phases split the run window into four named stages, each with a pre, core and post phase: reset, configure, main, shutdown. They run in order on the uvm domain's track, in parallel with run_phase (the schedule picture is in The Phase Schedule). Each phase has its own objection, so a stage ends only when nobody objects to it, and the next stage starts only then.

    Think of a stage play with acts. The actors (sequences) perform in their act and leave; the stage crew (drivers and monitors) stays for the whole show. When an act ends, everyone in it is ushered off, mid-sentence if necessary. You would not cast the stage crew as actors in act two.

    Stage

    Phases

    Good for

    Reset

    pre_reset, reset, post_reset

    A test or sequence that applies and releases reset, or waits for it.

    Configure

    pre_configure, configure, post_configure

    Register programming before traffic, for example through the register model.

    Main

    pre_main, main, post_main

    The traffic that is the point of the test.

    Shutdown

    pre_shutdown, shutdown, post_shutdown

    Letting in-flight work finish before checking.

    The picture: why a driver must not live in main_phase

    Sequence diagram: a driver in main_phase takes an item from the sequencer, is killed when main_phase ends, and the item is never completed, so the next request waits forever.
    When a runtime phase ends, the phaser kills every thread it started. A driver running in main_phase can be killed between get_next_item and item_done, leaving the sequencer and any sequence still using it stuck.

    That is the opening bug. Whenever main ended while the driver was in the middle of an item, the phaser killed the driver's thread, and item_done never happened. A later sequence on the same sequencer then waited forever. Drivers and monitors serve the whole run window, so they live in run_phase, which spans every runtime phase. Tests and sequences use the runtime phases.

    Build it: reset inside run_phase, stages in the test

    The DMA bench handles reset the simple way: each driver drives its idle values, then waits for @(posedge vif.rst_n) at the start of its run_phase. A driver that must survive a reset in the middle of a test watches for it in run_phase and restarts its loop:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // snippet
    task run_phase(uvm_phase phase);
      forever begin
        vif.psel    <= 1'b0;                  // idle while reset is asserted
        vif.penable <= 1'b0;
        @(posedge vif.rst_n);
        fork begin                            // isolates disable fork to this block
          fork
            forever begin
              seq_item_port.get_next_item(req);
              drive(req);
              seq_item_port.item_done();
            end
            @(negedge vif.rst_n);             // reset asserted mid-test
          join_any
          disable fork;                       // stops the drive loop
        end join
      end
    endtask

    The outer fork begin ... end join matters: disable fork kills every child of the current process, and the wrapper makes the drive loop and the reset watcher the only children it can reach. A real driver also completes or abandons the item it was holding when reset hit, so the sequencer is not left waiting.

    The test can then use the stages for its own flow. A sequence that should hold a stage open for as long as it runs can ask for that itself:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet
    class dma_cfg_seq extends uvm_sequence #(apb_item);
      `uvm_object_utils(dma_cfg_seq)
      function new(string name = "dma_cfg_seq");
        super.new(name);
        set_automatic_phase_objection(1);  // raise when started, drop when body ends
      endfunction
      task body();
        // KEY, LEN and CTRL writes
      endtask
    endclass
    
    // in the test:
    task configure_phase(uvm_phase phase);
      dma_cfg_seq s = dma_cfg_seq::type_id::create("s");
      s.set_starting_phase(phase);
      s.start(env.apb.sqr);
    endtask

    set_automatic_phase_objection(1) makes the sequence raise an objection on its starting phase when it starts and drop it when it finishes. get_starting_phase() and set_starting_phase() replace the old starting_phase member, which is deprecated in UVM 1.2 and removed in IEEE 1800.2. Here the test raises nothing itself; the sequence holds configure open for exactly as long as it runs.

    A default sequence starts automatically on a sequencer when a given phase begins, set through config_db with a phase-qualified path such as uvm_config_db#(uvm_object_wrapper)::set(this, "env.apb.sqr.configure_phase", "default_sequence", dma_cfg_seq::type_id::get()). It is convenient for simple flows and opaque for complex ones; many teams prefer explicit starts in the test.

    When the stages earn their keep

    The runtime phases pay off when several independent parts of a bench must reach the same milestone before any of them moves on: every interface out of reset before any configuration, all register programming done before any traffic, every stream drained before checking. Each stage's objection is that barrier, and a sequence can join it without knowing who else is there. They also give a mid-test reset a named place to return to. For a single-interface block bench like the DMA, a straight-line run_phase in the test expresses the same order more readably, and the bench does exactly that: program, stream, wait for the interrupt, check.

    Watch it run

    In the DMA bench nobody objects in any runtime phase, so all twelve start at time 0 and eleven of them end there, while the traffic runs in run_phase. The exception is post_shutdown: it ends together with run_phase, at 655000, as The Common Phase List explains.

    Diagram
    UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] started uvm.uvm_sched.main
    UVM_INFO dma_tb.sv(...) @ 0: uvm_test_top [PH] ended   uvm.uvm_sched.main
    ...
    UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] ended   common.run
    UVM_INFO dma_tb.sv(...) @ 655000: uvm_test_top [PH] ended   uvm.uvm_sched.post_shutdown

    With the configure sequence above, configure would end at the time the last register write completed, main would start right after, and run_phase would still be ending at whatever time the last objection in either track dropped.

    What breaks: the half-adopted schedule

    A subsystem bench combined two VIPs: one whose test flow used main_phase objections, and one whose sequences raised only in run_phase. The second VIP's traffic ran during configure, because run_phase had started at time 0 and nothing tied its stimulus to the configure stage. The DUT received data before its registers were programmed, and the failures looked like DUT bugs for a week.

    The lesson the team wrote down: pick one convention per bench. Either all stimulus is sequenced inside run_phase by the test (the most common choice, and the one the DMA bench uses), or the test and every sequence use the runtime stages consistently. Mixing them means each track's objections gate only their own track.

    Note

    A runtime phase can also be jumped back, for example from main to reset, with phase.jump(). That, and the reset-aware alternative most teams prefer, is covered in Reset Phase and Phase Jump.

    Interview

    "Why do many teams forbid runtime phases in VIP drivers and monitors?"

    A weak answer: "Runtime phases are complicated and not well supported." It is a preference, not a reason.

    A strong answer: "When a runtime phase ends, the phaser kills every thread it started. A driver in main_phase can be killed between get_next_item and item_done, which leaves the sequencer with an unfinished item and hangs the next sequence; a monitor can be killed mid-transaction and drop it. Drivers and monitors serve the whole run window, so they go in run_phase and handle reset themselves. Tests and sequences can use the runtime phases, each stage held open by its own objections."

    Follow-ups: How does a sequence hold a runtime phase open without the test raising? (set_automatic_phase_objection(1) with a starting phase) and What replaced starting_phase? (get_starting_phase()/set_starting_phase()).

    Check yourself No component overrides post_reset_phase. What happens to it?

    It still runs: the phaser starts and ends it, at the same time if nobody objects. An empty runtime phase costs almost nothing.

    Check yourself A register sequence is started in configure_phase but raises no objection, and the test raises none either. What happens?

    configure ends immediately (nobody objects), and the phaser kills the sequence's thread with it, probably before the first write completes. Raise an objection around it, or use set_automatic_phase_objection(1) with set_starting_phase(phase).

    Check yourself A driver is kept in run_phase, but the test uses the runtime phases. Does the driver keep working from reset through shutdown?

    Yes. run_phase spans the whole uvm domain's schedule, so the driver's thread lives from time 0 until run ends, serving whichever stage's sequences send it items.

    Next: every phase method so far started with super. What super.build_phase() Really Does shows what the library does in those calls, and the bench test that passes when it should not.

    Related topics

    • What super.build_phase() Really Does

    • Reset Phase and Phase Jump

    • The driver's run_phase loop

    Previous

    30 / 280

    Next: What super.build_phase() Really Does

    Up next

    Up nextWhat super.build_phase() Really Does

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why build_phase Is Top-Down
    • config_db Gets and Conditional Construction
    • Connect Phase: Bottom-Up TLM Wiring