UVM-03: UVM Phases — VLSI Trainers
VLSI Trainers UVM Series · UVM-03
UVM Series · UVM-03

UVM Phases

The complete UVM phase system — all 12 standard phases, execution order and direction, function phases vs task phases, the run-time sub-phases, phase objections, drain time, phase_ready_to_end, and common phase mistakes that break simulations.

📋 Phase System Overview

The UVM phase system controls the execution order of every component in the testbench. Without phases, there would be no guarantee that a component’s configuration is complete before it tries to use it, or that the testbench is fully connected before the stimulus starts. Phases enforce a consistent lifecycle across all components, regardless of how complex the hierarchy is.

Every uvm_component participates in phases by overriding the corresponding phase methods. You only override the phases relevant to your component — unused phases are simply not overridden. The UVM infrastructure calls each phase method at the right time and in the right order automatically.

Phases are not optional. The UVM infrastructure calls phase methods whether you override them or not. If you do not override a phase method, the base class version runs (which does nothing). If you override it, your code runs at the correct point in the simulation lifecycle.

📋 Three Phase Groups

The 12 standard UVM phases are organised into three groups that execute in strict sequence:

UVM Phase Execution Order — All 12 Standard Phases Group 1 — Build Phases function phases · zero simulation time build_phase top-down · creates component tree connect_phase bottom-up · TLM port wiring end_of_elaboration_phase bottom-up · final topology checks start_of_simulation_phase bottom-up · display banner/config All functions (0 sim time) Cannot use #delay or @(event) Group 2 — Run-Time Phases task phases · consume simulation time run_phase concurrent · main simulation work runs in parallel with ↓ pre_reset reset_phase post_reset pre_configure configure post_configure pre_main main_phase post_main shutdown_phase All tasks (consume sim time) run_phase + sub-phases run in parallel Group 3 — Cleanup Phases function phases · zero simulation time extract_phase bottom-up · gather final results check_phase bottom-up · error checking report_phase bottom-up · print results final_phase top-down · last exit tasks All functions (0 sim time) Runs after ALL run-time phases end vlsitrainers.com
Figure 1 — All 12 UVM standard phases in their three groups. Group 1 (build phases) configures and constructs the testbench in zero time. Group 2 (run-time phases) executes stimulus in real simulation time — run_phase and the sub-phases execute in parallel with each other. Group 3 (cleanup phases) collects and reports results in zero time after all run-time phases end.

📋 Build Phases

The four build phases execute in zero simulation time. They run before the simulator advances time, establishing the complete testbench structure and connectivity.

build_phase — Top-down, function

The first and most important phase. Runs top-down so that each parent can configure its children before creating them. This is where every component creates its sub-components using the factory, retrieves configuration from config_db, and applies factory overrides.

Critical rule: Always call super.build_phase(phase) at the start of your override. The base class build_phase applies any config_db values that were set using field automation (`uvm_field_* macros). Forgetting super.build_phase(phase) is one of the most common UVM bugs.

function void build_phase(uvm_phase phase);
  super.build_phase(phase);   // ALWAYS call first
  m_agent = my_agent::type_id::create("agent", this);
endfunction

connect_phase — Bottom-up, function

Runs after all build_phases complete. Connects TLM ports (analysis ports, seq_item ports) between components. Retrieves virtual interface handles from config_db. Bottom-up order ensures child ports are ready before parents connect to them.

end_of_elaboration_phase — Bottom-up, function

The testbench hierarchy and connectivity are now final. Use this phase to print the testbench topology (uvm_top.print_topology()), verify that expected components exist, or perform final configuration checks. No new construction or connections should be made here.

start_of_simulation_phase — Bottom-up, function

Last function phase before time starts. Use it for displaying simulation banners, printing the random seed, or logging initial configuration state. Simulation time is still zero here.

📋 Run-Time Phases

Run-time phases are tasks — they consume real simulation time. The run_phase and all sub-phases execute in parallel with each other. This means a component’s run_phase and its reset_phase run at the same time in different threads.

run_phase — Concurrent task

The original and most widely used run-time phase. Drivers, monitors, and scoreboards nearly always use run_phase. It runs concurrently in all components that implement it and ends when all objections to it are dropped.

For most testbenches, using only run_phase is the right choice. The sub-phases (reset, configure, main, shutdown) exist for structured multi-phase stimulus organisation but add complexity for limited benefit.

Run-time sub-phases

The sub-phases provide finer-grained control of when stimulus occurs. They run in sequence with each other (reset → post_reset → pre_configure → configure → post_configure → pre_main → main → post_main → shutdown) but in parallel with run_phase.

Run-Time Phases — Parallel Execution Model simulation time → run_phase (spans entire run-time period) reset post_reset pre_configure configure main_phase ★ post_main shutdown ← sequential → Drivers/monitors use run_phase. Tests that need structured stimulus use reset/configure/main/shutdown. vlsitrainers.com
Figure 2 — Run-time phase parallel execution. run_phase spans the full run-time period. The sub-phases execute sequentially with each other (reset, then post_reset, then configure, etc.) but all overlap with run_phase. Most drivers and monitors use only run_phase. Tests that want structured stimulus can use the named sub-phases.
For most designs: use only run_phase. The sub-phases are useful when you need guaranteed ordering between stimulus phases (e.g. reset must finish before configuration starts). But mixing run_phase with sub-phases in the same testbench adds complexity — components using different phases must coordinate via objections, which is error-prone. If your team is new to UVM, stay with run_phase only until the need for sub-phases is clear.

📋 Cleanup Phases

The four cleanup phases run after all run-time phases have ended. They execute in zero simulation time, bottom-up.

PhaseDirectionTypical use
extract_phaseBottom-upRead final DUT register values via backdoor, collect statistics from monitors, extract coverage data
check_phaseBottom-upCheck for unmatched transactions in scoreboards, verify expected events occurred, assert final coverage requirements
report_phaseBottom-upPrint pass/fail summary, coverage report, error counts, performance statistics
final_phaseTop-downLast cleanup before simulator exits — close files, flush output, print final banner
Use check_phase for end-of-test assertions, not run_phase. Checking at the end of run_phase misses any in-flight transactions that were still being processed. check_phase runs after the scoreboard has had time to process every transaction, making it the correct place for “was everything matched?” checks.

📋 Function vs Task Phases

This distinction is critically important and a frequent source of bugs for UVM beginners:

PropertyFunction phasesTask phases
Which phasesbuild, connect, end_of_elab, start_of_sim, extract, check, report, finalrun_phase and all sub-phases (reset, configure, main, shutdown, and their pre/post variants)
Simulation timeZero — execute instantaneouslyCan consume any amount of simulation time
Allowed constructsFunctions only — no #delay, no @(posedge clk), no waitFull task semantics — #delay, @event, wait, blocking calls all allowed
ConcurrencySequential — one component at a time per directionConcurrent — all components run simultaneously
Phase argumentfunction void build_phase(uvm_phase phase)task run_phase(uvm_phase phase)
Never use time-consuming constructs in function phases. Putting #10ns or @(posedge clk) in build_phase, connect_phase, or check_phase will cause a simulator error or hang. These phases are functions — they cannot consume time. If you need time to pass before checking something, use run_phase or extract_phase with a separate check mechanism.

📋 Phase Objections

A phase objection is a counter maintained by the UVM infrastructure. While the count is non-zero, the phase continues. When the count reaches zero (after having been non-zero), the phase ends. Any component or sequence can raise and drop objections at any time during the phase.

Phase Objection Counter — run_phase Lifecycle simulation time → count = 2 1 1 raise(test) raise(seq) drop(seq) drop(test) run_phase ends (drain time elapsed) vlsitrainers.com
Figure 3 — Objection counter during run_phase. The test raises an objection (count 0→1), then a sequence raises another (count 1→2). The sequence finishes and drops its objection (count 2→1). The test finishes and drops its objection (count 1→0). After drain time elapses, run_phase ends and the cleanup phases begin.

API

task run_phase(uvm_phase phase);
  // Raise before doing any work — keeps phase alive
  phase.raise_objection(this);
  phase.raise_objection(this, "Optional message for debug");

  // Do your simulation work here
  seq.start(m_seqr);

  // Drop when done — allow phase to end if no others pending
  phase.drop_objection(this);
endtask

// Set optional drain time — wait N ns after count=0 before ending
function void start_of_simulation_phase(uvm_phase phase);
  uvm_objection obj = phase.get_objection();
  obj.set_drain_time(this, 100ns);
endfunction
One raise_objection per component, in pairs with drop_objection. Every raise_objection must be matched by exactly one drop_objection. If you raise more than you drop, the simulation never ends (hangs). If you drop without raising (or drop too many times), UVM reports a fatal error. Always structure your run_phase with raise at the top and drop at the end, or use a try/finally equivalent.

📋 Drain Time

When the last objection is dropped (count reaches zero), the phase does not end immediately. The UVM infrastructure waits for a configurable drain time before actually ending the phase. If any component raises a new objection during the drain time, the timer resets.

Drain time is essential for pipelines and scoreboards. When a test drops its objection after the last sequence completes, there may still be transactions in flight — packets in a pipeline, DMA transfers in progress, responses being written to memory. Drain time gives these in-flight operations time to complete and reach the scoreboard before the simulation ends.

// Set drain time in start_of_simulation_phase
function void start_of_simulation_phase(uvm_phase phase);
  uvm_phase run_ph = uvm_domain::get_uvm_schedule().find_by_name("run");
  run_ph.get_objection().set_drain_time(this, 1us);
endfunction

// Or simpler — set on the current phase inside run_phase
task run_phase(uvm_phase phase);
  phase.raise_objection(this);
  phase.get_objection().set_drain_time(this, 200ns);
  seq.start(m_seqr);
  phase.drop_objection(this);
endtask

📋 phase_ready_to_end

phase_ready_to_end is a special callback that is called on every component when the objection count first reaches zero. A component that is not yet ready for the phase to end — for example, a scoreboard with unmatched transactions still being processed — can raise a new objection from inside this callback, extending the phase.

class my_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(my_scoreboard)

  int pending_count = 0;  // tracks in-flight transactions

  // Called when objection count first hits zero
  function void phase_ready_to_end(uvm_phase phase);
    if (pending_count > 0) begin
      // Not ready — raise a new objection to extend the phase
      phase.raise_objection(this, "Scoreboard still has pending txns");
      fork begin
        wait (pending_count == 0);  // wait until all matched
        phase.drop_objection(this);
      end join_none
    end
  endfunction
endclass
phase_ready_to_end must fork its drop_objection. The callback itself is a function — it cannot block. If you raise an objection inside it, you must fork a thread that waits for the condition and then drops the objection. The fork/join_none pattern shown above is the standard way to do this.

📋 Common Phase Mistakes

MistakeSymptomFix
Forgetting super.build_phase(phase)config_db get() always returns 0. Fields set via `uvm_field_* macros are not applied.Always call super.build_phase(phase) as the first line of your build_phase override.
Raising objection but never dropping itSimulation hangs forever. UVM prints “objection not dropped” after timeout.Ensure every raise_objection has a matching drop_objection in all code paths, including error exits.
Not raising any objectionSimulation ends immediately — run_phase exits before any stimulus runs.Raise an objection at the start of run_phase in the test (or whichever component drives the test scenario).
Using #delay in build_phaseSimulator error: “cannot call time-consuming code in function”.Only use time-consuming constructs in task phases (run_phase). Build, connect, and cleanup phases are functions.
Creating components in connect_phaseComponents created in connect_phase may not participate in phases correctly. Path name may be wrong.Create all components in build_phase only. connect_phase is for connections, not construction.
Connecting ports in build_phasePort connections fail — the target component may not exist yet (build is top-down).All TLM port connections go in connect_phase, never build_phase.
Forgetting drain time for pipelinesScoreboard misses transactions. Test ends while packets are still in the DUT pipeline.Set drain time via set_drain_time() or use phase_ready_to_end in the scoreboard.

📋 Quick Reference

ItemKey fact
Phase groupsBuild (4 phases) → Run-time (1 + 9 sub-phases) → Cleanup (4 phases)
build_phase directionTop-down — parent builds before children
connect_phase directionBottom-up — children connect before parents
cleanup phases directionBottom-up (extract, check, report) except final_phase (top-down)
Function phasesAll phases except run_phase and sub-phases — zero simulation time, no blocking
Task phasesrun_phase + sub-phases — can consume time, blocking constructs allowed
run_phase concurrencyRuns simultaneously in ALL components that implement it
super.build_phase(phase)Must be first line — applies `uvm_field macros and config_db auto-get
Objection count = 0 after raisePhase ends (after drain time)
Objection raise/drop ruleEvery raise must have exactly one matching drop
No objection raisedrun_phase exits immediately — simulation ends with no stimulus
Drain time purposeWait N simulation time after count=0 for in-flight transactions to complete
set_drain_time()phase.get_objection().set_drain_time(this, Nns)
phase_ready_to_end useRaise objection from it to extend phase; must fork drop_objection
Recommended sub-phasesreset, configure, main, shutdown — pre/post variants rarely needed
Best practiceUse run_phase only unless you specifically need ordered multi-phase stimulus
Scroll to Top