A team moves its stimulus out of the test and into a default sequence, a sequence the sequencer starts by itself at the beginning of a phase, set up with one config_db line. The test's run_phase becomes empty. The first run finishes in no time at all:
UVM_INFO seq_lab_tb.sv(515) @ 0: uvm_test_top [END] run phase over at 0, driver saw 0 itemsNobody objected, so the phase ended at time 0 and the sequence was killed before it sent anything. A sequence can keep the phase open, but only if it is told which phase it belongs to and asked to object. This lesson covers both, across UVM 1.1, 1.2 and IEEE 1800.2, and explains why the DMA bench does not use any of it.
An objection is a hand on the door: the phase cannot end while anyone is holding it (the rules are in Objections deep dive). The question is whose hand. A sequence knows when its own items are done. Only the test knows when the scenario is done: after the interrupt, after the last output word drained, after the status check. So a sequence holding the phase is right when the sequence is the scenario, as with a default sequence, and redundant or wrong when a test is already waiting for the real end.
For the sequence to object, it needs a phase to object on. That is its starting phase: the phase in which it was started.
Version | How a sequence finds its phase | How it objects |
|---|---|---|
UVM 1.1 | The public member | By hand: |
UVM 1.2 |
|
|
IEEE 1800.2 |
| Same as UVM 1.2 |
The UVM 1.1 habit of objecting in pre_body has a trap: pre_body is skipped when a parent starts the sequence with call_pre_post set to 0 (see Sequence lifecycle and hooks). Code that must use starting_phase today should move to the getter, which works in UVM 1.2 and IEEE 1800.2 alike.
set_automatic_phase_objection(1) makes start() raise an objection on the starting phase just before pre_start and drop it just after post_start. If the sequence is killed, kill() drops it too. Set it in the constructor so that every instance behaves the same way, however it is started:
// snippet: seq_lab_tb.sv
class lab_auto_seq extends uvm_sequence #(lab_item);
`uvm_object_utils(lab_auto_seq)
function new(string name = "lab_auto_seq");
super.new(name);
set_automatic_phase_objection(1);
endfunction
task body();
for (int i = 0; i < 5; i++) begin
lab_item it = lab_item::type_id::create("it");
start_item(it);
if (!it.randomize()) `uvm_fatal("RAND", "randomize failed")
finish_item(it);
end
`uvm_info("AUTO", "5 items sent", UVM_LOW)
endtask
endclassIf the starting phase is null, there is nothing to object on and the automatic objection silently does nothing. That is the normal case for a sequence started inside another sequence's body, and it is fine: the parent holds the phase.
A default sequence is registered against the sequencer's path plus the phase name. At the start of that phase the sequencer creates it, calls set_starting_phase(phase), randomizes it unless randomization is disabled, and starts it. It does not raise any objection for it:
// snippet: seq_lab_tb.sv, in the test's build_phase
uvm_config_db#(uvm_object_wrapper)::set(this, "env.sqr.run_phase", "default_sequence",
lab_auto_seq::type_id::get());The uvm_object_wrapper form passes a type and lets the factory build it, so a factory override applies. The other form, passing a configured sequence instance as uvm_sequence_base, and the way default sequences read their own knobs, are covered in Configuring sequences.
When a test or virtual sequence starts a sequence and does not wait for it, for example background traffic in a fork ... join_none, the starting phase is not set automatically. Set it before start():
// snippet
task run_phase(uvm_phase phase);
bg_traffic_seq bg = bg_traffic_seq::type_id::create("bg");
bg.set_starting_phase(phase);
bg.set_automatic_phase_objection(1);
fork bg.start(env.in_agt.sqr); join_none // bg now holds the phase until it ends
// ... the test's own work, under its own objection ...
endtaskEvery DMA test raises on its first line and drops after its last check. None of its sequences object. The reasons are worth saying in an interview:
The test knows the real end. A packet sequence ends when the last beat is accepted at the input. The job ends when the last word leaves the output and the interrupt is serviced, which no stream sequence can see.
Sequences stay reusable. apb_rw_seq runs inside tests, inside dma_vseq, and from the base test's helpers. With no phase logic, it behaves the same everywhere.
One owner is easy to debug. An objection trace with one raiser has one place to look. When the end must wait for the DUT, the bench uses a scoreboard's phase_ready_to_end (dma_drain_test), not more raisers; see End-of-test strategy.
// snippet: from the course DMA bench
// A virtual sequence drives a whole job across both agents, with background
// polling and a grab-based interrupt handler on the APB sequencer.
class dma_vseq_test extends dma_base_test;
`uvm_component_utils(dma_vseq_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
dma_vseq vseq = dma_vseq::type_id::create("vseq");
phase.raise_objection(this);
vseq.apb_sqr = env.apb.sqr;
vseq.axis_sqr = env.in_agt.sqr;
vseq.irq_vif = irq_vif;
if (!vseq.randomize()) `uvm_fatal("RAND", "dma_vseq randomize failed")
vseq.start(null);
if (vseq.status[2:1] !== 2'b01) `uvm_error("STATUS", $sformatf("expected DONE only, STATUS=%08h", vseq.status))
#100ns;
phase.drop_objection(this);
endtask
endclassEven the virtual-sequence test follows the rule: the test raises, starts dma_vseq with start(null), waits for it, and drops.
The lab test with lab_auto_seq as the run-phase default sequence and an empty test run_phase:
UVM_INFO seq_lab_tb.sv(48) @ 22000: uvm_test_top.env.drv [DRV] got lab_auto_seq WR 2c=204d826f
...
UVM_INFO seq_lab_tb.sv(281) @ 115000: uvm_test_top.env.sqr@@lab_auto_seq [AUTO] 5 items sent
UVM_INFO seq_lab_tb.sv(515) @ 115000: uvm_test_top [END] run phase over at 115000, driver saw 5 itemsThe same default sequence with set_automatic_phase_objection(0), the opening of this lesson:
UVM_INFO seq_lab_tb.sv(515) @ 0: uvm_test_top [END] run phase over at 0, driver saw 0 items
UVM_ERROR : 0Note the second log's UVM_ERROR : 0. A test that sends nothing and checks nothing passes. In the DMA bench, the scoreboard's SB_EMPTY check is what turns that into a failure.
A test forked a long background traffic sequence with join_none, ran a short directed scenario under its own objection, and dropped. The background sequence had set_automatic_phase_objection(1) in its constructor, so the author expected it to hold the phase until its traffic finished. It did not: the run phase ended as soon as the test dropped, and the background traffic was cut off after a fraction of its items, with coverage to match.
The automatic objection had nothing to object on. The sequence was started by hand, so its starting phase was null, and a null phase makes the automatic objection a silent no-op. One line, bg.set_starting_phase(phase) before start(), fixed it. The broader lesson: any mechanism that depends on the starting phase works by itself only for default sequences; everywhere else, you set the phase or you object yourself.
set_automatic_phase_objection(1) and was given set_starting_phase(phase), waits for it, and drops. How many objections are held during the sequence?Two: the test's and the sequence's, both on the run phase, so uvm_top's total is 2. It is harmless, because each is dropped by its owner, but it is redundant. An objection trace will show both, which is a reason to pick one owner.
starting_phase.raise_objection(this);. You move the bench to an IEEE 1800.2 library. What happens, and what do you write instead?It no longer compiles, because the starting_phase member is removed in IEEE 1800.2. Use set_automatic_phase_objection(1) in the constructor, or uvm_phase p = get_starting_phase(); if (p != null) p.raise_objection(this); in pre_start and the matching drop in post_start. Both also work in UVM 1.2.
forever loop of idle traffic). What happens to the test?The run phase never ends, because the sequence never reaches post_start to drop its objection. The test runs until the timeout and ends with PH_TIMEOUT. Endless background sequences must not object; let the test own the end, and the sequencer will kill the default sequence when the phase ends.
Next: the last lesson in this topic opens up the macros that legacy sequences use everywhere: The uvm_do macro family.