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
      • Why Phases Exist
      • The Phase Schedule: Common and Runtime Phases
      • Traversal Order Rules
      • Function Phases vs Task Phases
      • Signatures, Super Calls, and Debug
      • Inside the Phaser: Nodes, Imps and Schedules
    • UVM Phases: Build, Connect, Elaborate and Run
    • 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

‹The Phase System
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. The Phase System
  5. Why Phases Exist

Part 2 · Phases & Lifecycle · Intermediate

Why Phases Exist

Many authors share one timeline, so UVM creates, wires, runs and stops every component in the same order.

On this page

    You are integrating a scoreboard into a testbench that has no phases: each class wires itself in its constructor. The scoreboard needs a handle to the APB monitor's output, but the monitor is created three lines later, inside an agent that someone else owns. You reorder the top-level script, and now the stream agent breaks because it needed the scoreboard first. Two days later you have a 300-line script that only one person dares to touch.

    UVM's answer is to stop letting each component pick its own moment. Every component gets the same named callbacks, and the library calls them in one shared order. This lesson shows what that order buys you on the DMA bench, and what happens when one line of code is in the wrong callback.

    The idea: build every wall before anyone runs a cable

    Think of a building site. The framers put up every wall on every floor first. Only then does the electrician run cables between rooms, because a cable needs a wall at both ends. Power goes on after the wiring is inspected, and the building is signed off only when the work has actually stopped, not when a clock says it should have.

    A UVM testbench follows the same rule. A phase is one step of that shared schedule, and a phase method (such as build_phase or connect_phase) is the callback a component overrides to do its part of that step. The phaser runs one phase on every component before it starts the next. That gives four guarantees no single author could give alone:

    Question every integrator asks

    Phase that answers it

    Guarantee

    Does the thing I want to connect to exist yet?

    build_phase, then connect_phase

    Every component in the tree is built before any component connects.

    Is the wiring complete and legal?

    end_of_elaboration_phase

    All connections are resolved and checked before time 0 ends.

    When may I drive pins and start sequences?

    run_phase

    Every component's run thread starts together, after setup is done.

    Is the test finished, or just quiet?

    objections in run_phase

    Run ends when every component that is busy says it is done.

    The first row is the one integration bugs come from. A component never needs to know who else is in the tree or in what order they were created. It only needs to know that by the time its connect_phase runs, every build_phase in the whole tree has returned.

    The picture: two barriers at time zero

    Diagram: build_phase for the whole tree, then connect_phase for the whole tree, then binding resolution, then all run_phase threads start.
    The DMA bench at time 0. No connect_phase starts until every build_phase in the tree has returned, and no thread runs until every connection is resolved.

    The arrows between the boxes are barriers: nothing on the right starts until everything on the left has finished, across the whole tree. Inside a box the order follows the tree, parents first for build and children first for connect. The next lessons explain both directions; for now the barrier is what matters.

    Build it: the DMA env splits creating from wiring

    Here is the env of the course bench. Its build_phase only creates children and leaves one configuration note for the output agent. Its connect_phase only wires. It never creates in connect, and never wires in build.

    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
    // snippet: from the course DMA bench
    class dma_env extends uvm_env;
      `uvm_component_utils(dma_env)
      apb_agent      apb;
      axis_agent     in_agt, out_agt;
      dma_scoreboard sb;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // The output side is observed only: the DUT drives it.
        uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);
        apb     = apb_agent::type_id::create("apb", this);
        in_agt  = axis_agent::type_id::create("in_agt", this);
        out_agt = axis_agent::type_id::create("out_agt", this);
        sb      = dma_scoreboard::type_id::create("sb", this);
      endfunction
    
      function void connect_phase(uvm_phase phase);
        apb.ap.connect(sb.apb_export);
        in_agt.ap.connect(sb.in_export);
        out_agt.ap.connect(sb.out_export);
      endfunction
    endclass
    • apb.ap is the APB agent's public analysis port. The agent sets it in its own connect_phase (ap = mon.ap), which is why the env can only use it in connect. Traversal Order Rules explains why the agent's connect is guaranteed to run first.

    • sb.apb_export and its siblings are created in the scoreboard's constructor, so they exist the moment create returns. The env does not depend on that: by connect time, everything exists anyway.

    • The config_db note for out_agt is set before the agent is created. That is fine but not required. The agent reads it in its own build_phase, which runs after the env's build returns.

    Watch it run

    The base test prints the component tree in end_of_elaboration_phase, after every build and connect has finished. This is the start of the smoke test's log (ports and sequencer fields trimmed):

    Diagram
    UVM_INFO @ 0: reporter [RNTST] Running test dma_smoke_test...
    UVM_INFO uvm_root.svh(531) @ 0: reporter [UVMTOP] UVM testbench topology:
    ----------------------------------------------------------------
    Name                         Type                    Size  Value
    ----------------------------------------------------------------
    <unnamed>                    uvm_root                -     @68
      uvm_test_top               dma_smoke_test          -     @172
        env                      dma_env                 -     @242
          apb                    apb_agent               -     @256
            drv                  apb_driver              -     @458
            mon                  apb_monitor             -     @318
            sqr                  uvm_sequencer           -     @335
          in_agt                 axis_agent              -     @264
            ...
          out_agt                axis_agent              -     @272
            mon                  axis_monitor            -     @674
          sb                     dma_scoreboard          -     @280
    • Every line is stamped @ 0. All of build, connect and elaboration happen before simulated time moves.

    • out_agt has only a monitor: the passive setting the env left in config_db was read during the agent's build.

    • The whole tree exists at this point, so this is the right moment to audit it. Elaboration, Start and the Pre-Time-Zero Contract covers what else belongs here.

    What breaks: the connect line that moved into build

    The Env Owns the Infrastructure opened with a colleague moving dma_env's three connect calls into build_phase, and the run dying at time 0 on apb.ap.connect(sb.apb_export). Now you can name the exact mechanism.

    The root cause is the barrier. create constructed the agent, but nothing inside it has run yet: its build_phase (which creates the monitor) and its connect_phase (which sets ap = mon.ap) both come later. apb.ap is a handle that nobody has assigned. The fix is to put the line back in connect_phase, where every build in the tree has finished and every child has forwarded its ports.

    Notice that the scoreboard side would have worked: its exports are built in its constructor. Code that works for one endpoint and fails for the other is the classic sign of wiring in the wrong phase. The rule "create in build, wire in connect" removes the question entirely.

    Common pitfall

    A bench that ends its test with a fixed delay (#10us; $finish;) has the same disease in the time dimension: it guesses when the work is done. UVM ends the run phase when the busy components drop their objections. run_test, Timeouts and How a Simulation Ends shows what happens when no one raises one.

    Interview

    "Why does UVM need phases at all? Couldn't each component just do its setup in its constructor?"

    A weak answer: "Phases organise the code into stages: build, connect, run, report." That describes phases without saying what problem they solve.

    A strong answer: "A constructor only knows about itself and its parent. Wiring needs both endpoints to exist, and in a tree written by many teams nobody controls creation order. Phases add barriers: every build_phase in the whole tree finishes before any connect_phase starts, so a connect can assume every port exists. The same idea at the end: run stops when every busy component drops its objection, not after a guessed delay."

    Follow-ups to expect: Why is build top-down but connect bottom-up? (the next lessons) and What runs between connect_phase and run_phase? (end_of_elaboration and start_of_simulation, both at time 0).

    Check yourself A new passive agent is added to the env, and an unrelated active agent's driver connection breaks. What kind of mistake does that point to?

    Code that depends on creation order: some component wired or read a handle during build, so adding a sibling changed what existed at that moment. With all creates in build_phase and all wiring in connect_phase, adding a component cannot change what exists when connect runs, because every build has finished by then.

    Check yourself In dma_env::connect_phase, why is sb.apb_export safe to use even though the scoreboard's own connect_phase may not have run yet?

    The export is created in the scoreboard's constructor, and connect only needs the object to exist. The env's connect does not depend on anything the scoreboard does in its connect. What it does depend on is the agent's connect (ap = mon.ap), and bottom-up order guarantees that one ran first.

    Check yourself Someone argues that wiring in build_phase is fine "as long as you create the children first". Give the counter-example from this bench.

    apb.ap is assigned in the APB agent's connect_phase, and the monitor behind it is created in the agent's build_phase. Neither has run when the env's build creates the agent, so apb.ap.connect(...) in build dereferences a null handle. Creating a child does not run any of its phases.

    Next: the barriers above are only part of the schedule. In The Phase Schedule you will see all twenty-one phases, including twelve that run alongside run_phase.

    Related topics

    • The Phase Schedule

    • Connect Phase: Bottom-Up TLM Wiring

    • Your first UVM testbench

    Previous

    19 / 280

    Next: The Phase Schedule: Common and Runtime Phases

    Up next

    Up nextThe Phase Schedule: Common and Runtime Phases

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Traversal Order Rules
    • Function Phases vs Task Phases
    • Signatures, Super Calls, and Debug