Skip to content

UVM

UVM Phasing Overview

The complete UVM phase schedule — construction, run, and cleanup — zero-time function phases vs the time-consuming run task, and top-down vs bottom-up traversal.

UVM Phasing · Module 5 · Page 5.1

The Engineering Problem

You've relied on phasing in every previous module — build before connect, run after both, report at the end. Now phasing itself becomes the subject. The question is: how does the framework orchestrate a whole tree of independently-written components so they build, connect, and run in a coordinated order? The answer is the phase schedule — a fixed sequence of phases that UVM runs every component through, in lockstep.

The schedule has structure worth knowing precisely, because using the wrong phase is a common, confusing bug. There are zero-time construction phases (build, connect, end_of_elaboration, start_of_simulation), the time-consuming run phase (where simulation time actually passes), and zero-time cleanup phases (extract, check, report, final). The construction and cleanup phases are functions — they cannot consume time; the run phase is a task — it's the only one that can. And the framework traverses the tree differently per phase: build is top-down (a parent must build before its children), most others bottom-up. Get the phase kind wrong (time-consuming work in a function phase) or the phase choice wrong (driving stimulus in build), and it won't compile or won't work. This overview maps the whole schedule so every later activity lands in the right phase.

What is the complete UVM phase schedule, which phases are zero-time functions versus the time-consuming run task, how does the framework traverse the tree per phase, and why does the schedule orchestrate the whole environment correctly?

Motivation — why a fixed phase schedule

A single, fixed schedule that every component obeys is what makes independently-written components compose into a working environment:

  • It coordinates independent components. A driver, a monitor, and a scoreboard are written separately, yet they must build, connect, and run in a coordinated order. The phase schedule is the shared clock: the framework runs all components through build_phase, then all through connect_phase, then all through run_phase — so every component is built before any is connected, and connected before any runs.
  • It enforces the construct-then-connect-then-run dependency. A connection needs both endpoints to exist; a run needs the structure complete and wired. The phase order encodes these dependencies, so you never have to manage them by hand — you just put work in the right phase.
  • The phase kind tells you what's allowed. Function phases are zero-time, so they're for setup that mustn't consume time (creating, wiring, configuring, reporting). The run phase is a task, so it's for behaviour that takes time (driving, monitoring). Matching the activity to the phase kind is the difference between code that works and code that won't compile.
  • Traversal direction matters for correctness. Build is top-down because a parent creates its children; cleanup is bottom-up because children must finish before parents summarise. Knowing the direction explains why, for example, a parent can't see a grandchild's ports during its own build.

The motivation, in one line: phasing is the framework's orchestration mechanism — a fixed, lockstep schedule with zero-time function phases and one time-consuming run phase, traversed top-down or bottom-up — and knowing it is what lets you place every activity where it belongs.

Mental Model

Hold the phase schedule as a three-act day with a strict agenda:

A UVM simulation runs like a tightly-scheduled day in three acts, and every component attends every item on the agenda together. Act 1 — Setup (the morning, no clock running yet): everyone builds their part of the structure (top-down: managers set up before their reports), then connects to each other, finalises elaboration, and gets ready to start — all in zero simulation time, because nothing has happened yet, things are just being arranged. Act 2 — The work (the day itself, the clock runs): everyone runs — this is the only act where time passes, where stimulus is driven and the DUT operates. Act 3 — Wrap-up (the evening, clock stopped again): everyone extracts their results, checks them, reports, and does any final cleanup — zero time again, just gathering and summarising. The agenda is fixed and the same for all attendees; you don't get to run during setup or build during the work.

So when you write a phase method, ask two questions: is this setup, work, or wrap-up? (which act → which phase) and does it consume time? (if yes, it can only be the run phase — a task; if no, it's a function phase). Putting an activity in the wrong act, or trying to consume time in a zero-time phase, is the core mistake the schedule guards against.

Visual Explanation — the phase schedule

The complete schedule is nine common phases in three groups: construction, run, and cleanup. Each group has a defining property — zero-time functions, the one time-consuming task, zero-time functions again.

The nine common UVM phases in three groups: construction function phases, the run task phase, and cleanup function phasesConstruction → Run → CleanupConstruction → Run → Cleanup1build_phase (top-down)construct the component tree, parent before child. Zero-timefunction.2connect → eoe → sos (bottom-up)connect_phase, end_of_elaboration_phase, start_of_simulation_phase:wire and finalise. Zero-time functions.3run_phase (consumes time)the only time-consuming phase — a task. Stimulus runs; optionallythe 12 fine-grained run-time phases.4extract → check → report → final (bottom-up)gather results, check, report PASS/FAIL, final cleanup. Zero-timefunctions.
Figure 1 — the UVM phase schedule (the nine common phases). Construction (zero-time functions): build_phase (top-down), then connect_phase, end_of_elaboration_phase, start_of_simulation_phase (bottom-up). Run (the one time-consuming task): run_phase, optionally subdivided into 12 fine-grained run-time phases (reset, configure, main, shutdown, with pre_/post_). Cleanup (zero-time functions, bottom-up): extract_phase, check_phase, report_phase, final_phase. Only the run phase consumes simulation time.

Read the schedule as three groups with one watershed. The construction group (build, connect, end_of_elaboration, start_of_simulation) brings the environment into existence and wires it — all in zero simulation time, because these are functions. The run group is the single watershed: run_phase is a task, the only phase where simulation time advances, where the DUT operates and stimulus is driven (and where, if you need finer control, the 12 run-time sub-phases — reset, configure, main, shutdown, each with pre_/post_ — run in parallel with run_phase). The cleanup group (extract, check, report, final) gathers and reports results, zero-time again. Nine phases, three groups, one of which consumes time — that's the whole schedule, and every activity you write belongs to exactly one phase in it.

RTL / Simulation Perspective — function phases vs the run task

The phase kind is visible in the method signature: construction and cleanup phases are function void (zero-time); the run phase is a task (consumes time). A component implements the phases it needs.

phase methods — function phases (zero-time) vs the run phase (task)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class my_comp extends uvm_component;
  `uvm_component_utils(my_comp)
  function new(string n, uvm_component p); super.new(n, p); endfunction
 
  // CONSTRUCTION — zero-time FUNCTIONS:
  function void build_phase(uvm_phase phase);          // top-down: create children
    super.build_phase(phase);
  endfunction
  function void connect_phase(uvm_phase phase);        // bottom-up: wire ports
  endfunction
 
  // RUN — the one time-consuming TASK:
  task run_phase(uvm_phase phase);                     // only phase that can consume time
    phase.raise_objection(this);
    // ... drive/monitor over many clock cycles ...
    phase.drop_objection(this);
  endtask
 
  // CLEANUP — zero-time FUNCTIONS:
  function void check_phase(uvm_phase phase);          // bottom-up: check results
  endfunction
  function void report_phase(uvm_phase phase);         // bottom-up: report PASS/FAIL
  endfunction
endclass

The signatures encode the rule. build_phase, connect_phase, check_phase, and report_phase are function void — they cannot consume simulation time (you cannot @(posedge clk) or #10 inside a SystemVerilog function), which is exactly right, because construction and reporting are instantaneous arranging and summarising. run_phase is a task — it can block and consume time, which is required for driving stimulus over cycles. This is why the run phase is where all time-consuming behaviour lives and the function phases are for setup and teardown: the language enforces it through the function/task distinction. Note also that you call super.build_phase (and override the do_ work) and that you only implement the phases a given component needs — a pure data-collecting subscriber might implement only write and report_phase, a driver mainly build_phase and run_phase.

Verification Perspective — top-down vs bottom-up traversal

The framework runs all components through each phase before moving to the next, but the order within a phase differs: build is top-down (parent before child), while connect, the cleanup phases, and others are bottom-up (child before parent). The direction is dictated by what each phase needs.

build_phase traverses top-down (parent to child); connect and cleanup phases traverse bottom-up (child to parent)build: parentfirstthen childrencleanup: children firstcleanup:children…then parentenvbuild #1agentbuild #2driverbuild #3driverreport #1agentreport #2envreport #312
Figure 2 — phase traversal direction. build_phase runs top-down: the parent builds before its children, because the parent creates the children (it must run first). connect_phase and the cleanup phases (extract/check/report) run bottom-up: children before parents, because a parent often aggregates or depends on its children's results. The framework completes a phase across the whole tree (in the phase's direction) before starting the next phase.

The two directions follow from dependency. Build is top-down because a parent creates its children — the env's build_phase must run to create the agent before the agent's build_phase can run to create its driver, so construction necessarily flows from the top down (this is why a parent can't see a grandchild's ports during its own build — they don't exist yet, the lesson behind Module 4.7's null-handle bug). Cleanup is bottom-up because parents often aggregate their children: a child driver reports its local results first, then the agent, then the env summarises — children before parents. The framework completes each phase across the entire tree (in that phase's direction) before advancing to the next phase, which is what keeps every component synchronised: all builds finish before any connect begins. Direction within a phase, lockstep across phases.

Runtime / Execution Flow — the run phase is the only one that consumes time

The single most important property of the schedule is the watershed at the run phase: everything before and after it is zero-time, and only the run phase advances simulation time. This is what makes "construction" and "cleanup" instantaneous and the run phase the home of all behaviour.

Construction phases at time zero, run phase consumes time, cleanup phases at zero timeZero-time construction → time-consuming run → zero-time cleanupZero-time construction → time-consuming run → zero-time cleanup1Construction — at time 0build, connect, end_of_elaboration, start_of_simulation all executewith zero simulation time elapsed.2run_phase — time advancesthe only phase that consumes time: stimulus driven, DUT runs,monitors observe over many cycles.3run ends (objections dropped)when no objection remains, the run phase completes and time stopsadvancing.4Cleanup — at the final timeextract, check, report, final execute with no further time advance— gather and report.
Figure 3 — the time watershed. The construction phases (build through start_of_simulation) all execute at time zero — the tree is built, wired, and prepared with no simulation time elapsing. The run phase then advances time: stimulus is driven, the DUT runs, monitors observe, across many cycles. When the run phase ends (objections dropped), the cleanup phases (extract through final) again execute at the final time with no further advance. Only the run phase consumes time; the function phases bracket it at zero time.

This watershed is why the function/task distinction is not a technicality but the backbone of the schedule. The construction phases prepare — and preparation is instantaneous by nature, so they're functions and the language forbids them from consuming time. The run phase is where the simulation happens — so it's a task, and it's where every clock edge, every driven transaction, every observation occurs. The cleanup phases summarise — instantaneous again, functions again. Because of this, the entire structural and reporting machinery of a UVM environment happens "for free" at time zero and the final time, and all of simulation time is the run phase. It also explains the end-of-test mechanism (Module 2.9): objections gate the run phase specifically, because that's the only phase with a duration to control. Time lives entirely in the run phase; everything else brackets it.

Waveform Perspective — the schedule on a timeline

The schedule's shape is visible on a timeline: the construction phases collapse to time zero, the run phase spans the simulation, and the cleanup phases collapse to the end — the function/task watershed made concrete.

The phase schedule in time — construction at t=0, run spans time, cleanup at the end

12 cycles
The phase schedule in time — construction at t=0, run spans time, cleanup at the endConstruction (build/connect/…) — zero time: the tree is built and wired (B/C)Construction (build/co…run_phase — the only phase that consumes time: stimulus runsrun_phase — the only p…Cleanup (extract/check/report/final) — zero time: results gathered and reportedCleanup (extract/check…clkphaseB/CB/CRUNRUNRUNRUNRUNRUNRUNE/CE/CRPTvaliddata0000A0A100B0B10000000000t0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — the construction phases (build/connect/…, shown as B/C) execute at time zero with no pin activity; the run phase then spans the simulation, where stimulus is driven (valid/data) over many cycles; the cleanup phases (extract/check/report, shown as E/C/R) execute at the final time. The phase track shows the three groups; only the run phase has a duration. This is the schedule's defining shape: zero-time functions bracketing the one time-consuming run phase.

The phase track is the schedule drawn on time. The construction region (B/C) carries no pin activity because it's zero-time elaboration — the whole tree is built and wired in an instant. The RUN region is where the pins move, because the run phase is the only one that advances time; every transaction on the waveform happens here. The cleanup region (E/C, RPT) is again zero-time, gathering and reporting results at the final simulation time. This shape — a thin construction sliver at the start, a wide run span in the middle, a thin cleanup sliver at the end — is the signature of every UVM simulation, and it's a direct consequence of the function/task split: only the run phase has width on the time axis. Reading this is reading the schedule.

DebugLab — time-consuming work in a function phase

A reset wait that wouldn't compile — placed in build_phase, a zero-time function

Symptom

An engineer wanted the environment to wait for the DUT's reset to deassert before doing some setup, and wrote wait(vif.rst_n == 1); inside build_phase. It would not compile — the simulator rejected the time-consuming statement — and attempts to "fix" it by changing the wait to a #100 delay failed the same way. The setup logic simply could not wait for anything where it was written.

Root cause

Time-consuming work in a function phase. build_phase is a function void — it cannot consume simulation time, so any blocking or delay statement (wait, @(posedge clk), #delay, or calling a time-consuming task) is illegal inside it:

why a function phase can't wait
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
build_phase signature:  function void build_phase(uvm_phase phase);   ← a FUNCTION
function rule (SV):      a function cannot consume time (no @, #, wait, or task calls that block)
what was written:       wait(vif.rst_n == 1);   inside build_phase    ← time-consuming → illegal
result:                 compile error: cannot have time-controlling statement in a function
correct phase:          run_phase (a TASK) — the only phase that can wait/consume time:
                        task run_phase(uvm_phase phase); ... wait(vif.rst_n==1); ...

Construction phases are zero-time functions by design — they arrange the structure, which is instantaneous. Anything that must wait or take time belongs in the run_phase (or a run-time sub-phase), which is a task. The reset wait wasn't wrong; it was in a phase whose kind forbids waiting.

Diagnosis

The tell is a time-controlling statement that won't compile — a function/task phase-kind mismatch. Diagnose by checking the phase's kind against the activity:

  1. Is the activity time-consuming? Anything with @, #, wait, fork...join that blocks, or a call to a blocking task consumes time and can only live in a task phase — the run_phase or a run-time sub-phase. A compile error about a time statement in a function points straight at this.
  2. Which phase is it in? build_phase, connect_phase, end_of_elaboration_phase, start_of_simulation_phase, extract_phase, check_phase, report_phase, final_phase are all function void — zero-time. Only run_phase (and the run-time sub-phases) are tasks. Time-consuming code in any function phase is the bug.
  3. Distinguish setup from behaviour. Setup (create, wire, configure) is instantaneous → function phase. Behaviour (wait, drive, observe) takes time → run phase. The reset wait is behaviour, not setup.
Prevention

Match the activity's timing to the phase's kind:

  1. Time-consuming work goes in run_phase. Waiting for reset, driving stimulus, sampling over cycles — anything that consumes time — belongs in the run_phase (a task) or a run-time sub-phase, never in a function phase.
  2. Function phases are zero-time setup/teardown only. Use build/connect to create and wire, extract/check/report to summarise — all instantaneous. If you're tempted to wait in one, the work belongs in run_phase.
  3. Recognise the kind from the signature. function void *_phase cannot consume time; task run_phase can. The signature tells you what's allowed before you write a line.

The one-sentence lesson: construction and cleanup phases are zero-time functions and cannot wait or consume time — anything that must take time (waiting for reset, driving stimulus) belongs in the run_phase, the only task phase.

Common Mistakes

  • Time-consuming work in a function phase. build, connect, check, report, etc. are zero-time functions and cannot wait, delay, or drive over cycles. Put anything time-consuming in run_phase (a task).
  • Driving stimulus or waiting in build_phase. Build is for constructing the tree; behaviour belongs in the run phase. Trying to drive or wait in build is the function/task mismatch.
  • Expecting build to be bottom-up (or run/report to be top-down). Build is top-down (parent before child); connect and the cleanup phases are bottom-up. Assuming the wrong direction leads to accessing children that don't exist yet, or summarising before children have reported.
  • Forgetting super in a phase method. Omitting super.build_phase(phase) (and the like) can skip base-class phase work. Call super in overridden phase methods.
  • Confusing the run phase with the run-time sub-phases. run_phase and the 12 fine-grained run-time phases (reset/configure/main/shutdown with pre_/post_) run in parallel over the same time; use the sub-phases for finer ordering, but don't expect them to run before or after run_phase.
  • Objecting in a non-run phase. Objections gate the run-time phases (which have a duration); raising an objection in a zero-time function phase has no duration to control. End-of-test objections belong in run_phase.

Senior Design Review Notes

Interview Insights

The nine common phases run in this order: build_phase, connect_phase, end_of_elaboration_phase, start_of_simulation_phase, run_phase, extract_phase, check_phase, report_phase, and final_phase. They fall into three groups. The construction group — build, connect, end_of_elaboration, start_of_simulation — builds and wires the component tree and prepares for simulation; these are zero-time functions. The run group is just run_phase, the single time-consuming phase where stimulus is driven and the DUT operates (and which can be subdivided into 12 fine-grained run-time phases — reset, configure, main, shutdown, each with pre_ and post_ variants — that run in parallel with run_phase for finer ordering). The cleanup group — extract, check, report, final — gathers results, checks them, reports PASS/FAIL, and does final cleanup; these are zero-time functions again. The framework runs every component through each phase in lockstep, completing a phase across the whole tree before starting the next, with build traversed top-down and connect and the cleanup phases traversed bottom-up.

Exercises

  1. Order and group. List the nine common phases in order, group them into construction/run/cleanup, and mark which are functions and which is the task.
  2. Right phase. For each, name the correct phase: (a) create the agents; (b) connect the monitor to the scoreboard; (c) wait for reset and drive stimulus; (d) compare expected vs actual totals; (e) print the final PASS/FAIL.
  3. Fix the kind. A component has @(posedge vif.clk); inside connect_phase and won't compile. State the root cause and the phase it belongs in, citing the function/task distinction.
  4. Direction. Explain why build_phase is top-down and report_phase is bottom-up, using the create-children and aggregate-children dependencies.

Summary

  • UVM phasing is a fixed schedule the framework runs every component through in lockstep — nine common phases in three groups: construction (build, connect, end_of_elaboration, start_of_simulation), the run phase, and cleanup (extract, check, report, final).
  • Function phases vs the run task: construction and cleanup are zero-time function void methods (they cannot consume time); run_phase is a task and the only phase that consumes simulation time. The signature tells you what's allowed.
  • Traversal direction differs by phase: build is top-down (a parent creates its children), while connect and the cleanup phases are bottom-up (parents aggregate children). The framework completes each phase across the whole tree before advancing.
  • Time lives only in the run phase: construction collapses to time zero and cleanup to the final time, so every simulation has the shape of a thin setup sliver, a wide run span, and a thin cleanup sliver — and objections gate the run phase because it's the one phase with a duration. The 12 run-time sub-phases subdivide the run in parallel for finer ordering.
  • The durable rule of thumb: put setup in the zero-time function phases (build/connect to construct and wire, extract/check/report to summarise) and all time-consuming behaviour in the run_phase task — match the activity to the phase's kind and group, and the schedule orchestrates the whole environment.

Next — build_phase: with the whole schedule in view, the next chapters drill into each phase. First and most important is build_phase — where the component tree is constructed top-down, configuration is read, and the factory creates every component.