In short: A UVM phase ends only when its objection count reaches zero, so every component that still has work to do calls raise_objection before starting and drop_objection when finished; forget the raise and the test ends at time zero, forget the drop and the simulation hangs until the global timeout.
Why the run_phase needs a "keep alive" mechanism
In a UVM testbench the driver, monitor and scoreboard each launch a run_phase task, and most of them contain a forever loop. None of those loops ever returns, so the simulator cannot use "all tasks finished" as the end-of-test signal. UVM needs a different rule, and that rule is the objection mechanism: the phase stays open while anyone objects to it ending, and closes when nobody does.
Think of it as a vote counter. Each object that raises an objection adds one to a counter; each drop subtracts one. The moment the aggregate count is zero, the phase machinery begins its shutdown. If you have not yet read how phases are sequenced, start with our guide to UVM phases, then come back here.
How raise_objection and drop_objection work
The usual call pattern, in the test or in a top-level sequence, looks like this:
task run_phase(uvm_phase phase);
phase.raise_objection(this, "main_seq running");
main_seq.start(env.agent.sqr);
phase.drop_objection(this, "main_seq done");
endtask
Three details matter. First, the objection is registered against the component passed as the first argument, but the count is propagated up through every parent to uvm_top. A raise in env.agent.drv is therefore visible at the root. Second, the optional third argument is a count (default 1), so you can raise several at once and drop the same number. Third, raising is not blocking in simulation time: it takes zero time, which is why you must raise before the first #delay or @event in the process that owns the work.
Key fact: If no component raises an objection in run_phase, the phase ends in the very first time step. A test that "passes" with 0 ns of simulation time and no transactions is the classic symptom.
Where to raise: the discipline that prevents most bugs
Raise objections where the test intent lives, not where data flows. The right owners are the test class or the top-level virtual sequence. Drivers and monitors should normally not raise at all; they react to whatever the stimulus side is doing. A driver that raises on every item and drops after the pin wiggle produces thousands of count changes per millisecond and, worse, can make the count briefly touch zero between items, ending the phase mid-stream.
For sequences, UVM 1.2 offers set_automatic_phase_objection(1), which raises when the sequence starts and drops when it completes. That is convenient for simple tests. For anything with a scoreboard, prefer one explicit raise/drop pair in the test so the shape of the run is readable in a single place. Our overview of the UVM testbench architecture shows which component belongs to which layer, which tells you who should own the objection.
Drain time: letting the DUT finish
Stimulus finishing is not the same as the design finishing. Suppose a bus master issues its last write at 10,000 ns, but the DUT has a pipeline of 20 cycles at 10 ns and the scoreboard still compares the response at 10,200 ns. If you drop at 10,000 ns, the phase ends and the final check is never exercised. There are two clean fixes:
- Drain time:
phase.phase_done.set_drain_time(this, 500ns);keeps the phase open for a fixed window after the count reaches zero. Simple, but the number is a guess and has to scale with clock speed and latency. - Scoreboard objection: the scoreboard raises when it holds an unmatched expected item and drops when its queue is empty. The phase then ends exactly when the checking is done, regardless of latency. This is the more robust approach in large SoC environments. See how the UVM scoreboard holds expected data, and TLM for how items reach it.
Watching the counter move
The two failure modes and how to debug them
| Symptom | Likely cause | Fix |
|---|---|---|
| Test ends at 0 ns, no stimulus | No raise, or raise placed after a delay | Raise first thing in run_phase; check for a missing super.run_phase in derived tests |
| Test ends mid-traffic | Count briefly hit zero between sequences | Hold one objection across the whole sequence list |
Simulation hangs, then UVM_FATAL timeout | Raise without matching drop (early return, disable fork, exception path) | Pair raise/drop in one scope; use +UVM_OBJECTION_TRACE |
Run with +UVM_OBJECTION_TRACE in VCS, Xcelium or Questa and every raise and drop is logged with the component path and running total; the leaked drop is usually obvious within a minute. The safety net is the global watchdog: the default timeout is 9200 s of simulation time, and you can tighten it with +UVM_TIMEOUT=2000000,YES or uvm_top.set_timeout() so a hung regression fails in an hour, not overnight. For liveness checks beyond that, read about the UVM heartbeat and objections.
Key fact: Never place drop_objection after a statement that can exit the task early. Wrap the work so the drop is the last line executed on every path, or use the sequence automatic-objection feature.
Interview-ready summary
- Objections are a distributed counter; phase ends when the root total is 0 and drain time has elapsed.
- Raise in the test or top sequence, not in the driver or monitor.
- Use a scoreboard objection or drain time so in-flight DUT work finishes.
- A leaked raise causes a hang; a missing raise causes a zero-time pass. Both are caught by objection tracing.
- Objections only gate task phases; function phases such as
build_phaseandreport_phasecomplete on their own.
If you want hands-on practice building a complete testbench with proper end-of-test control, explore the verification tracks in our CourseTron courses, or look at structured classroom training from ChipXpert.
Ready to master UVM? Join the CourseTron verification course and build, debug and close out real testbenches step by step.
Frequently asked questions
What happens if I never call raise_objection in run_phase?
The run_phase ends in the first time step, because the count is already zero. The test usually reports zero errors and passes without driving anything, which is why regressions should also check for a minimum transaction count.
Should the driver raise objections?
Generally no. The driver is reactive; the test or top-level sequence owns the intent. Raising per item makes the count noisy and risks premature zero crossings.
Is uvm_test_done still used?
No. uvm_test_done.raise_objection is the old UVM 1.1 style and was deprecated. Use the phase argument: phase.raise_objection(this).
How do I stop a hung test from running forever?
Lower the global timeout with +UVM_TIMEOUT or set_timeout(), and enable +UVM_OBJECTION_TRACE to find the component whose drop never arrived.