How to terminate UVM Test

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
endclass

The 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.

MistakeSymptomFix
Never raise an objectionRun phase ends at time 0; no stimulus runsRaise before starting the sequence
Raise but never dropSimulation hangs until the global timeoutDrop after the sequence completes
Drop too earlyTest ends while traffic is still in flightWait 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
endtask

Pick 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);
end

Ending 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
endtask

Because 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
endtask

Common 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.

Similar Posts