Part 10 · Advanced Topics · Intermediate

phase_ready_to_end for Conditional Drain

How to delay phase completion until a condition is met, including a scoreboard-drain implementation pattern and race-safe coding guidelines.

Fixed delay vs conditional completion

Drain time is blind: it waits a fixed duration regardless of whether work has finished. phase_ready_to_end is conditional: it inspects current state and decides whether to hold the phase open.

This is ideal for components like scoreboards where completion is state-dependent (queue empty, map empty, all channels idle). It avoids overpaying fixed drain delay in easy tests while still preventing premature end in corner cases.

diagram
[UVM] phase_ready_to_end concept

  root count reached zero
      │
      ▼
  phase_ready_to_end callbacks execute on components
      │
      ├─ condition satisfied  -> do nothing
      └─ condition not met    -> raise temporary objection
                               spawn wait-for-condition
                               drop when condition true
      ▼
  phase can end

Scoreboard drain example (queue + ID map)

A robust scoreboard may need to wait until both FIFO and ID-map stores are empty. The pattern below raises a temporary objection only when pending expected entries exist.

systemverilog
class bus_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(bus_scoreboard)

  bus_txn exp_q[$];
  bus_txn exp_by_id [bit [7:0]];
  bit waiting_for_drain = 0;

  function bit is_drained();
    return (exp_q.size() == 0) && (exp_by_id.num() == 0);
  endfunction

  function void phase_ready_to_end(uvm_phase phase);
    if (phase.get_name() != "run")
      return;

    if (is_drained())
      return;

    // Prevent repeated fork storms if callback fires multiple times.
    if (waiting_for_drain)
      return;

    waiting_for_drain = 1;
    phase.raise_objection(this, "scoreboard waiting to drain");
    fork
      begin
        wait (is_drained());
        waiting_for_drain = 0;
        phase.drop_objection(this, "scoreboard drained");
      end
    join_none
  endfunction
endclass
  • Guard flag avoids spawning multiple wait threads from repeated callbacks.

  • Condition function keeps logic readable and testable.

  • Only raise when necessary; no-op when already drained.


Race conditions and safe patterns

The tricky edge case is a condition that oscillates while callback logic runs. For example, queue empties, then another expected arrives from late predictor traffic. Design condition checks and ownership so temporary objections reflect true pending work.

diagram
[CHECK] race-aware design points

  1. Condition must reflect all pending stores (FIFO + maps + channel FIFOs).
  2. Guard repeated callback entry with a flag.
  3. Ensure drop path always clears flag.
  4. Log when temporary objection is raised/dropped for debug visibility.
  5. Pair with timeout policy if condition can stall forever.
systemverilog
function void phase_ready_to_end(uvm_phase phase);
  if (phase.get_name() != "run") return;
  if (is_drained() || waiting_for_drain) return;

  `uvm_info("SCB_OBJ", "phase_ready_to_end hold", UVM_LOW)
  waiting_for_drain = 1;
  phase.raise_objection(this, "hold for pending compares");

  fork
    begin
      wait (is_drained());
      `uvm_info("SCB_OBJ", "drain complete", UVM_LOW)
      waiting_for_drain = 0;
      phase.drop_objection(this, "pending compares complete");
    end
  join_none
endfunction

When to use phase_ready_to_end

  • Use for state-based completion (queue empty, flush acknowledged, all channels idle).

  • Prefer over very large fixed drain when only rare tests need extra wait.

  • Keep logic local to component that knows drain condition semantics.

  • Still maintain check_phase assertions for leftover entries as final safety net.

diagram
Fixed drain_time                   phase_ready_to_end
  ─────────────────────────────      ─────────────────────────────────
  + Simple to configure              + Ends as soon as condition true
  + Predictable overhead             + Handles variable tail lengths
  - Pays cost every test             - More code / race handling
  - Can still be too short           - Needs careful ownership design

Key takeaways

  • phase_ready_to_end is conditional phase extension based on real component state.

  • It is ideal for scoreboard/predictor drain where tail length varies by test.

  • Use guard flags and clear logging to avoid callback re-entry bugs.

  • Combine with check_phase leftovers for strong end-of-test integrity.

Common pitfalls

  • Raising temporary objection without guaranteed drop path.

  • Condition checking only one pending store while others remain non-empty.

  • Spawning repeated wait threads on every callback invocation.