UVM Phasing

In short: UVM phasing is a fixed schedule that every component follows so the whole testbench starts, runs, and shuts down in the same order. Build phases run top down to create components, connect and end-of-elaboration wire them up, the run phase (plus its twelve sub-phases) drives real simulation time, and the cleanup phases run bottom up to report and check. Objections tell the run phase when it is allowed to finish.

Phasing is one of the first things that confuses people coming to UVM, because nothing in plain SystemVerilog forces every object to start and stop in a coordinated way. UVM adds that coordination through phases. This guide covers every phase, the order they run in, which run top down and which run bottom up, how objections keep the run phase alive, the twelve run-time sub-phases, phase jumping, domains, and a clear rule for where each piece of your code belongs. Worked examples and tables are included throughout.

A simple way to picture it

Think about staging a school play. First the director casts every role and hands out scripts (build). Then the cast rehearses entrances and cues so everyone knows who follows whom (connect). The stage manager does a final walkthrough to confirm the set is ready (end of elaboration). The curtain rises and the actual performance happens in front of the audience (run). When the show ends, the cast takes a bow, the crew strikes the set, and the director writes up notes on how it went (extract, check, report, final). UVM runs your testbench in exactly this order, and it does it automatically for every component you build.

Why phases exist at all

Imagine building a testbench without phasing. One component might try to connect a port before the component it connects to has been created. Another might start driving stimulus before the interface handle is available. Phasing removes that whole class of ordering bugs by guaranteeing that every component finishes creating its children before anyone tries to connect, and that everyone is connected before any stimulus runs. You get a predictable order for free, across a testbench with hundreds of components written by different people.

The two kinds of phase

There are two families of phase, and telling them apart is the single most useful thing to understand.

KindMethod typeConsumes simulation time?Examples
Function phasesfunction voidNo, they finish in zero timebuild_phase, connect_phase, extract_phase, report_phase
Task phasestaskYes, they can wait on clocks and eventsrun_phase and its twelve sub-phases

Because function phases take no time, you cannot put an @(posedge clk) or a #10 inside build_phase or connect_phase. Anything that needs to wait for simulation time belongs in a task phase, almost always run_phase. Mixing this up is the most common phasing error beginners make.

The common phases in order

These are the phases you will use every day, in the exact order UVM runs them.

PhaseTypeDirectionWhat you do here
build_phasefunctiontop downCreate child components with the factory; get config values
connect_phasefunctionbottom upConnect ports, exports, and analysis ports
end_of_elaboration_phasefunctionbottom upFinal adjustments; print the topology; sanity checks
start_of_simulation_phasefunctionbottom upPrint banners; last-minute setup before time starts
run_phasetaskparallelDrive and observe stimulus; this is where real time passes
extract_phasefunctionbottom upCollect final data from components
check_phasefunctionbottom upCheck that everything expected actually happened
report_phasefunctionbottom upPrint pass or fail and summary information
final_phasefunctionbottom upClose files; last cleanup

You will not override all nine in every component. In practice build_phase and connect_phase live in structural components like the environment and agent, run_phase lives in the driver and monitor, and report_phase or check_phase live in the scoreboard.

Top down versus bottom up, and why it matters

Direction is not a detail; it is the reason phasing works. build_phase runs top down: the test builds first, then its environment, then the agents, then the drivers and monitors. This has to be top down, because a parent must exist and know its configuration before it can create its children.

connect_phase runs bottom up: the leaf components are connected first, then their parents. This has to be bottom up, because a parent can only connect ports that already exist on children that were built in the previous phase. Picture a family tree: you build downward from the root, then wire upward from the leaves.

class my_env extends uvm_env;
  `uvm_component_utils(my_env)
  bus_agent agt;
  bus_scoreboard scb;
  function new(string name, uvm_component parent); super.new(name,parent); endfunction

  // TOP DOWN: parent creates children first
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agt = bus_agent     ::type_id::create("agt", this);
    scb = bus_scoreboard::type_id::create("scb", this);
  endfunction

  // BOTTOM UP: children are already built, now wire them
  function void connect_phase(uvm_phase phase);
    agt.mon.ap.connect(scb.imp);
  endfunction
endclass

A component that shows the phase methods

Here is a driver overriding the phases it actually needs. Notice build_phase and connect_phase are function void, while run_phase is a task that can wait on clock edges.

class bus_driver extends uvm_driver #(bus_item);
  `uvm_component_utils(bus_driver)
  virtual bus_if vif;
  function new(string name, uvm_component parent); super.new(name,parent); endfunction

  function void build_phase(uvm_phase phase);          // zero time
    super.build_phase(phase);
    if (!uvm_config_db#(virtual bus_if)::get(this,"","vif",vif))
      `uvm_fatal("NOVIF","no virtual interface")
  endfunction

  task run_phase(uvm_phase phase);                     // consumes time
    forever begin
      bus_item req;
      seq_item_port.get_next_item(req);
      @(posedge vif.clk);            // legal only in a task phase
      vif.valid <= 1;
      seq_item_port.item_done();
    end
  endtask
endclass

The twelve run-time sub-phases

The run_phase runs in parallel with a set of twelve finer sub-phases. Most projects only use a few of them, but knowing the list helps when you need precise ordering, for example resetting the design before configuring it.

OrderSub-phaseTypical use
1pre_resetWait for power-good or clocks to settle
2resetAssert and release the reset signal
3post_resetActions right after reset is gone
4pre_configurePrepare for configuration
5configureProgram registers and modes into the design
6post_configureVerify configuration took effect
7pre_mainSet up before main traffic
8mainThe bulk of the real stimulus
9post_mainWind down main traffic
10pre_shutdownPrepare to stop
11shutdownDrain pipelines and finish outstanding work
12post_shutdownFinal run-time cleanup

A common pattern is to put reset logic in the reset sub-phase and register programming in the configure sub-phase, so every test gets a clean reset then a known configuration before main traffic starts. Here is a reset sequence living in the reset sub-phase:

task reset_phase(uvm_phase phase);
  phase.raise_objection(this);
  vif.rst_n <= 0;
  repeat (5) @(posedge vif.clk);
  vif.rst_n <= 1;
  phase.drop_objection(this);
endtask

Objections: telling a phase when to end

A task phase like run_phase needs to know when everyone is done. Objections are the mechanism. Any component can raise an objection to say I still have work, and the phase stays alive as long as at least one objection is up. When the last objection drops, the phase ends. This is how UVM knows the difference between a test that is still running and one that has finished.

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

Two failure modes are worth memorizing. If you never raise an objection, the run phase ends immediately and your stimulus never runs. If you raise one and forget to drop it, the simulation hangs until it times out. A useful habit is to set a drain time so trailing activity has a moment to settle after the last objection drops:

function void end_of_elaboration_phase(uvm_phase phase);
  // give the design 100 ns to drain after the last drop
  phase.get_objection().set_drain_time(this, 100ns);
endfunction

Phase jumping

Sometimes you want to abort the normal flow, for example to inject a reset in the middle of a test and restart from the reset sub-phase. Phase jumping lets a component ask UVM to jump back to an earlier phase. Use it sparingly, because it makes the flow harder to follow, but it is the right tool for mid-test reset scenarios.

// force the run-time schedule back to the reset sub-phase
task main_phase(uvm_phase phase);
  phase.raise_objection(this);
  if (need_mid_test_reset) begin
    phase.jump(uvm_reset_phase::get()); // jump on the current phase, target as argument
  end
  phase.drop_objection(this);
endtask

Domains: running components on different clocks

A domain is a group of components that share the same run-time schedule. By default everything lives in one domain called uvm, but a multi-clock chip may need one part of the testbench to reset and configure independently from another. Putting components in separate domains lets their run-time sub-phases advance on their own timelines instead of marching in lockstep.

// place an agent in its own clock domain
uvm_domain my_domain = new("clkB_domain");
agt_b.set_domain(my_domain);

Most block-level testbenches never need custom domains. Reach for them only when two interfaces genuinely have independent reset and configuration timelines, such as two IP blocks on different clocks joined at the chip level.

Where should my code go?

This table is the quick answer to the question every beginner asks: which phase does my code belong in?

Your goalPhase to useWhy
Create a child componentbuild_phaseRuns top down before connecting
Read a config valuebuild_phaseNeeded before children are built
Connect ports or analysis exportsconnect_phaseChildren already exist to connect
Print the component treeend_of_elaboration_phaseTopology is final by now
Drive or sample pins over timerun_phaseOnly task phases consume time
Reset the designreset sub-phaseGuarantees reset before configure
Program registersconfigure sub-phaseRuns after reset, before main
Report pass or failreport_phaseRuns after all data is collected

Common mistakes to avoid

  • Putting a delay or clock wait inside a function phase like build_phase. Function phases take zero time, so this is illegal; move time-consuming code to run_phase.
  • Forgetting super.build_phase(phase). Skipping the super call can leave automatic configuration and field setup undone.
  • Connecting ports in build_phase instead of connect_phase. The child ports may not exist yet, giving a null handle error.
  • Never raising an objection, so run_phase ends before your sequence starts.
  • Raising an objection and never dropping it, so the simulation hangs until timeout.
  • Reaching for phase jumping or custom domains before you actually need them, which makes the flow hard to follow.

Keep learning

Phasing makes more sense once you see it inside a full testbench. Read the UVM testbench architecture guide to see where each phase lives, then browse more articles on the UVM category page and the language details on the SystemVerilog page. If you are preparing for a role, our interview questions cover phasing in detail.

Frequently asked questions

Phases matter most when you drive scenarios through them. See how reset testing uses a phase jump to rerun part of the run, and why this phase model is one of the advantages of UVM over plain SystemVerilog.

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

A parent must be built and configured before it can create its children, so building goes from the top of the tree downward. Connecting is the opposite: a parent can only connect ports that already exist on its children, so the children must be connected first, which means connecting goes from the leaves upward.

Can I put an @(posedge clk) inside build_phase?

No. build_phase is a function that runs in zero simulation time, so it cannot wait on a clock edge or a delay. Any code that needs to consume time must go in a task phase, which in almost every case is run_phase or one of its sub-phases.

What happens if I forget to raise an objection?

The run phase will see no active objections and end immediately, so your sequence never gets a chance to run. The simulation finishes in near-zero time and reports no activity, which is a classic sign of a missing objection.

Do I need to override all nine phases in every component?

No. You only override the phases that component actually uses. Structural components usually override build_phase and connect_phase, drivers and monitors override run_phase, and the scoreboard overrides check_phase or report_phase. Everything you do not override simply does nothing, which is fine.

When would I use the run-time sub-phases instead of run_phase?

Use them when you need strict ordering between stages, for example resetting the design in the reset sub-phase before programming registers in the configure sub-phase. If your test does not need that separation, a single run_phase is simpler and perfectly acceptable.

What is a drain time?

A drain time is a grace period after the last objection drops, during which the phase stays alive so trailing activity can settle. It is useful when a few clocks of cleanup are needed after the main stimulus finishes, preventing the phase from ending one cycle too early.

Similar Posts