Day 14 / 25
← Day 13 Day 15 →
HomeVerificationDay 14 — UVM Phasing
Track 3 — UVM Architecture

UVM Phasing

By EcrioniX · Updated Jun 22, 2026

UVM phasing is the backbone of every testbench. It controls when each component builds, connects, runs, and cleans up. Master objections, top-down vs bottom-up ordering, task vs function phases, and you will stop fighting mysterious test terminations forever.

⏱ 18 min read 📖 Day 14 of 25 🎯 UVM Internals
Contents
  1. UVM Phase Overview — The 12 Standard Phases
  2. Build Phases — Function Phases, Top-Down/Bottom-Up
  3. run_phase — Time-Consuming Task Phase
  4. Objection Mechanism — raise/drop, Why Test Ends
  5. Post-Run Phases — extract, check, report, final
  6. Phase Synchronisation — wait_for_state()
  7. Custom Phases — uvm_topdown_phase
  8. Common Pitfalls
  9. Phase Timing Diagram
  10. Full Code Example
  11. FAQ
simulation time ───────────────────────────────────────────────► build top-down fn connect bot-up fn end_of_elab bot-up fn start_of_sim bot-up fn run_phase (task) ⏱ consumes simulation time raise_objection → drop_objection extract bot-up check bot-up report bot-up final top-down ◄── function phases (zero time) ──► ◄── post-run function phases ──►
UVM standard phase sequence — only run_phase advances simulation time

1. UVM Phase Overview

The UVM phase mechanism imposes a structured lifecycle on every component in a testbench hierarchy. Instead of an ad-hoc soup of initial blocks and fork-join, all testbench components go through the same well-defined sequence of phases. This guarantees that a driver is never asked to drive before the interface is connected, a scoreboard never checks before the DUT has been reset, and a report is never printed before all checking is complete.

UVM defines 12 standard phases executed in this order:

#Phase NameTypeOrderPurpose
1build_phasefunctiontop-downConstruct child components, get config
2connect_phasefunctionbottom-upConnect TLM ports between components
3end_of_elaboration_phasefunctionbottom-upFinal hierarchy adjustments, display topology
4start_of_simulation_phasefunctionbottom-upPrint banner, set simulator state
5run_phasetaskparallelApply stimulus and check — consumes time
6extract_phasefunctionbottom-upExtract results from DUT for analysis
7check_phasefunctionbottom-upCompare results, report errors
8report_phasefunctionbottom-upPrint coverage, stats, pass/fail
9final_phasefunctiontop-downClean up files, close databases
Note: UVM also defines 12 optional run-time sub-phases (pre_reset through post_shutdown) that split run_phase into finer stages. Most teams use only the top-level run_phase.

2. Build Phases — Function Phases, Top-Down/Bottom-Up

The four phases before run_phase are all function phases — they execute in zero simulation time. The key behavioural difference is execution order:

build_phase — Top-Down

The environment's build_phase runs first. It calls super.build_phase(phase) and then creates its child components using type::type_id::create("name", this). Only after the parent creates the child does the child's build_phase run. This top-down ordering is intentional: the parent must exist before it can create children, and parents often push config into uvm_config_db for children to retrieve during their own build_phase.

Pitfall: Never create child components in connect_phase or later. Always use build_phase for component construction. If a child component is created after build_phase, its own build_phase will never execute, leaving it unconfigured.

connect_phase — Bottom-Up

Once all components exist, connect_phase wires them together via TLM ports: monitor.ap.connect(scoreboard.analysis_export). It runs bottom-up — leaf components connect first, then their parents. This matters because the parent may aggregate connections from multiple children.

end_of_elaboration_phase and start_of_simulation_phase

These are rarely overridden in practice. end_of_elaboration_phase is useful for printing the complete component hierarchy with uvm_top.print_topology(). start_of_simulation_phase is a last chance to print banners or configure the simulator before time zero.

3. run_phase — The Time-Consuming Task Phase

Every other phase is a zero-time function. run_phase is a task — it is the only place where simulation time can advance. All stimulus generation, DUT driving, monitoring, and checking that requires timing belongs here.

UVM forks run_phase across all components simultaneously. Every component that overrides run_phase gets its own independent thread. The env's run_phase, the agent's run_phase, the driver's run_phase, the monitor's run_phase — they all run in parallel. This parallelism is a feature: the driver can apply stimulus while the monitor watches outputs and the scoreboard checks results, all concurrently.

Design rule: Each component's run_phase should run a tight loop doing only its own job. The driver loops on get_next_item/item_done. The monitor loops watching interface signals. Do not mix driver logic into the monitor's run_phase.

4. The Objection Mechanism

The UVM phase controller watches an internal objection counter. A phase ends only when all objections have been dropped. If the counter reaches zero (no objections), the phase ends and UVM moves to the next phase.

In run_phase, call phase.raise_objection(this) at the very start — before any delays — and phase.drop_objection(this) after all stimulus is done. The test is typically the only component that manages the top-level objection:

class my_test extends uvm_test;
  `uvm_component_utils(my_test)

  my_env env;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    my_sequence seq;
    // MUST raise before any delays
    phase.raise_objection(this, "test started");

    seq = my_sequence::type_id::create("seq");
    seq.start(env.agent.sequencer);

    // Optional: let the design settle
    #100;

    // MUST drop after all work is done
    phase.drop_objection(this, "test done");
  endtask
endclass

Multiple Objections

Any component can raise and drop objections. The phase controller adds all outstanding objection counts. The phase ends when the total drops to zero. If two sequences are running in parallel, each should raise its own objection and drop it when complete — the phase will not end until both have dropped.

Fatal pitfall: If you raise_objection but never drop_objection, the simulation will run forever (or until timeout). Always ensure drop_objection is called on every code path, including error paths. Wrap the test body in a try/finally if needed.

5. Post-Run Phases — extract, check, report, final

After all objections in run_phase are dropped, UVM proceeds through the post-run function phases — all bottom-up:

extract_phase

Pull final state from the DUT — read register values, drain queues. This phase exists to separate data collection from checking, allowing clean code organisation. Most teams skip this and do extraction inline in run_phase.

check_phase

Compare expected vs actual results. Scoreboards do their final checks here. Any unreceived expected transactions should trigger errors. This is also where coverage thresholds are checked.

report_phase

Print summaries, coverage numbers, pass/fail totals. Call uvm_report_info and uvm_report_error here to produce the final test log. UVM's built-in report server prints the global summary automatically.

final_phase

Top-down (like build_phase). Used for closing file handles, flushing databases, and cleanup that must happen in hierarchy order. Most teams do not override this phase.

6. Phase Synchronisation

Sometimes one component must wait for another before proceeding within run_phase. UVM provides phase synchronisation APIs:

// Wait until ALL objections on run_phase have been dropped
phase.get_objection().wait_for(UVM_ALL_DROPPED, null, 0);

// Wait for a specific phase to start (e.g., from a monitor)
uvm_phase run_ph = uvm_root::get().find(
  "uvm_test_top").get_current_phase();

// Use a semaphore or event for lightweight sync between two components
event mem_init_done;

// Memory model run_phase
task run_phase(uvm_phase phase);
  initialise_memory();
  ->mem_init_done;   // signal ready
  forever @(posedge clk) check_access();
endtask

// Traffic sequence — wait for memory ready
task body();
  @(mem_init_done);
  // now safe to send transactions
  repeat (100) `uvm_do(item)
endtask
Best practice: For complex init dependencies, use uvm_config_db to pass an event or semaphore from the test to sequences. Avoid tight coupling between components through shared global variables.

7. Custom Phases

Teams working on complex multi-IP SoCs sometimes define domain-specific phases — for example, a power_on_phase before the standard reset sequence. UVM supports this via uvm_topdown_phase or uvm_bottomup_phase:

// Define a custom function phase
class power_on_phase extends uvm_topdown_phase;
  function new(string name = "power_on_phase");
    super.new(name);
  endfunction
  function void exec_func(uvm_component comp, uvm_phase phase);
    comp.m_current_phase = phase;
  endfunction
  static function power_on_phase get();
    if (m_inst == null) m_inst = new();
    return m_inst;
  endfunction
  static local power_on_phase m_inst;
endclass

// Register with UVM schedule (call from test's build_phase)
uvm_domain domain = uvm_domain::get_common_domain();
uvm_phase build_ph = domain.find_by_name("build");
domain.add(power_on_phase::get(), .after_phase(build_ph));
Caution: Custom phases add complexity. Use them only when the standard phase ordering genuinely cannot express your needed sequencing. For most projects, events, semaphores, or sub-phase ordering within run_phase is sufficient.

8. Common Pitfalls

PitfallSymptomFix
Missing raise_objectionSimulation ends at time 0Call phase.raise_objection(this) at start of run_phase
Missing drop_objectionSimulation never ends / timeoutAlways drop after all stimulus is done
Creating children in connect_phaseChild has no build_phase; unconfiguredOnly create components in build_phase
Connecting ports in build_phaseTarget component may not exist yetAll TLM connections go in connect_phase
Using uvm_config_db in run_phaseConfig not found; component uses defaultsAlways set config before build_phase; get in build_phase
Race on drop_objection in parallel forksPremature phase endUse a single test-level objection; let sequences signal completion via event

9. Phase Timing Diagram

The diagram below shows how phases map to simulation time, and how components execute in parallel during run_phase:

simulation time → Zero-time elaboration build connect end_elab start_sim run_phase — time advances here test.run_phase (raises/drops objection) driver.run_phase (get_next_item loop) monitor.run_phase (sample interface) scoreboard.run_phase (check predictions) extract / check / report / final
All components run their run_phase in parallel; test controls lifetime via objections

10. Complete Code Example

This example shows an environment with a driver and monitor, each with their own phase methods, and a test that properly manages the objection:

// ─── my_driver.sv ────────────────────────────────────────────
class my_driver extends uvm_driver#(my_seq_item);
  `uvm_component_utils(my_driver)
  virtual my_if vif;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
      `uvm_fatal("CFG", "No virtual interface")
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      drive_item(req);         // drive signals, consumes time
      seq_item_port.item_done();
    end
  endtask

  task drive_item(my_seq_item item);
    @(posedge vif.clk);
    vif.data  <= item.data;
    vif.valid <= 1;
    @(posedge vif.clk);
    vif.valid <= 0;
  endtask
endclass

// ─── my_scoreboard.sv ────────────────────────────────────────
class my_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(my_scoreboard)
  uvm_analysis_imp#(my_seq_item, my_scoreboard) analysis_export;
  my_seq_item expected_q[$];

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    analysis_export = new("analysis_export", this);
  endfunction

  function void write(my_seq_item item);
    my_seq_item exp;
    if (expected_q.size() == 0) begin
      `uvm_error("SB", "Unexpected transaction received") return;
    end
    exp = expected_q.pop_front();
    if (item.data !== exp.data)
      `uvm_error("SB", $sformatf("MISMATCH exp=%0h got=%0h", exp.data, item.data))
    else
      `uvm_info("SB", "PASS", UVM_HIGH)
  endfunction

  function void check_phase(uvm_phase phase);
    if (expected_q.size() > 0)
      `uvm_error("SB", $sformatf("%0d expected items never received", expected_q.size()))
  endfunction

  function void report_phase(uvm_phase phase);
    `uvm_info("SB", "=== Scoreboard Report ===", UVM_NONE)
  endfunction
endclass

// ─── my_test.sv ──────────────────────────────────────────────
class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  my_env env;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    my_sequence seq = my_sequence::type_id::create("seq");
    phase.raise_objection(this, "starting test");
    seq.start(env.agent.sequencer);
    #200;   // drain pipe
    phase.drop_objection(this, "test complete");
  endtask

  function void report_phase(uvm_phase phase);
    super.report_phase(phase);
    uvm_report_server svr = uvm_report_server::get_server();
    if (svr.get_severity_count(UVM_ERROR) == 0)
      `uvm_info("TEST", "*** TEST PASSED ***", UVM_NONE)
    else
      `uvm_error("TEST", "*** TEST FAILED ***")
  endfunction
endclass

Key Takeaways — Day 14

FAQ

What is the difference between task phases and function phases in UVM?
Function phases (build_phase, connect_phase, end_of_elaboration_phase, start_of_simulation_phase, extract_phase, check_phase, report_phase, final_phase) execute in zero simulation time — no delays, no waits. Task phases (run_phase and the optional pre/post-run sub-phases) are time-consuming and can contain #delay, @event, and other timing constructs. The simulator only advances time during task phases.
Why does my UVM test end immediately without running?
The most common cause is forgetting to call phase.raise_objection(this) at the start of run_phase. The UVM phase controller drains all objections before ending a phase. If no component raises an objection, the objection count is already zero and the phase ends immediately — often before any stimulus is driven.
What order do build_phase callbacks execute across the component hierarchy?
build_phase is top-down: the parent's build_phase runs first, then each child's build_phase in order of creation. This allows the parent to push configuration into uvm_config_db before children retrieve it during their own build_phase. All phases after connect_phase (including all post-run phases) are bottom-up — leaves first, root last.
What is phase synchronisation in UVM and when do I need it?
Phase synchronisation lets one component wait for another to reach a specific state within run_phase. Common use cases: waiting for a memory model to finish initialisation before starting traffic, or waiting for a reset sequence to complete before enabling the monitor. Lightweight mechanisms like events and semaphores passed via uvm_config_db are usually sufficient. The formal phase API (wait_for_state) is rarely needed in practice.