Part 2 · Phases & Lifecycle · Intermediate
set_drain_time Deep Dive: Grace Period After Objections Clear
How drain time works internally, set_drain_time API, per-component vs global drain, tuning guidelines, and interaction with phase_ready_to_end.
Why drain time exists
When the last objection drops and count reaches zero, some activity may still be in flight — a monitor may not have sampled the last transaction yet, a scoreboard may not have compared it, or a pipeline may not have flushed. If the phase ended instantly, this trailing activity would be lost.
[PHASE][RUN] the trailing activity problem
T=900ns sequence sends last transaction
T=900ns test drops objection (count=0)
T=905ns monitor samples last transaction ← would be MISSED without drain
T=910ns scoreboard compares last pair ← would be MISSED without drain
drain_time = grace period after count=0 for trailing activity to completeDrain time starts when count reaches zero, not when last drop is called.
During drain time, the phase is still alive — monitors/scoreboards still run.
After drain time expires, phase_ready_to_end fires, then phase ends.
set_drain_time API
// Set drain time on a specific component's objection contribution
task run_phase(uvm_phase phase);
// This component's drain time: phase waits this long after THIS component's
// last drop before considering phase_done
phase.phase_done.set_drain_time(this, 100ns);
phase.raise_objection(this, "main seq");
main_seq.start(env.v_sqr);
phase.drop_objection(this, "main seq done");
// Phase ends 100ns AFTER count reaches 0 (if no other objectors)
endtask
// Set drain time globally for the phase (in start_of_simulation or build)
function void start_of_simulation_phase(uvm_phase phase);
uvm_domain domain = uvm_domain::get_common_domain();
uvm_phase run_ph = domain.find(uvm_run_phase::get());
run_ph.get_objection().set_drain_time(500ns); // global 500ns drain
endfunction[PHASE][UVM] drain time timeline
count=2 → drop → count=1 → drop → count=0
│
▼ drain_time starts (e.g., 100ns)
│ monitors still sampling
│ scoreboards still comparing
▼ drain_time expires
phase_ready_to_end callback
▼
phase ENDset_drain_time(this, time) sets per-component drain contribution.
Global set_drain_time on the phase objection sets a floor for all.
The effective drain is the maximum of all contributors' drain times.
Tuning drain time
Too little drain time
Last transactions missed by monitor — not sampled before phase kill.
Scoreboard pending queue non-empty at check_phase.
Coverage holes for end-of-test scenarios.
Too much drain time
Every test pays the cost at every phase end — cumulative regression slowdown.
100ns drain × 12 runtime sub-phases × 1000 tests = significant wasted sim time.
Masks real objection bugs — sim 'works' because drain covers the gap.
// Tuning approach: start with protocol-appropriate minimum
// AXI: max outstanding burst completion time
// APB: 2-3 clock cycles
// Custom: measure monitor-to-scoreboard latency in waves
function void start_of_simulation_phase(uvm_phase phase);
uvm_phase run_ph;
run_ph = uvm_domain::get_common_domain().find(uvm_run_phase::get());
// Protocol-specific: 3 clock periods at fastest interface clock
run_ph.get_objection().set_drain_time(3 * cfg.fastest_clk_period);
endfunction[PHASE][RUN] drain time sizing guide
APB/simple bus: 2-5 clock cycles
AXI/ACE: max burst latency + pipeline depth
Multi-clock SoC: slowest relevant clock domain period × N
Scoreboard queue: measure max compare latency, add margin
verify: check_phase pending count = 0 with chosen drain timeMeasure actual monitor-to-scoreboard latency on a known-good test.
Set drain to minimum that clears pending queue in check_phase.
Different runtime sub-phases may need different drain times.
Drain time and runtime sub-phases
Each runtime sub-phase has its own objection and drain time. A short drain on reset_phase is fine; main_phase may need more:
class my_test extends uvm_test;
function void start_of_simulation_phase(uvm_phase phase);
uvm_domain dom = uvm_domain::get_common_domain();
// Short drain for structural phases
dom.find_by_name("reset_phase").get_objection().set_drain_time(50ns);
dom.find_by_name("configure_phase").get_objection().set_drain_time(50ns);
// Longer drain for traffic phases
dom.find_by_name("main_phase").get_objection().set_drain_time(200ns);
dom.find_by_name("shutdown_phase").get_objection().set_drain_time(500ns);
endfunction
endclassKey takeaways
Drain time provides a grace period after count=0 for trailing activity.
set_drain_time(this, t) per component; global set on phase objection for floor.
Tune to minimum that clears pending work — excessive drain wastes regression time.
Each runtime sub-phase can have independent drain time settings.
Common pitfalls
Using drain time to mask a missing objection drop — sim 'passes' but hides bug.
Same large drain on all 12 sub-phases — cumulative waste.
Zero drain on main_phase with pipelined scoreboard — last transactions lost.
Setting drain in run_phase instead of start_of_simulation — too late for some phases.