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.
[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 endScoreboard 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.
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
endclassGuard 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.
[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.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
endfunctionWhen 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.
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 designKey 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.