UVM Phases Explained: Build, Connect, Run and Objections

When you first open a UVM testbench, the hardest question is usually not “what does each class do?” but “who runs when?”. The answer is the phasing mechanism. UVM phases are a fixed, ordered set of callbacks that the simulator invokes on every component in your hierarchy, so that construction, wiring, and stimulus happen in a predictable sequence instead of a tangle of race conditions. Get the phases right and your environment comes up cleanly every time; get them wrong and you will chase null-handle crashes and ports that connect to nothing.

A note before the code: the UVM examples below are written for a UVM-capable simulator (industry-standard simulation tools, VCS, or industry-standard simulators) or for EDA Playground with a UVM target. They show canonical UVM structure and are meant to be read, not compiled on a free simulator, because the open tools (Icarus, Verilator) do not ship the full UVM base-class library. The one runnable block at the end uses plain SystemVerilog only, so you can execute it anywhere.

What a phase really is

A phase is just a method that UVM calls on your component for you. You never call build_phase yourself; you override it, and the UVM root schedules it. Phases come in two flavours. Function phases execute in zero simulation time and return immediately (their signature is function void). Task phases consume simulation time and can block on clocks and events (their signature is task). This single distinction explains most of the rules that follow: you may only wait for time inside a task phase.

The build-time phases

Before any clock ticks, UVM walks your component tree through a series of function phases. The three you will touch most often are build_phase, connect_phase, and end_of_elaboration_phase.

  • build_phase runs top-down: the test builds first, then the env, then the agents, then drivers and monitors. This order matters because a parent must exist and configure the factory before its children are constructed.
  • connect_phase runs bottom-up: leaf components are fully built, so you can safely wire their TLM ports and exports upward.
  • end_of_elaboration_phase also runs bottom-up and is the last chance to inspect or adjust the fully assembled hierarchy before time starts.
class my_env extends uvm_env;
  `uvm_component_utils(my_env)

  my_agent       agent;
  my_scoreboard  sb;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  // Construct children here. Runs top-down.
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agent = my_agent::type_id::create("agent", this);
    sb    = my_scoreboard::type_id::create("sb", this);
  endfunction

  // Wire ports/exports here. Runs bottom-up, so children already exist.
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    agent.mon.item_ap.connect(sb.item_imp);
  endfunction
endclass

Notice the discipline: anything built with the factory goes in build_phase, and every connect call waits until connect_phase. If you tried to connect inside build_phase, the child monitor’s analysis port might not exist yet, and you would dereference a null handle.

Why super.build_phase matters

You will see super.build_phase(phase) at the top of almost every build override, and it is not a cosmetic habit. For build_phase specifically, the base uvm_component implementation does real work: it applies automatic configuration, pulling values that a parent set through the configuration database into your registered fields. Skip the super call and that auto-configuration silently does not happen, which is a classic “my config_db set is being ignored” bug.

For the other phases the base implementations are mostly empty today, but calling super is still recommended. It keeps your code correct if a future UVM version adds behaviour, and it keeps the pattern uniform so a reviewer can trust every phase the same way. The rule of thumb for Indian interview prep: always call super in build_phase; call it in the others by convention.

The run phase and its sub-phases

Once elaboration finishes, UVM enters the one big task phase, run_phase, where simulation time actually advances and stimulus is driven. Running in parallel with run_phase is a finer-grained schedule of twelve run-time sub-phases. The four that students must know are the reset, configure, main, and shutdown phases.

Sub-phaseTypeTypical use
reset_phasetaskAssert reset, drive signals to known idle values
configure_phasetaskProgram registers / DUT configuration after reset
main_phasetaskRun the core traffic and functional stimulus
shutdown_phasetaskLet the DUT drain, flush pipelines, settle

These sub-phases run in order (reset, then configure, then main, then shutdown), and they let different components synchronise. A driver can hold reset in reset_phase while a register model waits for configure_phase, without either hard-coding delays about the other. For simple testbenches, many engineers just use run_phase and skip the sub-phases entirely; both styles are legal, but do not mix a run_phase sequence that fights a main_phase sequence on the same interface.

class reset_then_traffic_test extends uvm_test;
  `uvm_component_utils(reset_then_traffic_test)
  my_env env;
  // new() and build_phase omitted for brevity

  task reset_phase(uvm_phase phase);
    phase.raise_objection(this);
    reset_seq::type_id::create("rst").start(env.agent.seqr);
    phase.drop_objection(this);
  endtask

  task main_phase(uvm_phase phase);
    phase.raise_objection(this);
    traffic_seq::type_id::create("traf").start(env.agent.seqr);
    phase.drop_objection(this);
  endtask
endclass

Objections: how a phase knows it is done

A task phase cannot guess when your stimulus has finished, so UVM uses an objection mechanism. Think of it as a reference count. When a component raise_objection, it tells UVM “do not end this phase, I am still busy.” When it calls drop_objection, the count decrements. The moment the total objection count for a phase reaches zero, UVM ends that phase and advances to the next one.

This is why a sequence body is almost always bracketed by a raise and a drop. Forget the raise, and the run_phase ends at time zero before your first transaction is driven. Forget the drop, and the simulation hangs until the phase timeout fires. The pattern is simple and should become muscle memory:

task run_phase(uvm_phase phase);
  my_sequence seq;
  phase.raise_objection(this, "starting stimulus");
  seq = my_sequence::type_id::create("seq");
  seq.start(env.agent.seqr);
  phase.drop_objection(this, "stimulus done");
endtask

A useful habit is to pass a description string, as shown, and to set a drain time with phase.phase_done.set_drain_time when you need a few extra clocks after the last objection drops so the scoreboard can finish checking.

Phase ordering, end to end

Putting it together, the overall order is: build (top-down), connect (bottom-up), end_of_elaboration, start_of_simulation, then the run phase with its reset/configure/main/shutdown sub-phases running under it, and finally the clean-up function phases such as extract, check, and report (bottom-up). Every component finishes a given phase before any component starts the next one. That synchronisation barrier is the heart of why phasing works.

You do not need UVM installed to feel this ordering. The plain-SystemVerilog model below imitates a tiny component tree and calls each phase across every component before moving on, printing the exact order. Run it on any simulator.

module tb;

  // A tiny stand-in for a uvm_component, with phase methods.
  class component;
    string name;
    function new(string nm);
      name = nm;
    endfunction
    virtual function void do_build();
      $display("[build]   %s", name);
    endfunction
    virtual function void do_connect();
      $display("[connect] %s", name);
    endfunction
    virtual task do_run();
      $display("[run]     %s", name);
    endtask
  endclass

  component comps[3];
  integer i;

  initial begin
    comps[0] = new("env");
    comps[1] = new("env.driver");
    comps[2] = new("env.monitor");

    // Each phase runs across ALL components before the next phase starts.
    for (i = 0; i < 3; i = i + 1) comps[i].do_build();
    for (i = 0; i < 3; i = i + 1) comps[i].do_connect();
    for (i = 0; i < 3; i = i + 1) comps[i].do_run();

    $display("All phases complete.");
    $finish;
  end

endmodule

[build]   env
[build]   env.driver
[build]   env.monitor
[connect] env
[connect] env.driver
[connect] env.monitor
[run]     env
[run]     env.driver
[run]     env.monitor
All phases complete.

The output shows all three components passing through build, then all through connect, then all through run. Map that onto real UVM and you understand the contract: construction is complete before wiring, wiring is complete before time advances, and objections decide when each timed phase lets go. Master these four ideas and the rest of UVM stops feeling like magic.

Quick check

Why does build_phase run top-down but connect_phase run bottom-up?

A parent must be constructed and must configure the factory before it creates its children, so building goes top-down. Connecting is the reverse: leaf components must already exist before you can wire their ports upward, so connecting goes bottom-up.

Why is super.build_phase() especially important compared with super in other phases?

The base uvm_component build_phase applies automatic configuration, copying values set through the config database into your registered fields. Omit the super call and that auto-configuration silently does not run. Super in the other phases is mostly convention today but recommended for forward-compatibility.

What happens if you start a sequence in run_phase without raising an objection?

UVM sees a zero objection count, considers the phase already done, and ends run_phase at time zero, so your stimulus never gets driven. You must raise an objection before starting and drop it after.

In what order do the four common run-time sub-phases execute?

reset_phase, then configure_phase, then main_phase, then shutdown_phase, all running under the single run_phase. They let different components synchronise around reset, DUT configuration, main traffic, and drain without hard-coded delays.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD