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.

diagram
[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

systemverilog
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
diagram
[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 END
  • Legitimate 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

  1. Scoreboard pending queue drain — compare straggler transactions.

  2. Coverage sample final flush — ensure last bins are hit.

  3. Protocol checker final state verification — no outstanding bursts.

  4. Log flush — ensure all buffered messages are written.

systemverilog
// 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
endclass
  • Use 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:

systemverilog
// BAD: always re-raises
function void phase_ready_to_end(uvm_phase phase);
  phase.raise_objection(this, "always extend");
  // never drops → sim hangs
endfunction

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

diagram
[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 occur

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