Part 2 · Phases & Lifecycle · Intermediate
Canonical run_phase Objection Patterns: Test, Sequence, and Background
Production-ready patterns for test-owned objections, sequence self-management, virtual sequence coordination, and multi-source duration control.
Pattern 1: Test-owned top-level objection
The most common pattern — the test raises before starting the main virtual sequence and drops when it completes:
class base_test extends uvm_test;
`uvm_component_utils(base_test)
my_env env;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
my_vseq vseq = my_vseq::type_id::create("vseq");
fork super.run_phase(phase); join_none // spawn children
phase.raise_objection(this, "main virtual sequence");
vseq.start(env.v_sqr);
phase.drop_objection(this, "main virtual sequence done");
endtask
endclass[PHASE][RUN] test-owned pattern
test raises ──► vseq.start() ──► subseqs run on agents ──► test drops
driver/monitor: passive forever loops (no objection)
duration owner: testTest owns duration — clear, single owner for traces.
fork super.run_phase join_none ensures children start before stimulus.
Virtual sequence orchestrates sub-sequences without its own objection.
Pattern 2: Sequence self-managed objections
Long-running or reusable sequences manage their own duration via starting_phase:
class long_running_seq extends uvm_sequence #(item_t);
`uvm_object_utils(long_running_seq)
task pre_body();
if (starting_phase != null)
starting_phase.raise_objection(this, "long_running_seq");
endtask
task body();
repeat (num_iterations) begin
`uvm_do_with(req, { addr inside {[0:255]}; })
end
endtask
task post_body();
if (starting_phase != null)
starting_phase.drop_objection(this, "long_running_seq done");
endtask
endclass
// Test simply starts it — no manual raise/drop needed
task run_phase(uvm_phase phase);
fork super.run_phase(phase); join_none
long_running_seq::type_id::create("lrs").start(env.agent.sqr);
// phase stays alive until sequence post_body drops
endtask[RUN][UVM] sequence-owned pattern
pre_body → raise
body → stimulus
post_body → drop (even on uvm_error in body, post_body still runs)
benefit: reusable sequence carries its own duration contractpre_body/post_body pairing is the safest automatic raise/drop.
Reusable sequences should self-manage when started from multiple tests.
starting_phase is set by the sequencer when sequence starts in a phase context.
Pattern 3: Test + background traffic
When main and background sequences run concurrently, each manages its own objection — the phase ends only when both complete:
task run_phase(uvm_phase phase);
main_seq m_seq;
bg_traffic bg_seq;
fork super.run_phase(phase); join_none
// Main test flow
phase.raise_objection(this, "main flow");
m_seq = main_seq::type_id::create("m_seq");
m_seq.start(env.v_sqr);
phase.drop_objection(this, "main flow done");
// Background: self-managed via pre_body/post_body
bg_seq = bg_traffic::type_id::create("bg_seq");
bg_seq.start(env.bg_sqr);
// bg_traffic raises in pre_body — phase still alive after main drops
endtask[PHASE][RUN] dual-source duration
t=0 test raises (main) count=1
t=0 bg_seq raises count=2
t=500 test drops (main done) count=1 ← phase STILL alive
t=900 bg_seq drops count=0 ← phase ENDSMultiple independent duration sources are normal and supported.
Do not drop the test objection expecting bg traffic to keep sim alive unless bg raised.
Document which sequences self-manage vs test-managed.
Pattern 4: Coordinated stop with event
For controlled shutdown (drain in-flight transactions before drop), use an event or flag:
class coordinated_test extends uvm_test;
uvm_event stop_ev;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
stop_ev = uvm_event_pool::get_global("stop_ev");
endfunction
task run_phase(uvm_phase phase);
fork super.run_phase(phase); join_none
phase.raise_objection(this, "coordinated run");
run_stimulus();
stop_ev.trigger(); // signal agents to stop accepting new items
#(drain_ns); // wait for in-flight to complete
phase.drop_objection(this, "drained");
endtask
endclass
// Driver checks stop event
task run_phase(uvm_phase phase);
forever begin
if (stop_ev.is_on()) break;
seq_item_port.get_next_item(req);
drive(req);
seq_item_port.item_done();
end
endtaskKey takeaways
Test-owned objection is the default — simple and traceable.
Sequence pre_body/post_body is the safest self-managed pattern.
Multiple concurrent raisers are normal; phase ends when all drop.
Use events/flags for coordinated graceful shutdown before drop.
Common pitfalls
Test raises but sequence also raises — double-counting if not intentional.
Starting bg sequence without pre_body raise — sim ends when main test drops.
Coordinated stop without drain delay — in-flight transactions lost.
virtual sequence raises objection but sub-sequence also raises — inflated count.