How to Terminate UVM Test? (UVM Objections)
In short: A UVM run phase ends when no component holds an objection. Each component that has work raises an objection at the start and drops it when done; while any objection is up, the phase keeps running. So to end a test cleanly you raise an objection before your stimulus and drop it after. A drain time gives trailing activity a moment to settle, and a global timeout catches tests that would otherwise hang.
One of the first puzzles in UVM is knowing when a test is finished. In plain SystemVerilog you call $finish yourself, but in UVM the run phase decides on its own, based on objections. Understand objections and two whole classes of bug disappear: tests that end before any stimulus runs, and tests that hang forever. This guide explains the objection mechanism, the raise and drop pattern, drain time, timeouts, ending on a condition, and the mistakes that cause early exits and hangs. The running example is a test that sends a burst of traffic and then finishes cleanly.
A simple way to picture it
Think of a meeting that cannot end while anyone still has something to say. As long as one person keeps their hand raised, the meeting continues. When the last hand goes down, the chair closes the meeting. In UVM, each component raises its hand (an objection) when it starts work and lowers it (drops the objection) when finished. The run phase is the chair: it waits until every hand is down, then ends the phase. A drain time is like the chair pausing a few seconds after the last hand drops, in case someone has a final word.
The raise and drop pattern
The core pattern lives in run_phase. You raise an objection before the work, run the stimulus, then drop the objection. The phase stays alive for exactly as long as the objection is up.
class base_test extends uvm_test;
`uvm_component_utils(base_test)
bus_env env;
function new(string n, uvm_component p); super.new(n,p); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = bus_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
burst_seq seq;
phase.raise_objection(this, "starting burst"); // hand up
seq = burst_seq::type_id::create("seq");
seq.start(env.agt.sqr); // do the work
phase.drop_objection(this, "burst complete"); // hand down
endtask
endclassThe two string arguments are optional but worth adding: UVM prints them, so a log clearly shows who is keeping the phase alive and why, which makes hangs far easier to debug.
What happens if you forget
The two failure modes are exact opposites, and knowing them by sight saves hours.
| Mistake | Symptom | Fix |
|---|---|---|
| Never raise an objection | Run phase ends at time 0; no stimulus runs | Raise before starting the sequence |
| Raise but never drop | Simulation hangs until the global timeout | Drop after the sequence completes |
| Drop too early | Test ends while traffic is still in flight | Wait for completion, or add a drain time |
Drain time: letting the tail settle
When the last objection drops, the phase would end on that exact clock. Often you want a short window afterward so the design can drain its pipeline, a monitor can capture the final transaction, and the scoreboard can finish checking. That window is the drain time. You set it on the objection or the phase.
function void end_of_elaboration_phase(uvm_phase phase);
// give 200 ns after the last objection drops before ending
uvm_test_done.set_drain_time(this, 200ns);
endfunction// or set it directly on the phase objection for the run phase
task run_phase(uvm_phase phase);
phase.phase_done.set_drain_time(this, 200ns);
phase.raise_objection(this);
seq.start(env.agt.sqr);
phase.drop_objection(this); // phase actually ends 200 ns later
endtaskPick a drain time that comfortably covers the longest tail of activity, for example the deepest pipeline latency in the design. Too short and you cut off the last transaction; far too long only wastes a little simulation time, so err on the generous side.
A global timeout: catching true hangs
Even with objections correct, a real design bug can make a sequence wait forever, for example on a response that never comes. A global timeout ends the run with a fatal message after a set time so your regression does not stall all night. Set it once, high enough that healthy tests never hit it.
initial begin
// fail the run if it is still going after 1 ms
uvm_top.set_timeout(1ms, 1);
endEnding on a condition, not a fixed count
Real tests often should end when something happens, not after a fixed number of items. The pattern is the same: keep the objection up until the condition is met, then drop it. Here the test runs traffic until the scoreboard has checked a target number of reads.
task run_phase(uvm_phase phase);
phase.raise_objection(this);
fork
// keep sending traffic
forever begin
traffic_seq seq = traffic_seq::type_id::create("seq");
seq.start(env.agt.sqr); // start_item/finish_item live inside the sequence body
end
join_none
// wait for the real end condition
wait (env.scb.reads_checked >= 100);
phase.drop_objection(this); // done when the goal is reached
endtaskBecause the objection stays up until the wait completes, the phase cannot end early no matter how the traffic thread behaves. This is the clean way to tie test length to a goal rather than a guess.
Objections from more than one component
Objections are counted across the whole testbench, not just the test. A driver mid-transaction, a sequence still running, and the test can each hold an objection at once. The phase ends only when the total count reaches zero. This is why a well-behaved component raises its own objection while it has outstanding work, so the run cannot end underneath it.
// a monitor that holds the phase open while a transaction is in flight
task run_phase(uvm_phase phase);
forever begin
@(posedge vif.clk iff vif.valid);
phase.raise_objection(this); // do not let the test end mid-transaction
collect_transaction();
phase.drop_objection(this);
end
endtaskCommon mistakes to avoid
- Forgetting to raise an objection, so the run phase ends at time zero and no stimulus runs.
- Raising an objection and never dropping it, so the test hangs until the global timeout.
- Dropping the objection before the sequence actually finishes, cutting the test short.
- Relying only on a fixed delay like #1000 instead of objections, which is fragile and breaks when timing changes.
- No global timeout, so a design bug that stalls a sequence hangs the whole regression.
- Too short a drain time, so the last transaction is cut off before the scoreboard checks it.
Keep learning
Objections are part of phasing, so read UVM phasing to see where the run phase sits, and how UVM phasing is triggered for the mechanism that starts it. See the raise and drop pattern inside a full test in the UVM testbench architecture guide, and learn how fatal messages force an end in UVM reporting and macros. More is on the UVM category page and in our interview questions.
Frequently asked questions
Objections control when a phase ends, which matters when a run changes phases on purpose. See it applied in reset testing with a phase jump.
How does a UVM test know when to end?
The run phase ends when no component holds an objection. Each component raises an objection while it has work and drops it when finished, and the phase stays alive as long as the total count is above zero. When the last objection drops, the phase ends.
Why does my test finish immediately with no activity?
Almost always because no objection was raised. With nothing keeping the run phase alive, it ends at time zero before any stimulus runs. Raise an objection before starting your sequence and drop it after.
Why does my test hang forever?
The usual cause is an objection that was raised but never dropped, so the phase never ends. A design bug that makes a sequence wait on a response that never arrives has the same effect. A global timeout will end the run with a fatal message so it does not stall indefinitely.
What is drain time for?
Drain time is a grace period after the last objection drops, letting trailing activity settle: the pipeline drains, the monitor captures the final transaction, and the scoreboard finishes checking. Without it, the phase could end one cycle too early and miss the last result.
Should I use #delay or objections to control test length?
Use objections. A fixed delay is fragile because it breaks when timing changes and can cut the test short or waste time. Objections tie the end of the test to real completion of work, which is both safer and self-adjusting.
Do drivers and monitors need objections too?
They should raise an objection whenever they have outstanding work that must not be interrupted, such as a transaction in flight. Because objections are counted testbench-wide, this stops the run from ending while a component is mid-operation.




