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.

diagram
[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  $finish
  • run_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.

systemverilog
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
diagram
[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-sequence
  • Design 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:

  1. extract_phase — gather statistics, flush queues, snapshot coverage.

  2. check_phase — evaluate pass/fail invariants (not per-transaction compare).

  3. report_phase — print summary, error counts, coverage report.

  4. final_phase — last-chance teardown, close files, final hooks.

diagram
[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 ends
systemverilog
class 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
endclass
  • check_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)

diagram
[PHASE] premature end signature

  sim time ≈ 0ns
  test 'passes' instantly
  no stimulus in waveform
  cause: nobody called raise_objection

Stuck simulation (count never reaches zero)

diagram
[PHASE] stuck simulation signature

  sim runs until global timeout
  stimulus may have completed
  +UVM_OBJECTION_TRACE shows count > 0
  cause: raise without matching drop

Key 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.