Part 2 · Phases & Lifecycle · Intermediate
Simulation End Trigger: What Happens When Objections Drain
The phase_done mechanism, transition from run_phase to extract_phase, drain_time preview, and what components should expect at phase boundary.
When count hits zero, the phase ends
UVM monitors the root objection count for run_phase. When the last drop brings the count to zero, the phase scheduler triggers phase_done — a callback chain that eventually ends run_phase for all components and advances to extract_phase.
[PHASE][RUN][UVM] end-of-run_phase sequence
last drop → count = 0
│
▼
drain_time wait (if configured)
│
▼
phase_done.trigger() — all run_phase threads receive kill/end signal
│
▼
extract_phase (function, zero time)
│
▼
check_phase → report_phase → final_phase → $finishrun_phase ending does not immediately call $finish — four cleanup phases follow.
Forever loops in driver/monitor terminate when run_phase ends.
Drain time (covered in objections deep dive) adds delay after count=0.
What happens to forever loops
Driver and monitor run_phases typically contain forever loops. When the phase ends, UVM kills these tasks. Any cleanup that must happen before exit should be in shutdown logic or the post-run function phases.
class my_driver extends uvm_driver #(req_t);
task run_phase(uvm_phase phase);
req_t req;
forever begin
seq_item_port.get_next_item(req);
drive(req);
seq_item_port.item_done();
end
// THIS LINE IS NEVER REACHED — phase kill stops the forever loop
endtask
endclass
// Scoreboard may use a similar pattern
task run_phase(uvm_phase phase);
forever begin
wait (exp_q.size() > 0 && act_q.size() > 0);
compare(exp_q.pop_front(), act_q.pop_front());
end
endtask[RUN] component behavior at phase boundary
DRIVER/MONITOR forever loop killed — no graceful exit unless you design one
SCOREBOARD compare loop killed — pending queue items may be unprocessed
TEST run_phase task completes normally after drop
SEQUENCES body() completes or is killed if phase ends mid-sequenceDesign shutdown sequences or use runtime shutdown sub-phases for graceful drain.
Pending scoreboard queue items at phase end may indicate truncated checking.
extract/check phases are the right place for end-of-test invariant checks.
The cleanup phase chain
After run_phase ends, four function phases close the simulation in strict order:
extract_phase — gather statistics, flush queues, snapshot coverage.
check_phase — evaluate pass/fail invariants (not per-transaction compare).
report_phase — print summary, error counts, coverage report.
final_phase — last-chance teardown, close files, final hooks.
[PHASE][UVM] post-run timeline (zero simulation time)
run_phase END
│
extract_phase ──► scoreboard.report_stats(), cov.get_coverage()
│
check_phase ──► if (pending_q.size()) uvm_error(...)
│
report_phase ──► uvm_report_info summary, print coverage
│
final_phase ──► close log files, final hooks
│
simulation endsclass my_scoreboard extends uvm_scoreboard;
int pending;
function void check_phase(uvm_phase phase);
if (pending != 0)
`uvm_error("SCB", $sformatf("%0d unmatched transactions at end", pending))
endfunction
function void report_phase(uvm_phase phase);
`uvm_info("SCB", $sformatf("compared=%0d mismatches=%0d", cmp_cnt, err_cnt), UVM_LOW)
endfunction
endclasscheck_phase catches 'we ended with pending work' — a common silent failure.
report_phase is for human-readable summaries, not uvm_error.
All four cleanup phases are function phases — no time passes, no objections.
Premature end vs stuck simulation
Two symmetric failure modes bracket the end trigger:
Premature end (count never raised)
[PHASE] premature end signature
sim time ≈ 0ns
test 'passes' instantly
no stimulus in waveform
cause: nobody called raise_objectionStuck simulation (count never reaches zero)
[PHASE] stuck simulation signature
sim runs until global timeout
stimulus may have completed
+UVM_OBJECTION_TRACE shows count > 0
cause: raise without matching dropKey takeaways
Simulation ends when run_phase objections drain to zero (plus drain_time).
Forever loops in driver/monitor are killed at phase end — plan cleanup accordingly.
extract → check → report → final close the test after run_phase.
Premature end = no raise; stuck sim = no drop.
Common pitfalls
Assuming scoreboard will compare all transactions after objection drop — loops are killed.
Using check_phase for per-transaction compare — that belongs in run_phase/scoreboard.
Setting global timeout as a substitute for proper objection management.
Ignoring pending queue checks in check_phase — silent data loss at phase boundary.