UVM
Drop Objection
How drop_objection works — balancing every raise so the count returns to zero, dropping only after the work truly completes (including drain time for in-flight effects), and why dropping too early ends the phase with work still unchecked.
Objections · Module 17 · Page 17.3
The Engineering Problem
Raising an objection holds the phase open (Module 17.2). The drop is its mirror — it releases the hold, decrementing the count, and when the count reaches zero, the phase may end. Like the raise, it looks trivial — phase.drop_objection(this) — but it carries the harder half of the objection contract: balance and timing. Balance: every raise must be matched by drops that return the count to zero, or the phase never ends (a never-dropped objection hangs the simulation forever). Timing: the drop signals "my work is done" — but when is the work truly done? Not when the last stimulus is sent — there is latency between sending a transaction and its effects being fully observed and checked (the DUT's pipeline, the response path, the scoreboard's comparison). Drop the instant the last stimulus leaves and the phase ends with the last transactions still in flight, unchecked — a false pass. The problem this chapter solves is dropping correctly: balancing every raise, and dropping only after the work truly completes — including the drain of in-flight effects.
drop_objection is the call a component makes to release its objection and decrement the phase's count. Its signature mirrors the raise: phase.drop_objection(this, description, count) — this (the component), description (optional, for tracing), count (optional, default 1). The drop decrements the count, and when the total returns to zero, the phase may end (after any drain time). Two disciplines govern it. Balance: every raise must be matched by drops totaling the same count, on every code path — a missing drop hangs the phase (count never zero), an excess drop is an error (count can't go below zero). Timing: drop only after the work truly completes — not when the last stimulus is sent, but after its effects have drained (propagated through the DUT, been observed, been checked) — using a drain time (set_drain_time) when there's trailing latency. This chapter is the drop in detail: the call, balance, drop timing, and drain time.
How does
drop_objectionwork — balancing every raise so the count returns to zero, and dropping only after the work truly completes (including drain time for in-flight effects) — and why does dropping too early end the phase with work still unchecked?
Motivation — why the drop is the harder half
The drop seems symmetric with the raise, but it carries the harder problems — balance and true-completion timing. The reasons:
- An unbalanced drop hangs or errors. The phase ends only when the count is zero, so every raise must be matched by drops summing to the same count. A missing drop leaves the count nonzero → the phase hangs forever; an excess drop drives the count below zero → an error.
- Balance must hold on every path. The drop must execute on every code path — normal completion, early returns, exceptions, disabled threads. A path that skips the drop hangs the phase. This is why the auto-objection (Module 17.2) is safer than manual raise/drop.
- "Work done" is not "last stimulus sent." The moment you send the last transaction is not the moment the work is done — the transaction must propagate through the DUT, the response must arrive, the monitor must observe it, the scoreboard must check it. Drop when the last stimulus leaves and you drop before the work is actually finished.
- In-flight effects need drain time. Because of latency, there's a window after the last stimulus where results are still arriving and being checked. The phase must stay open through that drain. A drain time holds the phase open a configured delay after the count would reach zero — letting trailing work complete.
- Dropping too early silently loses checking. If the phase ends with the last transactions in flight, those transactions are never checked — a silent loss of coverage on exactly the final work, which can hide bugs and pass a broken design.
The motivation, in one line: the drop must balance every raise on every path (or the phase hangs or errors) and be placed after the work truly completes — not at last-stimulus-sent but after the effects drain (propagate, observe, check) — because dropping too early ends the phase with in-flight work unchecked, silently losing coverage on the final transactions.
Mental Model
Hold dropping an objection as clocking out — only when your work is truly finished, with a grace period for trailing tasks:
Dropping an objection is clocking out: you sign out of the register when your work is done, and the workplace closes when the register is empty. But you must clock out only when your work is truly finished — if a task is still in flight (a report still printing, a result still coming back), clocking out too early lets the building close with work unfinished. A grace period after the last clock-out keeps the doors open just long enough for trailing tasks to complete. Picture the workplace (the phase) that closes when the register is empty (the count is zero). You clocked in when you started (raised, Module 17.2); now you clock out when you're done (drop). Two things must be right. First, balance: every clock-in needs a clock-out — if you clock in and forget to clock out (on some path — you left through a side door, an exception), the register never empties and the building never closes (the phase hangs); and you can't clock out more than you clocked in (the register can't go below zero — an error). Second, and subtler, timing: you must clock out only when your work is truly finished — not when you sent the last request, but when its result has come back and been checked. There's often trailing work: you fire off the last task, but the result is still in the pipeline, the checker is still verifying it. If you clock out the instant you fire it off, the building sees the register empty and closes — with the result still in flight, never checked. So you wait for the trailing work to drain before clocking out. And the workplace itself offers a grace period (the drain time): after the last person clocks out, the doors stay open a configured while longer, so any last task still completing can finish before the doors lock. Clock out only when truly done; balance every clock-in; and let the grace period cover the trailing work.
So dropping an objection is clocking out when truly done: balance every raise with a matching drop on every path (or the phase hangs/errors), and time the drop for after the work truly completes — not at last-stimulus-sent but after the effects drain — with the drain time as the grace period that holds the doors open for trailing work. Clock out only when your work — including its trailing effects — is truly finished, and make sure every clock-in is matched.
Visual Explanation — the drop decrements toward zero
The defining picture is the decrement: the drop lowers the count, and when it reaches zero (after any drain time), the phase may end.
The figure shows the drop and what it does. A component (whose work is done) calls phase.drop_objection(this, description, count) — the arguments mirror the raise (this, optional description, optional count). The drop decrements the objection count (-= count). Then the outcome depends on the resulting total: if it's still positive (other work remains — some other component is still objecting), the phase stays alive; if it reaches zero, the phase may end — but first, any configured drain time elapses, holding the phase open a while longer for trailing work, then the phase ends. The crucial reading is that the drop moves toward ending but does not guarantee it: the phase ends only when the count is zero and the drain time has passed. The warning-colored count is decremented; the success-colored end happens only at zero-plus-drain. This captures the two conditions for the phase to end: all objections dropped (count zero) and the drain complete. A single drop, then, is one step toward ending — it removes one component's hold, and whether the phase ends depends on whether that was the last hold (count zero) and whether the drain time has elapsed. The diagram is the anatomy of the drop: decrement the count, and if it's now zero, wait out the drain time, then end — so the drop is the release that, combined with all other releases and the drain, lets the phase finish. The drain time in the path is the visual reminder that zero count is necessary but not instantly sufficient — the phase gives trailing work a grace period before closing.
RTL / Simulation Perspective — balance, timing, and drain time
In code, the drop must balance the raise on every path, and be placed after the work truly drains — with a drain time for trailing latency. The code shows all three.
// === BALANCE: every raise matched by a drop — on EVERY path (auto-objection does this safely) ===
task run_phase(uvm_phase phase);
phase.raise_objection(this, "stimulus");
seq.start(env.agt.seqr); // the work
wait_for_responses_to_drain(); // ✓ wait until the LAST response is observed + checked
phase.drop_objection(this, "stimulus complete"); // drop AFTER the work truly completes
endtask
// ✗ dropping right after seq.start() returns would drop while the LAST responses are still in flight
// === DRAIN TIME: hold the phase open a configured delay after the count reaches zero ===
function void my_test::build_phase(uvm_phase phase);
uvm_phase rp = uvm_run_phase::get();
rp.phase_done.set_drain_time(this, 100ns); // ← after count hits 0, wait 100ns for in-flight work
endfunction
// the phase ends at: (count == 0) AND (drain time elapsed) — trailing responses get time to arrive
// === Why balance on every path matters — a skipped drop hangs the phase forever ===
task buggy(uvm_phase phase);
phase.raise_objection(this);
if (early_exit) return; // ✗ returns WITHOUT dropping → count stuck > 0 → phase HANGS
do_work();
phase.drop_objection(this);
endtaskThe code shows the three drop concerns. Balance: the run_phase raises then drops — but the drop must execute on every path. The buggy example shows the failure: an if (early_exit) return; that returns without dropping leaves the count stuck above zero → the phase hangs forever. (This is why the auto-objection of Module 17.2 is safer — it brackets the body and drops on all exits automatically.) Timing: the run_phase waits for the responses to drain (wait_for_responses_to_drain() — until the last response is observed and checked) before dropping; the ✗ comment marks the trap — dropping right after seq.start() returns drops while the last responses are still in flight. Drain time: rp.phase_done.set_drain_time(this, 100ns) holds the phase open a configured delay (100ns) after the count reaches zero — so the phase ends at (count == 0) AND (drain time elapsed), giving trailing responses time to arrive. The shape to carry: a drop must (1) balance the raise on every path (or the phase hangs — prefer the auto-objection), (2) be placed after the work truly drains (wait for the last response to be observed and checked, not just sent), and (3) use a drain time when there's trailing latency (so the phase stays open through the drain). The drop is not "I sent the last transaction" — it's "the last transaction's effects have fully completed and been checked."
Verification Perspective — the two failure modes of the drop
The drop has two characteristic failure modes — never dropped (the phase hangs) and dropped too early (work unchecked) — and they are opposite errors with opposite symptoms. Seeing both frames the drop's discipline.
The figure shows the drop's two failure modes — opposite errors with opposite symptoms — and the correct drop between them. Never dropped: a raise with no matching drop (on some path) leaves the count nonzero, so the phase never ends — the simulation hangs (a loud, obvious failure — the run never completes). Dropped too early: the drop happens before the work's effects have drained, so the count reaches zero while work is in flight, the phase ends, and the last transactions go unchecked — a silent false pass (a quiet, dangerous failure — the run completes and passes, but missed the final work). The brand-colored correct drop is balanced (every raise matched, on every path) and timed (after the work truly completes, with drain time) — between the two failure modes. The verification insight is that the drop is bracketed by two dangers pulling in opposite directions: drop too little/late (never) → hang; drop too much/early (premature) → unchecked work. The never-dropped failure is bad but obvious (the sim hangs — you notice). The dropped-too-early failure is worse because silent (the sim passes — you don't notice) — it quietly loses checking on exactly the final transactions, which can hide a real bug behind a green result. So the drop's discipline is to thread between: balance (so it does drop, on every path — avoiding the hang) and timing (so it drops late enough, after the drain — avoiding the premature end). The figure is the map of drop errors: too little → hang (loud), too much/early → unchecked (silent), correct → balanced and timed (ends when work truly done). The silent failure is the one to fear — which is why drop timing (drain) gets the careful attention.
Runtime / Execution Flow — dropping after the work drains
At run time, the correct drop comes after the work's effects have drained — there's a gap between last-stimulus-sent and work-fully-checked, and the drop belongs after that gap. The flow shows the sequence.
The flow shows why the drop belongs after the drain, not at last-stimulus-sent. Sent (step 1): the last stimulus is sent — but its effects are still in flight; the work is not done yet (the warning color marks the trap of treating this as "done"). Propagate (step 2): the response propagates through the DUT — the transaction works through the pipeline, and the response arrives some cycles later. Check (step 3): the monitor observes it and the scoreboard checks it — the last work is verified. Drop (step 4): only now is the work truly complete; drop the objection (a drain time can cover this gap automatically). The runtime insight is the gap between step 1 (last stimulus sent) and step 4 (work checked): there's latency — the DUT's pipeline, the response path, the scoreboard's comparison — and the drop belongs after that latency, at step 4, not at step 1. Dropping at step 1 (the tempting place — right after seq.start() returns) ends the phase while the response is still propagating (step 2) — before it's observed and checked (step 3) — so the last transaction's check never happens. The correct drop waits for the work to drain through steps 2–3, then drops at step 4. The drain time (Module's mechanism) automates this: it holds the phase open a configured delay after the count would reach zero, covering the gap so trailing responses arrive and are checked before the phase actually ends. The flow is the timing map of the drop: the work isn't done when you stop sending — it's done when the effects have drained and been checked — so the drop (or the drain time) must span the gap between sending and checking, or the final work is lost.
Waveform Perspective — dropping too early cuts off the response
The drop-timing hazard is visible on a timeline: the last stimulus is sent, the response arrives cycles later, and a too-early drop ends the phase before the response is checked. The waveform contrasts the two.
Dropping at last-stimulus-sent cuts off the response; dropping after drain checks it
12 cyclesThe waveform shows the drop-timing hazard and its fix. The last stimulus is sent at cycle 1 (last_stim) — but the work is not done: the DUT's response arrives several cycles later at cycle 4 (response), and the scoreboard checks it at cycle 5 (check). The correct drop waits for this drain: obj_count stays positive through the response and check, and drops to zero at cycle 6 (after the work is verified) — so the phase ends only after the response is checked. The dashed contrast (drop_early) shows the hazard: an early drop at cycle 1 would take the count to zero immediately and end the phase before the response (cycle 4) and check (cycle 5) — the last transaction goes unchecked. The crucial reading is the gap between last_stim (cycle 1) and check (cycle 5): the response and its check happen in that gap — the drain window — and the count must stay positive through it. The correct drop keeps obj_count positive until after the check (dropping at cycle 6); the early drop would zero it at cycle 1, cutting off the drain. The picture to carry is that the work's completion lags the last stimulus — the response and check trail the send — so the drop must lag too, waiting out the drain window (or a drain time must hold the phase open through it). Reading this waveform — does the count stay positive until the last response is checked? does the phase end only after the drain? — confirms the drop is timed correctly. The count holding through the response and check, dropping only after is the signature of a correctly timed drop: the phase ends when the work — including its trailing effects — is truly done, not when the last stimulus left.
DebugLab — the test that passed but never checked its last transactions
A false pass because the objection was dropped when the last stimulus was sent, not after it was checked
A test passed — but a bug in the DUT's handling of the final transactions escaped. Investigation showed the last few transactions of every test were never checked: the scoreboard's log ended several transactions short of what was sent. The DUT was still processing the final transactions when the test ended — their responses arrived after the phase had already closed, so the scoreboard never saw them. The bug lived in exactly those unchecked final transactions, and the test passed because it didn't check the work where the bug was.
The objection was dropped the instant the last stimulus was sent — before the DUT's pipeline had drained and the final responses had been observed and checked. The count hit zero with work still in flight, so the phase ended early:
✗ DROP right after the stimulus is SENT — before the responses drain:
task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(env.agt.seqr); // sends all stimulus; RETURNS when the LAST item is SENT
phase.drop_objection(this); // ✗ drops NOW — but the last responses are still IN FLIGHT
endtask
// count → 0 while the DUT pipeline is draining → phase ends → last responses NEVER checked → false pass
✓ DROP after the work truly DRAINS (wait for it, or set a drain time):
task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(env.agt.seqr);
wait_until_all_responses_checked(); // ✓ wait for the pipeline to drain + scoreboard to finish
phase.drop_objection(this);
endtask
// OR, declaratively: rp.phase_done.set_drain_time(this, 200ns); // hold open 200ns after count=0This is the drop-too-early bug — the silent, dangerous drop failure: the objection is dropped when the last stimulus is sent, not when the work is truly done, so the phase ends with the final transactions still in flight, unchecked. The seq.start() call returns when the last item is sent — not when its response has come back and been checked. So dropping right after seq.start() returns drops while the DUT's pipeline is still draining: the last responses are in flight, not yet observed, not yet checked. The count hits zero, the phase ends, and those final responses arrive at a dead testbench — the scoreboard never sees them. The test passes because it checked everything except the final transactions — and the bug was in the final transactions, so it escaped. The silence is the danger: there's no hang, no error — the test completes and passes, quietly having skipped the last checks. The fix is to drop after the work truly drains: either explicitly wait for the last response to be observed and checked (wait_until_all_responses_checked()) before dropping, or declaratively set a drain time (set_drain_time(this, 200ns)) that holds the phase open a configured delay after the count reaches zero — long enough for the pipeline to drain and the scoreboard to finish. Now the phase stays open through the drain, the final responses are checked, and the bug no longer escapes. The general lesson, and the chapter's thesis: drop the objection only after the work truly completes — not when the last stimulus is sent, but after its effects have drained (propagated through the DUT, been observed, been checked) — because there's latency between sending and checking, and dropping too early ends the phase with in-flight work unchecked, a silent false pass that hides bugs in the final transactions; wait for the drain, or set a drain time. Clock out only when your work — including its trailing results — is truly finished, not the moment you fire off the last request.
The tell is a pass where the last transactions are unchecked. Diagnose drop-too-early:
- Compare sent vs checked counts. If the scoreboard checked fewer transactions than were sent, the final ones were cut off by an early drop.
- Check what the drop follows. A drop right after seq.start() returns drops at last-stimulus-sent, before responses drain.
- Account for DUT latency. If the DUT has pipeline latency, the responses to the last stimulus arrive after it's sent; the drop must wait for them.
- Inject a bug in the final transactions. If a deliberate bug in the last transactions isn't caught, the phase is ending before they're checked.
Drop only after the work truly drains:
- Wait for the last response to be checked. Drop after the pipeline drains and the scoreboard finishes, not when the last stimulus is sent.
- Set a drain time for trailing latency.
set_drain_timeholds the phase open a configured delay after the count reaches zero, covering the drain window. - Balance every raise on every path. Ensure the drop executes on all paths (the auto-objection helps), so the phase doesn't hang.
- Test with an injected final-transaction bug. Confirm a bug in the last transactions is caught — proof the drain is long enough.
The one-sentence lesson: drop the objection only after the work truly completes — not when the last stimulus is sent, but after its effects have drained (propagated through the DUT, been observed, been checked) — using a drain time for trailing latency; dropping too early ends the phase with in-flight work unchecked, a silent false pass that hides bugs in the final transactions.
Common Mistakes
- Dropping when the last stimulus is sent. The work isn't done until the responses drain and are checked; drop after the drain, or set a drain time.
- Skipping the drop on some path. An early return or exception that bypasses the drop leaves the count nonzero and hangs the phase; balance on every path (the auto-objection helps).
- Dropping more than was raised. The count can't go below zero; an excess drop is an error. Match drops to raises exactly, including count.
- No drain time despite DUT latency. If responses arrive after the last stimulus, a drain time (or an explicit wait) is needed, or the last work goes unchecked.
- Assuming seq.start() returning means the work is done. It returns when the last item is sent, not when its effects are checked; wait for the drain.
- Manual raise/drop where the auto-objection fits. The auto-objection balances on all paths; manual drops are easy to skip on an exit path.
Senior Design Review Notes
Interview Insights
drop_objection releases a component's objection and decrements the phase's objection count; when the total returns to zero, the phase may end, after any drain time. Its signature mirrors the raise: phase.drop_objection(this, description, count), with this the component, description an optional string for tracing, and count an optional amount defaulting to one. Two disciplines govern when and how to call it. The first is balance: every raise must be matched by drops totaling the same count, on every code path. A missing drop leaves the count nonzero, so the phase never ends and the simulation hangs. An excess drop drives the count below zero, which is an error. So the drop must execute on all paths, including early returns and exceptions, which is why the automatic phase objection is safer than manual raise and drop. The second discipline is timing: drop only after the work truly completes. Crucially, that is not when the last stimulus is sent. There's latency between sending a transaction and its effects being fully observed and checked — the DUT's pipeline, the response path, the scoreboard's comparison. If you drop the instant the last stimulus leaves, the count reaches zero while the last responses are still in flight, the phase ends, and those final transactions are never checked. So you drop after the effects drain — after the last response is observed and checked — or you set a drain time, which holds the phase open a configured delay after the count reaches zero, covering the drain window automatically. The mental model is clocking out: you sign out only when your work is truly finished, and a grace period after the last clock-out keeps the doors open for trailing tasks. So drop_objection decrements toward ending, but you call it only after the work, including its trailing effects, is done, and you make sure every raise is matched.
Dropping too early is dangerous because it's silent — the test passes while skipping the final checks — whereas never dropping is loud, the simulation just hangs. These are the two opposite failure modes of the drop. Never dropping happens when a raise has no matching drop on some path, so the count stays nonzero and the phase never ends; the simulation hangs, which you notice immediately because the run doesn't complete. It's a bug, but an obvious one. Dropping too early is the opposite and far worse. The drop happens before the work's effects have drained — typically right after the last stimulus is sent, before the DUT's responses have propagated and been checked. The count reaches zero while work is still in flight, the phase ends, and the last transactions go unchecked. The test completes and passes, but it quietly lost checking on exactly the final transactions. If a bug lives in those final transactions, it escapes, and you get a green result on a broken design. The danger is the silence: there's no hang, no error, nothing to alert you — the test just passes, having skipped the last checks. This is why drop timing gets such careful attention: the loud failure you'll catch, but the silent one can ship a bug. The way to catch it is to compare the count of transactions sent against the count checked — if the scoreboard checked fewer, the final ones were cut off — and to inject a deliberate bug in the last transactions and confirm it's caught. The fix is to drop only after the work truly drains, by waiting for the last response to be checked or by setting a drain time. So never dropping hangs loudly, dropping too early passes silently, and the silent one is the one to fear, which is why the drop's discipline emphasizes both balancing it and timing it after the drain.
Drain time is a configured delay that holds the phase open after the objection count reaches zero, before the phase actually ends, to let in-flight work complete. You set it with set_drain_time on the phase's objection, for example phase_done.set_drain_time(this, 200ns). With it, the phase ends only when two conditions hold: the count is zero, and the drain time has elapsed since the count reached zero. So after the last objection is dropped, the phase waits the drain time, during which trailing work — the last responses propagating through the DUT, the scoreboard finishing its checks — can complete before the phase closes. You need drain time whenever there's latency between the last stimulus and its effects being fully observed and checked, which is common because real DUTs have pipeline latency. The last transaction is sent, but its response arrives several cycles later, and the scoreboard checks it after that. If you dropped the objection when the stimulus was sent and there were no drain time, the count would hit zero immediately and the phase would end before the response arrived, leaving the last transactions unchecked. The drain time covers that gap automatically: it keeps the phase alive long enough for the pipeline to drain. It's a declarative alternative to explicitly waiting for the last response before dropping — instead of coding the wait, you let the drain time hold the phase open. The right drain time is long enough to cover the worst-case latency from last stimulus to last check; too short and you still cut off some work, too long and you waste a little simulation time idling. You verify it's long enough by injecting a bug in the final transactions and confirming it's caught. So drain time is the grace period after the count reaches zero, and you use it whenever responses or checks trail the last stimulus, which is essentially whenever the DUT has latency.
Because the phase ends only when the count returns to zero, so if any path raises without a matching drop, the count stays nonzero and the phase hangs forever. The objection count is incremented by each raise and decremented by each drop, and the phase stays alive while the count is positive. For the phase to end, every raise must be matched by drops totaling the same count. The subtlety is that this must hold on every code path through the component, not just the normal one. If a task raises an objection and then takes an early return — say an if-condition that returns before reaching the drop — that path raised without dropping, so the count is stuck above zero and the phase never ends. The same happens with an exception that unwinds past the drop, or a thread that's disabled before it reaches the drop. Each of these is a path where the raise isn't balanced, and any one of them hangs the phase. This is hard to get right manually, because you have to ensure the drop executes on every possible exit, which is easy to miss when there are multiple return points or error paths. That's a major reason the automatic phase objection is preferred for sequences: it brackets the body and handles the raise and drop around it, dropping on all exits automatically, so you can't accidentally skip the drop on some path. When you do raise and drop manually, you need to be careful to drop on every path — often by structuring the code so the drop is unconditional after the work, or by using a mechanism that guarantees the drop runs. The opposite imbalance, dropping more than you raised, is also an error, because the count can't go below zero, but the common and more insidious case is the missing drop that hangs the phase. So balance on every path is essential, and the practical guidance is to prefer the auto-objection and, when manual, to ensure the drop is unconditional and unskippable.
The work is done when the effects of the last stimulus have fully propagated, been observed, and been checked — not when the last stimulus is sent. This distinction is the heart of correct drop timing. Sending the last transaction is just the start of its journey: it has to propagate through the DUT's pipeline, the response has to arrive, the monitor has to observe that response, and the scoreboard has to check it. Only when that last check completes is the work truly finished. The trap is that seq.start() returns when the last item is sent, not when its effects are checked, so it's tempting to drop the objection right after seq.start() returns. But at that moment, the last responses are still in flight — the DUT is still processing, the responses haven't arrived, the scoreboard hasn't checked them. Dropping there ends the phase with that work unfinished, and the final transactions go unchecked. So for the purpose of dropping, you must wait until the last response has been observed and checked. You can do this explicitly, by waiting on a condition that signals all responses checked before dropping, or declaratively, by setting a drain time that holds the phase open long enough after the count reaches zero for the pipeline to drain and the scoreboard to finish. The general principle is that completion lags the last stimulus by the system's latency, so the drop must lag too. A good way to calibrate this is to think about the worst-case path from sending a transaction to checking its result, and ensure the drop or drain time covers it. And you validate it by injecting a bug into the final transactions and confirming it's caught — if it isn't, the phase is ending before those transactions are checked, meaning the drop is too early or the drain too short. So the work is done at last-checked, not last-sent, and the drop belongs there.
Exercises
- Read the signature. Explain each argument of
drop_objection(this, description, count)and how it relates to the raise. - Name the two failures. Describe the never-dropped and dropped-too-early failures, their symptoms, and which is more dangerous.
- Time the drop. Given a DUT with pipeline latency, explain where the drop belongs and how a drain time helps.
- Balance the paths. Given a task that raises then early-returns on a condition, explain the hang and fix it.
Summary
drop_objectionreleases a component's objection and decrements the phase's count; when the total returns to zero, the phase may end (after any drain time). Signature:phase.drop_objection(this, description, count).- Balance: every raise must be matched by drops totaling the same count, on every path — a missing drop hangs the phase forever (count never zero); an excess drop is an error (count can't go below zero). The auto-objection balances on all paths safely.
- Timing: drop only after the work truly completes — not when the last stimulus is sent, but after its effects drain (propagate through the DUT, are observed, are checked) — because completion lags the last stimulus by the system's latency.
- Drain time (
set_drain_time) holds the phase open a configured delay after the count reaches zero, covering the drain window so trailing responses are checked — the phase ends at(count == 0)AND(drain elapsed). - The durable rule of thumb: drop the objection only after the work truly drains — wait for the last response to be observed and checked (or set a drain time for the latency), and balance every raise with a drop on every path (prefer the auto-objection) — because dropping too early ends the phase with the final transactions unchecked (a silent false pass that hides bugs), and a skipped drop hangs the phase forever.
Next — Phase Completion: how objections actually end a phase — the full picture of phase completion: the objection count reaching zero, the drain time elapsing, the phase-ready-to-end and phase-ended callbacks, and how UVM advances from one phase to the next once the objections clear.