A UVM objection is how a testbench decides when a time-consuming phase, such as run_phase, is allowed to end. Any component or sequence that still has work to do raises an objection to the phase ending, and drops it when the work is finished. When the last objection is dropped, the phase ends and the testbench moves on to the next phase.
A testbench has many components running in parallel, and none of them knows when the others are done. Rather than one central controller deciding when to stop, UVM lets each part of the testbench vote. The phase only ends when nobody is objecting any more.
9 min read | Intermediate Level
What You'll Learn
- Explain how objections decide when a task phase ends
- Raise and drop objections correctly from a test or a sequence
- Use drain time to let in-flight transactions finish
- Debug a simulation that ends too early or never ends
How Objections Work
Every task phase - run_phase and the run-time phases such as reset_phase and main_phase - has its own built-in uvm_objection object. It behaves like a counter. Raising an objection adds one, dropping it subtracts one, and the phase can end only when the count is back to zero.
The counts are also hierarchical. When m_driver raises an objection, its parents m_agent, m_env and uvm_test_top each add it to their total count, all the way up to uvm_top. That is what makes the trace and display output below show who is holding the phase open.
A task phase ends as soon as nothing is objecting. If no component raises an objection at all, run_phase ends at time 0 and your stimulus never runs.
Syntax
You do not create uvm_objection objects yourself. Use the phase handle passed into every phase method.
// Methods of uvm_phase
virtual function void raise_objection (uvm_object obj, string description = "", int count = 1);
virtual function void drop_objection (uvm_object obj, string description = "", int count = 1);
function uvm_objection get_objection(); // the phase's objection object
// Method of uvm_objection
function void set_drain_time (uvm_object obj = null, time drain);
objis normallythis, so traces show which component or sequence raised the objection.descriptionis optional, but a short message such as"sending reset sequence"makes hang debugging much easier.countlets one call raise or drop several objections at once. It almost always stays at 1.
Raising an Objection in a Test
The usual place to raise an objection is the test, around the stimulus it starts. The test knows when its top-level sequence has finished, so it is in the best position to say the phase may end.
class my_test extends uvm_test;
`uvm_component_utils(my_test)
function new(string name = "my_test", uvm_component parent = null);
super.new(name, parent);
endfunction
// Rest of the code (build_phase creates m_env)
virtual task main_phase(uvm_phase phase);
my_virtual_seq m_vseq = my_virtual_seq::type_id::create("m_vseq");
phase.raise_objection(this, "running main stimulus"); // keep main_phase alive
m_vseq.start(m_env.m_vseqr); // blocks until the sequence ends
phase.drop_objection(this, "main stimulus done"); // main_phase may now end
endtask
endclass
Because start() blocks until the sequence completes, the objection covers exactly the time the stimulus is running.
Example: Who Keeps the Phase Alive?
This example raises objections in two places to show how they combine. The test objects while a 200 ns sequence runs, and the driver objects for 400 ns. Raising an objection in a driver is unusual - it is done here only for illustration.
`include "uvm_macros.svh"
import uvm_pkg::*;
// Let's create a driver that attempts to raise/drop objection
class my_driver extends uvm_driver;
`uvm_component_utils(my_driver)
function new(string name="my_driver", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
uvm_objection l_obj = phase.get_objection();
`uvm_info(get_type_name(), $sformatf("Driver started"), UVM_MEDIUM)
// Raise an objection in the driver just for illustration
// Usually the driver waits until it gets an item from the
// sequencer and does not have to raise any objections.
phase.raise_objection(this);
#400;
phase.drop_objection(this);
l_obj.display_objections();
`uvm_info(get_type_name(), $sformatf("Driver ended"), UVM_MEDIUM)
endtask
endclass
// Let the driver be instantiated inside an agent
class my_agent extends uvm_agent;
`uvm_component_utils(my_agent)
my_driver m_driver;
function new(string name="my_agent", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_driver = my_driver::type_id::create("m_driver", this);
endfunction
endclass
// Define a virtual sequencer to run the test stimulus/hold other handles
class my_vseqr extends uvm_sequencer;
`uvm_component_utils(my_vseqr)
function new(string name="my_vseqr", uvm_component parent=null);
super.new(name, parent);
endfunction
endclass
// Define an environment to hold the agent/virtual sequencer
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_vseqr m_vseqr;
my_agent m_agent;
function new(string name="my_env", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_agent = my_agent::type_id::create("m_agent", this);
m_vseqr = my_vseqr::type_id::create("m_seqr", this);
endfunction
endclass
// Define the test stimulus that consumes time
class my_seq extends uvm_sequence;
`uvm_object_utils(my_seq)
function new(string name="my_seq");
super.new(name);
endfunction
virtual task body();
`uvm_info(get_type_name(), $sformatf("Sequence started"), UVM_MEDIUM)
#200;
`uvm_info(get_type_name(), $sformatf("Sequence ended"), UVM_MEDIUM)
endtask
endclass
// Define the test which starts the virtual sequence/test stimulus
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env m_env;
function new(string name="my_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_env = my_env::type_id::create("m_env", this);
endfunction
virtual task run_phase(uvm_phase phase);
my_seq l_seq = my_seq::type_id::create("my_seq");
// Lets get a handle to the objection inorder to print stats
uvm_objection l_obj = phase.get_objection();
super.run_phase(phase);
// Let the test raise an objection and then display all objections
phase.raise_objection(this);
l_obj.display_objections();
l_seq.start(m_env.m_vseqr);
l_obj.display_objections();
phase.drop_objection(this);
endtask
endclass
// Let a clock run forever in the testbench
module tb;
reg clk;
always #10 clk = ~clk;
initial begin
clk = 0;
run_test("my_test");
end
endmodule
The simulation was run with +UVM_OBJECTION_TRACE, which prints a line for every raise and drop. display_objections() prints the counts as a hierarchy table.
UVM_INFO @ 0: reporter [RNTST] Running test my_test... UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top raised 1 objection(s): count=1 total=1 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object uvm_test_top): count=0 total=1 UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1072) @ 0: run [UVM/OBJ/DISPLAY] The total objection count is 1 --------------------------------------------------------- Source Total Count Count Object --------------------------------------------------------- 0 1 uvm_top 1 1 uvm_test_top --------------------------------------------------------- UVM_INFO testbench.sv(14) @ 0: uvm_test_top.m_env.m_agent.m_driver [my_driver] Driver started UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent.m_driver raised 1 objection(s): count=1 total=1 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent added 1 objection(s) to its total (raised from source object ): count=0 total=1 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top.m_env added 1 objection(s) to its total (raised from source object ): count=0 total=1 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_test_top added 1 objection(s) to its total (raised from source object uvm_test_top.m_env.m_agent.m_driver): count=1 total=2 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object uvm_test_top.m_env.m_agent.m_driver): count=0 total=2 UVM_INFO testbench.sv(82) @ 0: uvm_test_top.m_env.m_seqr@@my_seq [my_seq] Sequence started UVM_INFO testbench.sv(84) @ 200: uvm_test_top.m_env.m_seqr@@my_seq [my_seq] Sequence ended UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1072) @ 200: run [UVM/OBJ/DISPLAY] The total objection count is 2 --------------------------------------------------------- Source Total Count Count Object --------------------------------------------------------- 0 2 uvm_top 1 2 uvm_test_top 0 1 m_env 0 1 m_agent 1 1 m_driver --------------------------------------------------------- UVM_INFO @ 200: run [OBJTN_TRC] Object uvm_test_top dropped 1 objection(s): count=0 total=1 UVM_INFO @ 200: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top): count=0 total=1 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent.m_driver dropped 1 objection(s): count=0 total=0 UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1072) @ 400: run [UVM/OBJ/DISPLAY] The total objection count is 1 --------------------------------------------------------- Source Total Count Count Object --------------------------------------------------------- 0 1 uvm_top 0 1 uvm_test_top 0 1 m_env 0 1 m_agent --------------------------------------------------------- UVM_INFO testbench.sv(24) @ 400: uvm_test_top.m_env.m_agent.m_driver [my_driver] Driver ended UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent.m_driver all_dropped 1 objection(s): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent subtracted 1 objection(s) from its total (dropped from source object ): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env.m_agent subtracted 1 objection(s) from its total (all_dropped from source object ): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env subtracted 1 objection(s) from its total (dropped from source object ): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top.m_env subtracted 1 objection(s) from its total (all_dropped from source object ): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_test_top subtracted 1 objection(s) from its total (all_dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0 total=0 UVM_INFO @ 400: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object uvm_test_top.m_env.m_agent.m_driver): count=0 total=0 UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 400: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 400: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
Reading the log against the timeline: at 0 ns the test and the driver both raise, so uvm_top holds a total of 2. At 200 ns the sequence finishes and the test drops, leaving a total of 1. The phase still cannot end, because m_driver is objecting. At 400 ns the driver drops, the total reaches 0, and the TEST_DONE message reports that run is moving on to extract.
Quick Check
Q: If the driver in this example did not raise an objection, when would the run phase end?
Show Answer
At 200 ns, when the test drops its objection after the sequence finishes. The driver would simply be stopped in the middle of its #400 delay, because the phase's processes are killed when the phase ends.
Objections in Sequences
A sequence can also hold the phase open, which is useful when sequences are started as default sequences and no test code wraps them. A sequence gets the phase through get_starting_phase(), which returns null unless the phase was set - automatically for a default sequence, or by calling set_starting_phase() before start().
Raising Explicitly in body()
// Rest of the code: my_seq_item is a simple uvm_sequence_item with one rand field
class my_sequence extends uvm_sequence #(my_seq_item);
`uvm_object_utils (my_sequence)
rand int num_items = 5;
constraint c_num_items { num_items > 0; num_items <= 20; }
function new (string name = "my_sequence");
super.new(name);
endfunction
virtual task body();
uvm_phase phase = get_starting_phase(); // null unless the starting phase was set
if (phase != null)
phase.raise_objection(this, "Sequence generating stimulus");
repeat (num_items) #10; // stand-in for the real stimulus
if (phase != null)
phase.drop_objection(this, "Sequence finished stimulus generation");
endtask
endclass
class my_test extends uvm_test;
`uvm_component_utils(my_test)
function new(string name="my_test", uvm_component parent=null);
super.new(name, parent);
endfunction
virtual task run_phase(uvm_phase phase);
my_sequence l_seq = my_sequence::type_id::create("l_seq");
`uvm_info(get_type_name(), $sformatf("my_test started"), UVM_MEDIUM)
l_seq.set_starting_phase(phase); // give the sequence the phase to object to
l_seq.start(null);
`uvm_info(get_type_name(), $sformatf("my_test ended"), UVM_MEDIUM)
endtask
endclass
UVM_INFO @ 0: reporter [RNTST] Running test my_test... UVM_INFO testbench.sv(64) @ 0: uvm_test_top [my_test] my_test started UVM_INFO @ 0: run [OBJTN_TRC] Object l_seq raised 1 objection(s) (Sequence generating stimulus): count=1 total=1 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object l_seq, Sequence generating stimulus): count=0 total=1 UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq dropped 1 objection(s) (Sequence finished stimulus generation): count=0 total=0 UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq all_dropped 1 objection(s) (Sequence finished stimulus generation): count=0 total=0 UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object l_seq, Sequence finished stimulus generation): count=0 total=0 UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object l_seq, Sequence finished stimulus generation): count=0 total=0 UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase UVM_INFO testbench.sv(69) @ 50: uvm_test_top [my_test] my_test ended UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
Five items of 10 ns each give the drop at 50 ns. The description strings appear in the trace, which is exactly what you want when hunting for the objection that never dropped.
Automatic Phase Objection
For the common case where a sequence should object for its whole lifetime, call set_automatic_phase_objection(1) in its constructor. If the starting phase is set, UVM raises the objection just before pre_start() and drops it just after post_start().
class my_auto_sequence extends uvm_sequence #(my_seq_item);
`uvm_object_utils (my_auto_sequence)
rand int num_items = 5;
constraint c_num_items { num_items > 0; num_items <= 20; }
function new (string name = "my_auto_sequence");
super.new(name);
set_automatic_phase_objection(1); // raise before pre_start(), drop after post_start()
endfunction
virtual task body();
`uvm_info (get_type_name(), $sformatf("sequence started"), UVM_MEDIUM)
// No explicit raise_objection or drop_objection calls are needed here,
// as they are handled automatically by the base class
repeat (num_items) begin
#10; // Simulate some time passing between transactions
end
`uvm_info (get_type_name(), $sformatf("sequence ended"), UVM_MEDIUM)
endtask
endclass
// Rest of the code: my_test is the same as above, starting my_auto_sequence instead
UVM_INFO @ 0: reporter [RNTST] Running test my_test... UVM_INFO testbench.sv(57) @ 0: uvm_test_top [my_test] my_test started UVM_INFO @ 0: run [OBJTN_TRC] Object l_seq raised 1 objection(s) (automatic phase objection): count=1 total=1 UVM_INFO @ 0: run [OBJTN_TRC] Object uvm_top added 1 objection(s) to its total (raised from source object l_seq, automatic phase objection): count=0 total=1 UVM_INFO testbench.sv(34) @ 0: reporter@@l_seq [my_auto_sequence] sequence started UVM_INFO testbench.sv(42) @ 50: reporter@@l_seq [my_auto_sequence] sequence ended UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq dropped 1 objection(s) (automatic phase objection): count=0 total=0 UVM_INFO @ 50: run [OBJTN_TRC] Object l_seq all_dropped 1 objection(s) (automatic phase objection): count=0 total=0 UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (dropped from source object l_seq, automatic phase objection): count=0 total=0 UVM_INFO @ 50: run [OBJTN_TRC] Object uvm_top subtracted 1 objection(s) from its total (all_dropped from source object l_seq, automatic phase objection): count=0 total=0 UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase UVM_INFO testbench.sv(62) @ 50: uvm_test_top [my_test] my_test ended UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 50: reporter [UVM/REPORT/SERVER] --- UVM Report Summary ---
Never enable automatic phase objection on a sequence whose body() contains a forever loop. The objection is dropped only when the sequence ends, so the phase never ends and the simulation hangs.
Drain Time
When the last objection drops, transactions may still be in flight: a response the DUT has not produced yet, or a monitor that has not finished sampling. Drain time makes the objection wait a little longer before it lets the phase end.
virtual task run_phase(uvm_phase phase);
phase.get_objection().set_drain_time(this, 100ns); // wait 100ns after the count hits 0
phase.raise_objection(this);
// Rest of the code (start stimulus)
phase.drop_objection(this);
endtask
If any component raises a new objection during the drain time, the countdown is cancelled and the phase stays open. Set drain time once, in the test or environment, rather than scattering delays at the end of individual sequences.
Debugging Objections
+UVM_OBJECTION_TRACEon the command line prints every raise and drop with its description, as in the logs above.+UVM_PHASE_TRACEtraces phase starts and ends, which helps when a phase seems to end too early.display_objections()on the objection object prints the current hierarchy of counts at any point in your code.- For finer control, extend
uvm_objection_callbackto react to raises and drops, and useclear()on an objection to release all counts at once.
Common Mistakes
Mistake #1: Raising an objection that is never dropped
A raise inside a branch that returns early, or a sequence that blocks forever, leaves the count above zero, and the simulation hangs until a timeout. Make every raise have exactly one matching drop on every path, and use descriptive strings so +UVM_OBJECTION_TRACE shows the culprit.
Mistake #2: Raising no objection at all
Starting a sequence in run_phase without an objection lets the phase end at time 0. The log shows the test starting and the report summary straight after, with no stimulus in between.
Mistake #3: Calling get_starting_phase() on a manually started sequence
A sequence started with start() has no starting phase unless the caller set one with set_starting_phase(). Without a null check, phase.raise_objection() dereferences a null handle.
Mistake #4: Raising objections in every component
Monitors, drivers and scoreboards that each raise and drop objections for every transaction add overhead and make the end of test hard to reason about. Keep objections in the test or top-level sequence, and in the scoreboard only if it must wait for outstanding checks.
Summary
In this article, you learned:
- Each task phase has a built-in objection, and the phase ends when its count reaches zero
- Objections are raised and dropped through the phase handle, ideally with a description
- Counts propagate up the hierarchy, so traces show which component is holding a phase open
- Sequences can object through
get_starting_phase()or automatically withset_automatic_phase_objection(1) - Drain time delays the end of a phase so in-flight transactions can finish