Part 2 · Phases & Lifecycle · Intermediate
phase_ready_to_end: Last-Chance Phase Extension
The phase_ready_to_end callback, how to re-raise objections to extend a phase, legitimate use cases, and abuse patterns to avoid.
The callback that fires before phase end
After objections drain to zero and drain time expires, UVM fires phase_ready_to_end on components before actually ending the phase. This is the last chance to raise a new objection and extend the phase.
[PHASE][UVM] phase_ready_to_end in lifecycle
count=0 → drain_time wait → phase_ready_to_end (all components)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
re-raise? no re-raise re-raise?
(extend) (proceed) (extend)
│ │ │
└───────────────┼───────────────┘
▼
count still 0?
│
YES → phase END
NO → back to running (wait for new drops)phase_ready_to_end fires after drain time, before phase kill.
Components can raise a new objection here to extend the phase.
If no re-raise occurs, the phase ends immediately after the callback round.
Implementing phase_ready_to_end
class pending_check_scoreboard extends uvm_scoreboard;
pending_q = {};
function void phase_ready_to_end(uvm_phase phase);
// Last-chance check: do we still have pending comparisons?
if (pending_q.size() > 0) begin
`uvm_info("SCB", $sformatf("re-raising: %0d pending at phase end",
pending_q.size()), UVM_MEDIUM)
phase.raise_objection(this, "draining pending comparisons");
// Process remaining items
while (pending_q.size() > 0)
compare_one(pending_q.pop_front());
phase.drop_objection(this, "pending drained");
end
endfunction
endclass[PHASE][RUN] re-raise cycle
count=0, drain done
phase_ready_to_end: scoreboard sees pending_q.size()=3
→ raise_objection (count=1)
→ process 3 items
→ drop_objection (count=0)
phase_ready_to_end again (no re-raise this time)
phase ENDLegitimate use: drain pending work discovered at phase boundary.
Must drop the re-raised objection in the same callback — or sim hangs.
UVM may call phase_ready_to_end multiple times if re-raises occur.
Legitimate use cases
Scoreboard pending queue drain — compare straggler transactions.
Coverage sample final flush — ensure last bins are hit.
Protocol checker final state verification — no outstanding bursts.
Log flush — ensure all buffered messages are written.
// Coverage collector: sample final state
class cov_collector extends uvm_subscriber #(txn_t);
function void phase_ready_to_end(uvm_phase phase);
if (uncovered_critical_bins()) begin
phase.raise_objection(this, "final coverage sample");
sample_final_state();
phase.drop_objection(this, "final coverage done");
end
endfunction
endclass
// Protocol checker: verify no outstanding transactions
class proto_checker extends uvm_component;
int outstanding;
function void phase_ready_to_end(uvm_phase phase);
if (outstanding > 0) begin
`uvm_warning("CHK", $sformatf("%0d outstanding at phase end", outstanding))
phase.raise_objection(this, "wait outstanding");
wait (outstanding == 0);
phase.drop_objection(this, "outstanding cleared");
end
endfunction
endclassUse for deterministic cleanup, not as a substitute for proper drain time.
Always pair re-raise with drop in the same callback execution.
Log re-raises at UVM_MEDIUM — they indicate boundary timing issues.
Abuse patterns to avoid
Infinite re-raise loop
If phase_ready_to_end always re-raises (e.g., forever loop condition never false), the phase never ends:
// BAD: always re-raises
function void phase_ready_to_end(uvm_phase phase);
phase.raise_objection(this, "always extend");
// never drops → sim hangs
endfunctionUsing re-raise instead of proper drain time
Re-raise in phase_ready_to_end should handle exceptional stragglers, not routine trailing activity. Routine trailing activity belongs in drain time configuration.
[PHASE][UVM] phase_ready_to_end decision
USE when: unexpected pending work discovered at boundary
DON'T USE: routine trailing activity (use drain_time instead)
DON'T USE: keeping sim alive because you forgot to raise earlier
DON'T USE: infinite wait for condition that may never occurKey takeaways
phase_ready_to_end fires after drain time — last chance to extend the phase.
Re-raise + process + drop in the same callback for straggler cleanup.
Legitimate for pending queue drain, final coverage, outstanding protocol checks.
Never use as substitute for proper drain time or missing earlier objections.
Common pitfalls
Re-raise without drop in phase_ready_to_end — instant hang.
Infinite re-raise loop — condition never false, phase never ends.
Using phase_ready_to_end for routine work — indicates drain time too short.
Multiple components re-raising simultaneously — race on who drops last.