UVM Phases in Order: build, connect, run and Objections (With Real Logs)

In short: UVM runs every component through the same phases, in a fixed order: build, connect, end_of_elaboration, start_of_simulation, run, extract, check, report, final. build_phase (and final_phase) go top-down, parent before child, because a parent creates its children. All other function phases go bottom-up. run_phase is the only common phase that takes time, and it runs in all components at once, alongside 12 optional run-time sub-phases. It ends when no objections are raised, so a test that never raises one ends at time 0.

UVM phase order: build and final top-down, other function phases bottom-up, run_phase in parallel with the 12 run-time sub-phases from pre_reset to post_shutdown
Figure 1. UVM phase order, with all 12 run-time sub-phases.

Every UVM testbench does the same thing in the same order.

Once you see that order, “where does this code go?” answers itself.

Every log on this page is real: Synopsys VCS X-2025.06-SP1 with UVM 1.2.

Think of it like putting on a theatre show

  • build: the director hires stage managers, and each stage manager hires their own crew. Top-down: you can’t hire a crew before their boss exists.
  • connect: the crew run cables and headsets between stations. Everyone already exists, so order doesn’t matter much.
  • end_of_elaboration / start_of_simulation: the final walk-through, then “places, everyone”.
  • run: the show. Everyone works at the same time: lights, sound, actors.
  • objections: every actor with a scene still to play keeps a hand up. The curtain falls only when every hand is down.
  • drain time: the applause after the last line, before the lights come up.
  • extract, check, report: count the tickets, check the props, write the review.
  • final: lock up the theatre.

The phases at a glance

PhaseTakes time?OrderPut hereTheatre picture
build_phaseNo (function)Top-downtype_id::create, config_db::getHire the crew
connect_phaseNoBottom-upPort and export connectionsRun the cables
end_of_elaboration_phaseNoBottom-upPrint the topology, last tweaksFinal walk-through
start_of_simulation_phaseNoBottom-upBanners, set verbosity, catchers“Places, everyone”
run_phaseYes (task)All in parallelDrive, monitor, start sequencesThe show
extract_phaseNoBottom-upPull final counts from scoreboardsCount the tickets
check_phaseNoBottom-upEnd-of-test checks (queues empty?)Check the props
report_phaseNoBottom-upPrint pass/fail and summariesWrite the review
final_phaseNoTop-downClose filesLock up

run_phase also has 12 run-time sub-phases that run alongside it: pre_reset, reset, post_reset, pre_configure, configure, post_configure, pre_main, main, post_main, pre_shutdown, shutdown, post_shutdown. They get their own full section below, with what each one is for, the rules they follow, 4 real runs, and when (and when not) to use them.

Example 1: watch the order happen

One component class prints its full name in every phase. The test builds env, and env builds env.drv.

// Every component prints its full name in every phase
class noisy extends uvm_component;
  `uvm_component_utils(noisy)
  noisy kid;
  bit   make_kid;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void say(string ph); $display("%4t  %-14s %s", $time, ph, get_full_name()); endfunction

  function void build_phase(uvm_phase phase);
    say("build");
    if (make_kid) kid = noisy::type_id::create("drv", this);   // child created here
  endfunction
  function void connect_phase(uvm_phase phase);             say("connect");          endfunction
  function void end_of_elaboration_phase(uvm_phase phase);  say("end_of_elab");      endfunction
  function void start_of_simulation_phase(uvm_phase phase); say("start_of_sim");     endfunction
  task          run_phase(uvm_phase phase);                 say("run");              endtask
  task          main_phase(uvm_phase phase);                say("main");             endtask
  function void extract_phase(uvm_phase phase);             say("extract");          endfunction
  function void check_phase(uvm_phase phase);               say("check");            endfunction
  function void report_phase(uvm_phase phase);              say("report");           endfunction
  function void final_phase(uvm_phase phase);               say("final");            endfunction
endclass

The test’s run_phase raises an objection, waits 10 ns, and drops it.

Run 1: ./simv +UVM_TESTNAME=order_test

UVM_INFO @ 0: reporter [RNTST] Running test order_test...
   0  build          uvm_test_top.env
   0  build          uvm_test_top.env.drv
   0  connect        uvm_test_top.env.drv
   0  connect        uvm_test_top.env
   0  end_of_elab    uvm_test_top.env.drv
   0  end_of_elab    uvm_test_top.env
   0  start_of_sim   uvm_test_top.env.drv
   0  start_of_sim   uvm_test_top.env
   0  run            uvm_test_top.env.drv
   0  run            uvm_test_top.env
   0  main           uvm_test_top.env.drv
   0  main           uvm_test_top.env
  10  test drops its objection
UVM_INFO .../uvm_objection.svh(1276) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
  10  extract        uvm_test_top.env.drv
  10  extract        uvm_test_top.env
  10  check          uvm_test_top.env.drv
  10  check          uvm_test_top.env
  10  report         uvm_test_top.env.drv
  10  report         uvm_test_top.env
  10  final          uvm_test_top.env
  10  final          uvm_test_top.env.drv

What the log shows (UVM library paths shortened to ...):

  • build: env first, then env.drv. Top-down: drv doesn’t exist until env‘s build creates it.
  • connect, end_of_elab, start_of_sim: env.drv first, then env. Bottom-up: children finish first.
  • run and main: both start at time 0, in both components. They run side by side, not one after the other.
  • At 10 ns the test drops its objection, UVM prints [TEST_DONE] 'run' phase is ready to proceed, and extract, check and report all happen at 10 ns, bottom-up again.
  • final: env first, then env.drv. Top-down, the only other phase that walks that way.
Run 1 order on VCS: build and final top-down (env before env.drv), connect to report bottom-up (env.drv before env), run and main start together at 0 ns
Figure 2. Run 1 on VCS: who printed first in each phase.

Objections: how run_phase knows when to stop

run_phase has no natural end. A driver loops forever; a monitor watches forever.

So UVM counts raised hands. Each raise_objection puts a hand up; each drop_objection puts one down. When the count reaches zero, run_phase ends for every component, and UVM moves on to extract.

Example 2: forget the objection

// Test 2: forget the objection
class no_objection_test extends uvm_test;
  `uvm_component_utils(no_objection_test)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    $display("%4t  run_phase started, sending 3 packets", $time);
    repeat (3) #10 $display("%4t  packet sent", $time);
  endtask
  function void report_phase(uvm_phase phase);
    $display("%4t  report_phase", $time);
  endfunction
endclass

Run 2: ./simv +UVM_TESTNAME=no_objection_test

UVM_INFO @ 0: reporter [RNTST] Running test no_objection_test...
   0  run_phase started, sending 3 packets
   0  report_phase

run_phase started at time 0, then not one packet, and report_phase at time 0. The whole test lasted 0 ns. Nobody raised a hand, so the curtain fell the moment the show started. No error, no warning: the report summary shows a clean run. That’s what makes this mistake so easy to miss.

Example 3: drain time

// Test 3: drain time keeps the phase open after the last drop
class drain_test extends uvm_test;
  `uvm_component_utils(drain_test)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    uvm_objection obj = phase.get_objection();
    obj.set_drain_time(this, 25);                            // stay open 25 ns after the last drop
    phase.raise_objection(this);
    repeat (3) #10 $display("%4t  packet sent", $time);
    phase.drop_objection(this);
    $display("%4t  objection dropped", $time);
  endtask
  function void report_phase(uvm_phase phase);
    $display("%4t  report_phase", $time);
  endfunction
endclass

Run 3: ./simv +UVM_TESTNAME=drain_test

UVM_INFO @ 0: reporter [RNTST] Running test drain_test...
  10  packet sent
  20  packet sent
  30  packet sent
  30  objection dropped
UVM_INFO .../uvm_objection.svh(1276) @ 55: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
  55  report_phase

Packets at 10, 20 and 30, the drop at 30, and report_phase at 55, not 30: exactly 25 ns of drain. Drain time is the applause: it gives the last response time to come back through the monitor and reach the scoreboard.

Timeline: run 2 with no objection ends at 0 ns; run 3 holds one objection to 30 ns, drains 25 ns and ends at 55 ns
Figure 3. Objections on a timeline: runs 2 and 3.

Run 4, see every hand go up and down: +UVM_OBJECTION_TRACE

UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top raised 1 objection(s): count=1  total=1
UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object uvm_test_top): count=0  total=1
  10  packet sent
  20  packet sent
  30  packet sent
UVM_INFO @ 30: run [OBJTN_TRC] Object uvm_test_top dropped 1 objection(s): count=0  total=0
  30  objection dropped
UVM_INFO @ 55: run [OBJTN_TRC] Object uvm_test_top all_dropped 1 objection(s): count=0  total=0
UVM_INFO @ 55: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top): count=0  total=0
UVM_INFO @ 55: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object uvm_test_top): count=0  total=0
UVM_INFO .../uvm_objection.svh(1276) @ 55: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
  55  report_phase

How to read it:

  • At 0, uvm_test_top raises one objection, and it passes up to uvm_top: the total there goes to 1.
  • At 30, the test drops its objection. Its own count is 0, but **uvm_top doesn’t let go yet**: that’s the drain clock running.
  • At 55, all_dropped fires and uvm_top‘s total reaches 0. Only then does run end.

When a test hangs or ends too early, this switch is the first thing to try. Look for the component whose raise has no matching drop (a hang), or a run with no raise at all (time 0).

The 12 run-time sub-phases

run_phase is one long task from start to finish. The UVM library also gives you a second, finer schedule that runs at the same time: 12 task phases in four groups of three.

  • pre_reset starts at the same moment as run_phase.
  • post_shutdown ends together with run_phase.
  • In between they go strictly in order, one after another.

Think of it like the stage manager’s running order

In the theatre picture, run_phase is the stage manager’s headset: on from the moment the house opens until the last person leaves.

The sub-phases are the running order on the stage manager’s clipboard:

GroupSub-phaseWhat UVM says it’s forDone whenTheatre picture
Resetpre_resetBefore reset is asserted: wait for power-good, set clocks and outputs to safe valuesReady to assert resetHouse lights dim
ResetresetReset asserted: drive idle values, start clocks, then de-assert resetReset released, clock stableEveryone to opening marks
Resetpost_resetJust after reset: idle traffic, link trainingDUT in a known, active stateCurtain up
Configurepre_configureDecide the DUT configurationConfiguration definedRead the set list
ConfigureconfigureProgram the DUT: register writes, memory loadsDUT configuredDress the stage for act 1
Configurepost_configureWait for it to settle, enable the DUT, sample config coverageDUT fully enabledLighting check
Mainpre_mainWait for training or rate negotiationEveryone ready for traffic“Standby”
MainmainThe primary stimulus: your data sequencesTest goal reachedThe performance
Mainpost_mainIncluded for symmetry; rarely usedn/aFinal bow
Shutdownpre_shutdownIncluded for symmetry; rarely usedn/aCurtain closes
ShutdownshutdownLet data drain out of the DUTInterfaces idleAudience leaves
Shutdownpost_shutdownLast run-time checks that need the DUT: read counters, dump memoriesReady to endStage manager’s final checks

The “what it’s for” and “done when” columns are paraphrased from the UVM 1.2 class reference for the run-time phases.

Five rules the sub-phases follow

  1. Each sub-phase has its own objections. It ends when its own hands go down. A sub-phase where nobody raises a hand ends at once.
  2. Everyone moves together. No component starts configure until every component in the same domain has finished reset. The fastest waits for the slowest. (Example 4.)
  3. Threads die at the end of their sub-phase. Anything you fork inside configure_phase is killed when configure ends. (Example 5.)
  4. You can jump back. phase.jump(uvm_reset_phase::get()) sends everyone back to reset: reset-on-the-fly testing. (Example 6.)
  5. Domains break the lockstep. A component in its own uvm_domain follows its own running order through the 12 sub-phases. (Example 7.)

In the theatre: no department moves to the next cue until every department calls “ready” (rule 2). A crew member hired “for this scene only” leaves when the scene ends (rule 3). “Stop the show, back to opening positions!” (rule 4). The orchestra pit following its own conductor (rule 5).

The demo for examples 4 to 7

// A component that spends a set time in reset, configure, main and shutdown
class worker extends uvm_component;
  `uvm_component_utils(worker)
  int rst_d = 10, cfg_d = 10, main_d = 10, shut_d = 10;   // ns spent in each sub-phase
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void say(string ph, string what); $display("%4t  %-4s %-10s %s", $time, get_name(), ph, what); endfunction

  task busy(uvm_phase phase, string ph, int d);
    phase.raise_objection(this);
    say(ph, "start");
    #d;
    say(ph, "end");
    phase.drop_objection(this);
  endtask

  task run_phase(uvm_phase phase);       say("run", "start (runs alongside all sub-phases)"); endtask
  task reset_phase(uvm_phase phase);     busy(phase, "reset",     rst_d);  endtask
  task configure_phase(uvm_phase phase); busy(phase, "configure", cfg_d);  endtask
  task main_phase(uvm_phase phase);      busy(phase, "main",      main_d); endtask
  task shutdown_phase(uvm_phase phase);  busy(phase, "shutdown",  shut_d); endtask
  function void extract_phase(uvm_phase phase); say("extract", ""); endfunction
endclass

Example 4: the fastest waits for the slowest

sync_test builds two workers. drv spends 20 ns in reset; mon spends 5 ns.

Run 5: ./simv +UVM_TESTNAME=sync_test

UVM_INFO @ 0: reporter [RNTST] Running test sync_test...
   0  drv  run        start (runs alongside all sub-phases)
   0  mon  run        start (runs alongside all sub-phases)
   0  drv  reset      start
   0  mon  reset      start
   5  mon  reset      end
  20  drv  reset      end
  20  drv  configure  start
  20  mon  configure  start
  30  drv  configure  end
  30  mon  configure  end
  30  drv  main       start
  30  mon  main       start
  40  drv  main       end
  40  mon  main       end
  40  drv  shutdown   start
  40  mon  shutdown   start
  50  drv  shutdown   end
  50  mon  shutdown   end
  50  drv  extract
  50  mon  extract

mon finished reset at 5 ns, then sat idle for 15 ns. Its configure started at 20 ns, the same moment as drv‘s, because drv was still in reset. From then on both moved in lockstep: configure 20 to 30, main 30 to 40, shutdown 40 to 50, extract at 50. Meanwhile run_phase started at 0 for both and ran underneath the whole time. In the theatre: no department moves to the next cue until every department calls “ready”.

Gantt chart: mon finishes reset at 5 ns but waits idle until drv finishes reset at 20 ns; then both run configure, main and shutdown together until 50 ns
Figure 4. The fastest waits for the slowest (VCS).

Example 5: a thread that dies with its sub-phase

// Test 2: a background thread started in configure_phase
class kill_test extends uvm_test;
  `uvm_component_utils(kill_test)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task configure_phase(uvm_phase phase);
    phase.raise_objection(this);
    fork
      forever #5 $display("%4t  heartbeat (started in configure_phase)", $time);
    join_none
    #12 phase.drop_objection(this);
  endtask
  task main_phase(uvm_phase phase);
    phase.raise_objection(this);
    $display("%4t  main_phase start", $time);
    #20 $display("%4t  main_phase end", $time);
    phase.drop_objection(this);
  endtask
endclass

Run 6: ./simv +UVM_TESTNAME=kill_test

UVM_INFO @ 0: reporter [RNTST] Running test kill_test...
   5  heartbeat (started in configure_phase)
  10  heartbeat (started in configure_phase)
  12  main_phase start
  32  main_phase end

Heartbeats at 5 and 10 ns. configure ended at 12 ns when the test dropped its objection, and the heartbeat never printed again: not at 15, 20, 25 or 30, all through main_phase (12 to 32 ns), even though the loop says forever. No error, no warning. UVM silently killed everything configure_phase started. That’s why a background sequence (a responder, a traffic generator that should run all test long) belongs in run_phase.

Timeline: heartbeats at 5 and 10 ns during configure_phase; configure ends at 12 ns and the heartbeat never prints again during main_phase from 12 to 32 ns
Figure 5. A thread started in configure dies with it (VCS).

Example 6: reset in the middle of the test

// Test 3: jump back to reset once, as if the DUT was reset mid-test
class jump_test extends uvm_test;
  `uvm_component_utils(jump_test)
  worker dut_agent;
  bit    jumped;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    dut_agent = worker::type_id::create("agt", this);
  endfunction
  task main_phase(uvm_phase phase);
    phase.raise_objection(this);
    #5;
    if (!jumped) begin
      jumped = 1;
      $display("%4t  test: reset on the fly, jumping back to reset_phase", $time);
      phase.jump(uvm_reset_phase::get());
    end
    phase.drop_objection(this);
  endtask
endclass

Run 7: ./simv +UVM_TESTNAME=jump_test

UVM_INFO @ 0: reporter [RNTST] Running test jump_test...
   0  agt  run        start (runs alongside all sub-phases)
   0  agt  reset      start
  10  agt  reset      end
  10  agt  configure  start
  20  agt  configure  end
  20  agt  main       start
  25  test: reset on the fly, jumping back to reset_phase
UVM_INFO .../uvm_phase.svh(1558) @ 25: reporter [PH_JUMP] phase main (schedule uvm_sched, domain uvm) is jumping to phase reset
UVM_WARNING @ 25: main_objection [OBJTN_CLEAR] Object 'uvm_top' cleared objection counts for main_objection
  25  agt  reset      start
  35  agt  reset      end
  35  agt  configure  start
  45  agt  configure  end
  45  agt  main       start
  55  agt  main       end
  55  agt  shutdown   start
  65  agt  shutdown   end
  65  agt  extract
  • The first main started at 20 ns. At 25 the test jumped, and agt‘s first main never printed “end”: it was cut off mid-way.
  • UVM announces the jump ([PH_JUMP] phase main ... is jumping to phase reset) and warns that it cleared the main_phase objections ([OBJTN_CLEAR]). That warning is expected on every jump; it’s UVM telling you the hands that were up no longer count.
  • Then reset (25 to 35), configure (35 to 45) and main (45 to 55) ran a second time, followed by shutdown and extract at 65.
  • In the theatre: “Stop the show, back to opening positions!”

This is the one job sub-phases do better than anything else: checking that the DUT recovers from a reset in the middle of traffic. More in reset testing using phase jumps.

Timeline: reset, configure and main start; at 25 ns the test jumps back to reset; reset, configure and main run again, then shutdown ends at 65 ns
Figure 6. Reset on the fly with phase.jump() (VCS).

Example 7: a component on its own clock (domains)

// Test 4: same as test 1, but mon lives in its own domain
class domain_test extends sync_test;
  `uvm_component_utils(domain_test)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    uvm_domain mon_domain;
    super.build_phase(phase);
    mon_domain = new("mon_domain");
    mon.set_domain(mon_domain);         // mon no longer waits for drv
  endfunction
endclass

Run 8: ./simv +UVM_TESTNAME=domain_test

UVM_INFO @ 0: reporter [RNTST] Running test domain_test...
   0  drv  run        start (runs alongside all sub-phases)
   0  mon  run        start (runs alongside all sub-phases)
   0  mon  reset      start
   0  drv  reset      start
   5  mon  reset      end
   5  mon  configure  start
  15  mon  configure  end
  15  mon  main       start
  20  drv  reset      end
  20  drv  configure  start
  25  mon  main       end
  25  mon  shutdown   start
  30  drv  configure  end
  30  drv  main       start
  35  mon  shutdown   end
  40  drv  main       end
  40  drv  shutdown   start
  50  drv  shutdown   end
  50  drv  extract
  50  mon  extract
  • Same delays as example 4, but now mon started configure at 5 ns, right after its own reset, and raced ahead: main at 15, shutdown at 25, done at 35 ns, while drv was only halfway.
  • drv kept its own pace: reset to 20, configure to 30, main to 40, shutdown to 50.
  • extract still happened at 50 ns for both. Domains only split the 12 sub-phases; the common phases (build, connect, extract, report and so on) stay shared, so everyone waits for the slowest domain before the test ends.
  • In the theatre: the orchestra pit following its own conductor, but everyone still takes the final bow together.

Real use: two parts of a chip that reset or power up independently, such as a block on its own power island.

Gantt chart: with mon in its own domain it runs reset, configure, main and shutdown from 0 to 35 ns without waiting; drv runs 0 to 50 ns; extract for both at 50 ns
Figure 7. mon in its own uvm_domain (VCS).

So when should you use sub-phases?

The experts on Verification Academy are almost unanimous: default to run_phase with sequences, and reach for sub-phases only with a clear reason.

Why they warn against them:

  • No shared meaning. The standard doesn’t pin down what belongs in each sub-phase, so two VIPs from two teams may use configure for different things, and mixing them gets hard to debug (cgales, 2016; Dave Rich, 2019).
  • They force themselves on you. A VIP that uses sub-phases makes every testbench it lands in use them too (chr_sue, in the same 2016 thread).
  • The use model was never settled. The Mentor/Siemens UVM Cookbook has advised avoiding them since early UVM 1.x (mperyer, 2013).
  • Threads die at phase ends (example 5), which surprises people with background sequences (2019).

When they earn their place:

SituationUse sub-phases?Why
Reusable VIP or agent you’ll shareNoStick to run_phase so it fits any testbench
Drivers, monitors, scoreboardsNoThey must see every transaction from start to end
Reset in the middle of a testYesphase.jump() back to reset is built for this (example 6)
Blocks that reset or power up independentlyYes, with domainsEach domain keeps its own running order (example 7)
One self-contained testbench, team agrees on the meaning of each phaseMaybeClear stages in the test; keep them in the test and env only
You just want reset, then config, then trafficNoThree sequences in a row in run_phase do the same job

The pattern most teams use instead:

task run_phase(uvm_phase phase);
  phase.raise_objection(this);
  reset_seq.start(env.agt.sqr);       // what reset_phase would do
  cfg_seq.start(env.agt.sqr);         // what configure_phase would do
  main_seq.start(env.agt.sqr);        // what main_phase would do
  phase.drop_objection(this);
endtask

Same order, no surprises, and drivers and monitors stay in run_phase the whole time.

Go deeper

Each part of phasing has its own page on this site:

Common mistakes (and the fix for each)

These come from real Verification Academy threads, retold in my own words with links.

1. A test that ends at time 0

  • What happened: in 2018 an engineer started a sequence in run_phase and the simulation ended at time 0 (Verification Academy, 2018).
  • Why: no objection raised, so run_phase ended immediately. Example 2 shows exactly this.
  • Fix: phase.raise_objection(this); before seq.start(...), phase.drop_objection(this); after.

2. Raising objections everywhere

  • What happened: in 2018 someone asked why people don’t raise objections in every phase and every class (Verification Academy, 2018), and in another thread whether every sequence needs its own (Verification Academy, 2018).
  • Why: objections only matter in phases that take time. Build and connect are functions; they can’t wait. And a driver that raises inside its forever loop can hang the test, because the drop is never reached while it waits for the next item.
  • Fix: raise and drop in the test’s run_phase, around the top-level sequence. The test knows when the test is done.

3. Expecting a “construction phase”

  • What happened: in 2019 an engineer asked when constructors run compared to build_phase (Verification Academy, 2019).
  • Why: there’s no construction phase. Each child is constructed inside its parent’s build_phase, with type_id::create. That’s why build has to go top-down.
  • Fix: create children in build_phase, never in the constructor.

4. Relying on sibling build order

  • What happened: in 2014 someone asked who calls build_phase and in what order (Verification Academy, 2014).
  • Why: parents build before children, but the order between siblings (two agents in one env) isn’t promised.
  • Fix: never let one sibling’s build depend on another’s. Share settings through config_db from the parent.

5. Thinking final_phase is bottom-up

  • What happened: in 2014 an engineer asked whether final_phase is top-down or bottom-up (Verification Academy, 2014).
  • Why: build_phase and final_phase are top-down; every other function phase is bottom-up. Example 1’s log shows both.
  • Fix: put end-of-test reporting in report_phase; keep final_phase for closing files.

6. Starting a background sequence in a sub-phase

  • What happened: in 2019 an engineer asked whether always-on background sequences should start in configure_phase instead of run_phase (Verification Academy, 2019).
  • Why: anything started in a sub-phase is killed when that sub-phase ends (example 5). cgales called this the main reason sub-phases aren’t recommended.
  • Fix: start background sequences in run_phase, forked, so they live as long as the test.

7. Mixing main_phase in one component with run_phase in another

  • What happened: in 2017 an engineer needed a monitor’s run_phase to wait for a driver’s main_phase (Verification Academy, 2017); another asked whether main_phase can replace run_phase (Verification Academy).
  • Why: main_phase is not a synonym for run_phase. It starts only after reset and configure finish, and ends before shutdown. As soon as anyone adds time to an earlier sub-phase, everything keyed to main_phase shifts.
  • Fix: pick one style per testbench. If you must, chr_sue’s advice: use sub-phases in all components, or in none.

FAQ

What is the order of UVM phases?

build, connect, end_of_elaboration, start_of_simulation, run, extract, check, report, final.

Which UVM phases are top-down?

build_phase and final_phase. The other function phases are bottom-up.

Which UVM phase consumes simulation time?

Only run_phase and its 12 run-time sub-phases. Everything else is a function.

Why does my UVM test end at time 0?

Nothing raised an objection in run_phase. See example 2.

What is drain time?

Extra time run_phase stays open after the last objection drops, so late responses can still be checked. See example 3.

What are the UVM run-time sub-phases?

Twelve task phases that run alongside run_phase, in order: pre_reset, reset, post_reset, pre_configure, configure, post_configure, pre_main, main, post_main, pre_shutdown, shutdown, post_shutdown.

Do I need an objection in every sub-phase?

Yes, in every sub-phase where you want time to pass. Each one ends when its own objections drop; with none raised it ends at once.

Should I use main_phase or run_phase?

run_phase, unless you have a specific reason such as reset-on-the-fly testing. Most experts recommend run_phase with reset, configure and main sequences.

What does phase.jump() do?

It sends the phase schedule to another phase, typically back to reset_phase, killing the threads of the current phase and clearing its objections. See example 6.

References

  • IEEE 1800.2-2020, UVM standard: the phasing and common-phases clauses, and uvm_objection.
  • UVM 1.2 class reference: run-time phases (uvm_runtime_phases.svh).
  • Verification Academy forum threads linked in the mistakes and sub-phase sections (accessed 11 Nov 2026).
  • All 8 runs by Hardik on Synopsys VCS X-2025.06-SP1 with UVM 1.2, 11 Nov 2026.