UVM
Common Objection Bugs
The recurring objection mistakes catalogued by symptom — the two failure poles (phase ends too early vs phase never ends) plus the over-drop error, each bug mapped to its symptom and fix so you can recognize and prevent it.
Objections · Module 17 · Page 17.5
The Engineering Problem
The previous chapters covered the objection mechanism — raise (Module 17.2), drop (17.3), completion (17.4) — each with one characteristic bug. This chapter gathers the recurring objection mistakes into a catalog — a recognition guide. Objection bugs are common, costly (each can burn a debug session), and — crucially — they fall into a small number of patterns with recognizable symptoms. The problem an engineer faces is diagnostic: a test ends in zero time, or hangs forever, or passes but missed its final transactions — which objection bug is it, and what's the fix? Without a map, each is a fresh investigation. The problem this chapter solves is recognition: organizing the objection bugs by their symptoms so that, given a failure, you can name the bug and apply the fix — fast.
The objection bugs fall into two opposite poles, plus a distinct error. Pole 1 — the phase ends too early (work lost): never raised (count zero from the start → zero-time test), raised too late (zero-count window → intermittent premature end), dropped too early (final work in flight → unchecked, silent false pass), and missing/too-short drain (trailing work cut off). Pole 2 — the phase never ends (a hang): never dropped or drop skipped on a path (count stuck positive → simulation hangs), unbalanced under-drop (count never reaches zero), and re-raise forever in phase_ready_to_end (extension never settles → hang or max-iterations error). The over-drop error: dropping more than was raised drives the count below zero — an immediate error. Recognizing which pole (and which bug) a symptom belongs to is most of the diagnosis. This chapter is the catalog: the two poles, each bug's symptom and fix, the diagnostic from symptom to bug, and the prevention principles that avoid whole classes.
What are the recurring objection bugs, and how do they organize into the two failure poles — the phase ends too early (work lost) and the phase never ends (a hang), plus the over-drop error — so that a symptom points straight to the bug and its fix?
Motivation — why a catalog matters
Cataloguing the objection bugs is worth it because they are common, patterned, and recognizable. The reasons:
- Objection bugs are common and costly. Every testbench raises and drops objections, and every one of the mistakes is easy to make. Each can cost a debug session — a hang that stalls a regression, a false pass that ships a bug.
- They fall into a small number of patterns. Despite the variety of causes, the symptoms cluster into two poles — ends too early and never ends — plus the over-drop error. Knowing the patterns turns a fresh investigation into a lookup.
- The symptom points to the pole. A zero-time or under-checked test → ends too early; a hang → never ends. Recognizing the pole from the symptom narrows the bug immediately.
- The fixes are symmetric and learnable. Ends too early → fix the timing (raise first, drop after drain); never ends → fix the balance (drop on every path). A small set of principles prevents whole classes.
- Prevention beats debugging. Most objection bugs are prevented by a few habits — raise synchronously first, drop after the work drains, balance on every path, prefer the auto-objection. The catalog makes the habits explicit.
The motivation, in one line: objection bugs are common and costly but fall into a small number of recognizable patterns — two poles (ends too early, never ends) plus the over-drop error — so a catalog organized by symptom turns each failure from a fresh investigation into a fast recognition (name the pole, name the bug, apply the fix), and the prevention principles avoid whole classes before they cost a debug session.
Mental Model
Hold the objection bugs as two ways a meeting goes wrong — it adjourns before the work is done, or it never adjourns at all:
Every objection bug is one of two failures of the meeting: it adjourns too early (before the business is finished — someone never raised a hand, or lowered it too soon) or it never adjourns (a hand stays up forever — someone forgot to lower theirs, or kept raising it). Plus one error: lowering more hands than were raised. Knowing which failure you're seeing tells you where to look. Picture the meeting (the phase) again. There are exactly two ways its adjournment goes wrong. The meeting adjourns too early — the business wasn't finished, but the chair called it: no one raised a hand to hold it open (never raised), or someone lowered their hand while they still had business (dropped too early), or raised it too late to catch the chair, or the grace period was too short for the trailing work. The symptom is work left undone — and it can be quiet (the meeting ends, seemingly fine, but skipped the last items). Or the meeting never adjourns — a hand stays up forever: someone raised it and forgot to lower it (never dropped), or left the room (an exception, an early exit) without lowering it (skipped on a path), or keeps raising it on a condition that never clears (re-raise forever). The symptom is a meeting that runs forever — loud (everyone's stuck in the room), but you must find whose hand is up. And one distinct error: lowering more hands than were raised — the chair notices the count went negative and calls it out of order. Which failure you're seeing — adjourned-too-early or never-adjourns — tells you where to look: too-early → check who should have raised and when they lowered; never-adjourns → find whose hand is stuck up and why.
So the objection bugs are two ways the meeting fails: adjourns too early (work lost — never raised, dropped too early, raised too late, drain too short) and never adjourns (a hang — never dropped, skipped on a path, re-raise forever), plus the over-drop error. The symptom tells you the pole: premature end (often quiet) → check the raise/drop timing; hang (loud) → find the stuck hand. Name the failure, and you know where to look.
Visual Explanation — the two poles of objection failure
The defining picture is the taxonomy: every objection bug is ends too early (work lost) or never ends (hang), with the over-drop as a separate error.
The figure shows the taxonomy — every objection bug placed on one of two poles, plus the over-drop error. Phase ends too early (the warning-colored top pole): never raised, raised too late, dropped too early, missing/too-short drain — all make the count reach zero before the work is truly done, so work is lost (a zero-time or under-checked test, often silent). Phase never ends (the warning-colored middle pole): never dropped, drop skipped on a path, re-raise forever in phase_ready_to_end — all make the count never return to zero, so the phase hangs (the simulation never completes, loud). The over-drop error (the brand-colored bottom): dropping more than was raised drives the count below zero → an immediate error. The crucial reading is the opposite symptoms of the two poles: ends-too-early → count zero too soon → work lost (and often quiet — the test ends and may pass); never-ends → count stuck positive → hang (and loud — the run stalls). Recognizing the pole is most of the diagnosis: see a hang, and you know it's the never-ends pole — a missing drop somewhere; see a zero-time or final-work-unchecked test, and you know it's the ends-too-early pole — a raise/drop timing problem. The over-drop is distinct — not a too-early/never-ends failure but a count-below-zero error (caught immediately). The diagram is the map of objection failure: two poles with opposite symptoms, plus the over-drop error — and the first diagnostic step is always to place the symptom on this map, which narrows the bug to a handful.
RTL / Simulation Perspective — the bugs and their fixes in code
In code, each bug has a recognizable shape and a direct fix. The code catalogs the most common ones side by side.
// === ENDS TOO EARLY ===
// (1) NEVER RAISED → zero-time test: FIX: raise before the work
task run_phase(uvm_phase phase);
seq.start(...); // ✗ no raise → count 0 → phase ends immediately
phase.raise_objection(this); // ✓ raise FIRST (synchronously, before the work)
seq.start(...); phase.drop_objection(this);
endtask
// (2) RAISED TOO LATE → intermittent end: FIX: raise before ANY time advance (no @/#/fork first)
// (3) DROPPED TOO EARLY → final work unchecked: FIX: drop after the work DRAINS (or set drain time)
// === NEVER ENDS (hang) ===
// (4) NEVER DROPPED / SKIPPED ON A PATH → hang: FIX: drop on EVERY path (prefer auto-objection)
task buggy(uvm_phase phase);
phase.raise_objection(this);
if (err) return; // ✗ early return SKIPS the drop → count stuck > 0 → HANG
phase.drop_objection(this);
endtask
// (5) RE-RAISE FOREVER in phase_ready_to_end → hang / max-iterations error: FIX: extend only on resolvable work
// === OVER-DROP (error) ===
// (6) DROP > RAISE → count below zero (error): FIX: match drops to raises exactly (incl. count)
phase.raise_objection(this);
phase.drop_objection(this);
phase.drop_objection(this); // ✗ second drop with no matching raise → count < 0 → ERROR
// === THE PREVENTION: the auto-objection avoids most of these (balances on all paths, times correctly) ===
function new(...); super.new(...); set_automatic_phase_objection(1); endfunctionThe code catalogs the bugs by their shape and fix. Ends too early: (1) never raised — seq.start() with no prior raise → the phase ends immediately; fix: raise first. (2) raised too late — a raise after a time advance → intermittent end; fix: raise before any @/#/fork. (3) dropped too early — drop before the work drains → final work unchecked; fix: drop after the drain (or set a drain time). Never ends: (4) never dropped / skipped on a path — an if (err) return; that skips the drop → count stuck → hang; fix: drop on every path. (5) re-raise forever in phase_ready_to_end → hang / max-iterations error; fix: extend only on resolvable work. Over-drop: (6) drop > raise — a second drop with no matching raise → count below zero → error; fix: match drops to raises exactly. And the prevention: the auto-objection (set_automatic_phase_objection(1)) avoids most of these — it balances on all paths (no skipped drop), times the raise/drop correctly (around the body), and eliminates the manual timing/balance mistakes. The shape to carry: each bug has a recognizable code shape (no-raise, raise-after-delay, drop-before-drain, return-skipping-drop, extra-drop) and a direct fix (raise-first, raise-synchronously, drop-after-drain, drop-on-every-path, match-counts) — and the auto-objection is the single habit that prevents the most of them.
Verification Perspective — the diagnostic from symptom to bug
Given a symptom, the catalog narrows to the bug by a short decision path. Seeing the diagnostic is seeing how to use the catalog.
The figure shows the diagnostic — symptom to bug in a short path. Hang (never completes) → never-ends pole (step 1): the phase never ends, so the count is stuck positive — look for a drop skipped on a path (an early return, an exception), an unbalanced under-drop, or a re-raise that never settles in phase_ready_to_end. Zero-time, no stimulus → never raised (step 2): nothing held the phase open; it ended immediately — raise before the work. Intermittent premature end → raised too late (step 3): a zero-count window before a deferred raise — raise synchronously, first. Passes but final work unchecked → dropped early / drain short (step 4): the drop or drain didn't cover the work's completion — drop after drain, or use phase_ready_to_end (Module 17.4). The verification insight is that the symptom is highly diagnostic: each distinct symptom maps to one or two candidate bugs, so the diagnosis is fast. A hang immediately says never-ends pole (a missing drop) — you don't guess among all the bugs, you look for a skipped drop. A zero-time test immediately says never raised. An intermittent premature end says raised too late (the intermittence is the tell — it's scheduling-dependent). A pass-but-final-unchecked says dropped early or drain short (the silent failure — found by comparing sent vs checked, or an injected final-transaction bug). The key distinctions: hang vs premature end (the two poles); within premature end, zero-time (never raised) vs intermittent (raised late) vs passes-but-under-checked (dropped early). The figure is the decision tree: start at the symptom, and two or three questions land on the bug — which is the whole value of the catalog: a symptom you'd otherwise investigate from scratch becomes a quick lookup to a named bug with a known fix.
Runtime / Execution Flow — the prevention principles
Most objection bugs are prevented by a small set of habits. The flow shows the principles that avoid whole classes.
The figure shows the prevention principles — a small set of habits that avoid whole classes. Raise synchronously, first (before any time advance) avoids never-raised and raised-too-late — the premature-start bugs. Drop after the work drains (set a drain time for fixed latency, or use phase_ready_to_end for state-dependent completion) avoids dropped-too-early and drain-short — the final-work-loss bugs. Balance every raise with a drop on every path (drop on all exits) avoids never-dropped and skipped-on-a-path — the hangs. And prefer the auto-objection (set_automatic_phase_objection(1)), which does the raise, drop, balance, and timing for you, avoids most of these at once (the success-colored principle, with a bus edge to "most bugs prevented"). The verification insight is that the objection bugs are not a large, unstructured set of traps — they're prevented by four habits, three of which map directly to the three failure-causing decisions (when to raise, when to drop, whether to balance), and the fourth (the auto-objection) subsumes the others for the common case. So the practical takeaway of the catalog is not "memorize N bugs" but "adopt four habits": raise first, drop after drain, balance on every path, prefer the auto-objection. The first three prevent the manual-objection bugs; the fourth makes the manual objections unnecessary for most sequences (where it handles all three correctly). The figure is the prevention map: each habit closes off a class of bug, and together they close off almost all — so most objection bugs are never written if the habits are in place. The catalog's real lesson is the prevention: four habits, most bugs gone.
Waveform Perspective — the signatures of the two poles
The two poles have opposite, recognizable signatures on a timeline: ends-too-early shows the count reaching zero too soon; never-ends shows the count stuck positive. The waveform contrasts them.
The two poles' signatures: count zero too soon (ends early) vs count stuck positive (hang)
12 cyclesThe waveform shows the opposite signatures of the two poles. Top (ends too early): obj_count_early reaches zero at cycle 2, while work_in_flight is still high — the phase ends with work still going, so the work is lost. (The work actually finishes at cycle 6 — but the phase already ended at cycle 2, before it.) Bottom (never ends): obj_count_hang goes positive and stays positive forever — it never returns to zero, so the phase hangs and the simulation never completes. The crucial reading is the two distinct signatures: the ends-too-early signature is count-zero-while-work-remains (the count drops to zero before work_in_flight clears — a gap where the phase ends but the work continues); the never-ends signature is count-never-zero (the count stays positive indefinitely). The picture to carry is that the two poles are visually opposite on the count trace: ends-too-early → the count falls to zero too soon (before the work is done); never-ends → the count never falls to zero (stuck up). Recognizing which signature you see identifies the pole — and which pole narrows the bug (Figure 2). Reading an objection-bug waveform — does the count reach zero while work remains (ends early), or never reach zero at all (hang)? — is the first diagnostic move. The count-zero-too-soon vs count-never-zero contrast is the visual essence of the two poles: one ends the phase before the work is done; the other never ends it — and the count trace shows you which, at a glance.
DebugLab — the regression that hung on a skipped drop
A phase hanging forever because an early-return path skipped the drop
A test hung — the run never completed, timing out in the regression after the wall-clock limit. The simulation log showed the run_phase active but no further progress: no error, no end, just a frozen phase. The test had raised and dropped objections correctly on its normal path — and most tests passed — but this test (and only on certain stimulus) hung indefinitely. The objection count was stuck above zero, and nothing was dropping it.
A path through the code — an early return on an error condition — raised an objection but returned before dropping it, leaving the count stuck positive forever:
✗ a path RAISES then RETURNS without dropping → count stuck > 0 → HANG:
task run_phase(uvm_phase phase);
phase.raise_objection(this);
status = do_setup();
if (status != OK) return; // ✗ early return SKIPS the drop → objection never dropped
seq.start(...);
phase.drop_objection(this); // only reached on the SUCCESS path
endtask
// on the error path (status != OK), the raise is never matched → count stuck → phase HANGS forever
✓ DROP on EVERY path (or use the auto-objection, which drops on all exits):
task run_phase(uvm_phase phase);
phase.raise_objection(this);
status = do_setup();
if (status == OK) seq.start(...); // do the work only on success...
phase.drop_objection(this); // ...but ALWAYS drop (every path reaches here)
endtask
// OR: set_automatic_phase_objection(1) in a sequence → raise/drop bracket the body on ALL exitsThis is the skipped-drop bug — the canonical hang, and the most common cause of a phase that never ends. The phase ends only when the count returns to zero, so every raise must be matched by a drop on every path. Here, the run_phase raised an objection, then early-returned on an error condition (if (status != OK) return;) — before reaching the drop_objection, which sat only on the success path. So on the error path, the raise was never matched: the count stayed above zero forever, and the phase hung. The tell is exactly this: a hang (the run never completes) that occurs only on certain stimulus (the stimulus that triggers the error path) — the normal path drops correctly, so most tests pass, and only the error-triggering test hangs. The hang is loud (the regression stalls) but finding the stuck objection requires knowing it's a skipped drop (and, in the next chapter, tracing the objection). The fix is to drop on every path: restructure so the drop_objection is reached on all exits — do the work conditionally (if (status == OK) seq.start(...)) but always reach the drop — or use the auto-objection (set_automatic_phase_objection(1)), which brackets the body and drops on all exits (including early returns and exceptions) automatically. The general lesson, and the chapter's thesis: a hang (the phase never ends) is almost always a never-dropped objection — a drop skipped on some path (an early return, an exception, a disabled thread) that leaves the count stuck positive; ensure the drop executes on every path (restructure so it's unconditional, or prefer the auto-objection that drops on all exits). When you see a hang, look for the skipped drop — and the next chapter shows how to trace it. A stuck hand is a hand someone forgot to lower on their way out — make sure every exit lowers the hand.
The tell is a hang, often only on certain stimulus. Diagnose skipped drops:
- Confirm the pole: hang → never-ends. A run that never completes means the count is stuck positive — a missing drop.
- Look for early returns and exceptions between raise and drop. Any path that exits before the drop skips it.
- Check which stimulus triggers it. A hang only on certain stimulus points at an error or conditional path that skips the drop.
- Trace the objection to the stuck component. Objection tracing (Module 17.6) shows which objection is outstanding and where.
Drop on every path:
- Make the drop unconditional. Structure the code so the drop_objection is reached on all paths, doing the work conditionally instead.
- Prefer the auto-objection.
set_automatic_phase_objection(1)drops on all exits, including early returns and exceptions, eliminating skipped drops. - Avoid raising before code that can exit early. If a path can return or throw before the drop, the raise is unbalanced.
- Test error and corner paths. Exercise the paths that trigger early returns to confirm they don't hang.
The one-sentence lesson: a phase that hangs is almost always a never-dropped objection — a drop skipped on some path (an early return, an exception, a disabled thread) leaving the count stuck positive — so ensure the drop executes on every path (make it unconditional, or prefer the auto-objection that drops on all exits).
Common Mistakes
- No raise. The phase ends immediately (zero-time test, no stimulus); raise before the work.
- Raise after time advances. A zero-count window causes intermittent premature ending; raise synchronously, first.
- Drop before the work drains. The final transactions go unchecked (silent false pass); drop after the drain or use phase_ready_to_end.
- Drop skipped on a path. An early return or exception that skips the drop hangs the phase; drop on every path (auto-objection).
- Over-drop. Dropping more than raised drives the count below zero — an error; match drops to raises exactly.
- Re-raise forever in phase_ready_to_end. An extension whose condition never resolves hangs or hits max iterations; extend only on resolvable work.
Senior Design Review Notes
Interview Insights
Objection bugs fall into two opposite poles plus a distinct error. The first pole is the phase ending too early, where the count reaches zero before the work is truly done, so work is lost. This includes never raising an objection, which makes the phase end immediately in zero time with no stimulus; raising too late, after time has advanced, which leaves a zero-count window and causes intermittent premature ending; dropping too early, before the work's effects have drained and been checked, which leaves the final transactions unchecked in a silent false pass; and a missing or too-short drain time, which cuts off trailing work. The second pole is the phase never ending, a hang, where the count never returns to zero. This includes never dropping an objection or skipping the drop on some path, like an early return or exception, which leaves the count stuck positive; an unbalanced under-drop; and re-raising forever in phase_ready_to_end on a condition that never resolves. The distinct error is the over-drop: dropping more than was raised drives the count below zero, which UVM flags as an immediate error. The key insight is that the two poles have opposite, recognizable symptoms. Ends-too-early shows as a zero-time or under-checked test, and it's often silent because the test ends and may even pass. Never-ends shows as a hang, which is loud because the run stalls, but you have to find which objection is stuck. So recognizing the pole from the symptom is most of the diagnosis: a hang means a missing drop somewhere, while a premature end means a raise or drop timing problem. The fixes are symmetric: raise first and synchronously, drop after the work drains, and balance every raise with a drop on every path, preferably using the auto-objection.
You start with the symptom and let it narrow the bug to one or two candidates. The first question is which pole: does the phase hang, or does it end too early? If it hangs — the run never completes — it's the never-ends pole, meaning the count is stuck positive, so you look for a drop skipped on some path, an unbalanced under-drop, or a re-raise in phase_ready_to_end that never settles. If it ends too early, you distinguish among the premature-end bugs by the specific symptom. A test that ends in zero time with no stimulus at all is never raised — nothing held the phase open. A test that ends prematurely but intermittently, varying run to run, is raised too late — there was a zero-count window before a deferred raise, and whether the phase caught it depends on scheduling, which is why it's intermittent. A test that passes but whose final transactions are unchecked is dropped too early or has a too-short drain — the drop or drain didn't cover the work's completion, and you find it by comparing the count of transactions sent against checked, or by injecting a bug in the final transactions and seeing if it's caught. And if you see a count-below-zero error, that's an over-drop. So the diagnosis is a short decision path: hang versus premature end first, then within premature end, zero-time versus intermittent versus passes-but-under-checked. Each distinct symptom maps to essentially one bug, so once you recognize the symptom, you know the bug and the fix. This is the whole value of cataloguing the bugs — a symptom you'd otherwise investigate from scratch becomes a quick lookup. The silent ones, like dropped-too-early, are the ones to watch for, because the test passes, so you catch them by actively comparing sent versus checked counts or injecting final-transaction bugs rather than waiting for a visible failure.
Because the phase ends only when the objection count returns to zero, so if the phase never ends, the count is stuck positive, which means some raise was never matched by a drop. The most common cause is a drop skipped on some code path. A task raises an objection and then, on some path, exits before reaching the drop — an early return on an error condition, an exception that unwinds past the drop, or a disabled thread. On that path, the raise is unbalanced, the count stays above zero, and the phase hangs forever. A telltale sign is that the hang occurs only on certain stimulus — the stimulus that triggers the error or conditional path — while the normal path drops correctly and most tests pass. So it's the error-triggering test that hangs. The fix is to ensure the drop executes on every path. You restructure the code so the drop_objection is unconditional and reached on all exits, doing the work conditionally instead of returning early. For example, instead of raising, then returning early on an error, then dropping only on success, you raise, do the work only on success, but always fall through to the drop. The better fix for sequences is to use the automatic phase objection, set_automatic_phase_objection(1), which brackets the body and drops on all exits automatically, including early returns and exceptions, so you can't skip the drop. This eliminates the whole class of skipped-drop hangs. To find which objection is stuck when you do hit a hang, you use objection tracing, which shows which objections are outstanding and where, and that's the subject of the debugging chapter. So the rule is: a hang means a missing drop, you look for paths between the raise and drop that can exit early, and you fix it by making the drop unconditional or by using the auto-objection that guarantees it on every path.
Dropped too early is the most dangerous, because it's silent — the test passes while skipping the final checks, so a real bug can ship undetected. Compare the failure modes. The never-ends bugs, like a skipped drop, cause a hang, which is loud: the run stalls, the regression times out, and you immediately know something is wrong. You'll investigate and fix it. The never-raised bug causes a zero-time test with no stimulus, which is also fairly visible — the test runs in no time and does nothing, which stands out. But dropped too early is different. The objection is dropped when the last stimulus is sent, before the DUT's responses have drained and been checked. The count reaches zero, the phase ends, and the final transactions go unchecked. The test completes and passes — there's no hang, no error, nothing visibly wrong — 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. That's the danger: a silent false pass that can let a bug through to silicon. The related too-short-drain bug under variable latency is similarly dangerous and intermittent — fine on typical runs, broken on slow ones, with the same silent unchecked-final-transactions symptom. Because these are silent, you can't rely on noticing a failure; you have to actively guard against them, by comparing the count of transactions sent against checked to confirm none were dropped, by injecting a deliberate bug in the final transactions and confirming it's caught, and by using phase_ready_to_end to extend until outstanding work is truly resolved rather than guessing a drain time. So while the hangs are more annoying day to day, the dropped-too-early family is the most dangerous because it passes silently, which is why drop timing and the drain mechanisms get such careful attention.
Four habits prevent most objection bugs: raise synchronously and first, drop after the work drains, balance every raise with a drop on every path, and prefer the automatic phase objection. The first three map directly to the three failure-causing decisions. Raising synchronously, before any time advance — before any clock edge, delay, or fork — makes the count positive from time zero and prevents both never-raised and raised-too-late, the premature-start bugs. Dropping after the work drains — waiting for the last response to be observed and checked, using a drain time for fixed latency or phase_ready_to_end for state-dependent completion — prevents dropped-too-early and too-short-drain, the final-work-loss bugs. Balancing every raise with a drop on every path — making the drop unconditional and reached on all exits, including error and exception paths — prevents never-dropped and skipped-on-a-path, the hangs. The fourth habit, preferring the auto-objection via set_automatic_phase_objection(1), subsumes the others for the common case: it brackets the sequence body, raising and dropping around it, with correct timing and balance on all exits, so it avoids most of these bugs at once. The practical takeaway is that objection bugs aren't a large unstructured set of traps to memorize; they're prevented by a handful of habits. Adopt the four, and most objection bugs are simply never written. The first three protect manual objections, and the fourth makes manual objections unnecessary for most sequences, where it handles all three correctly. So when reviewing or writing objection code, the questions are: is the raise first and synchronous, does the drop follow the drain, is the drop on every path, and could the auto-objection replace the manual raise and drop? Those four habits close off the classes of objection bugs, which is the real lesson of cataloguing them — not to memorize the bugs, but to internalize the prevention.
Exercises
- Place the symptom. For each — zero-time test, hang, passes-but-final-unchecked, count-below-zero error — name the pole and the bug.
- Fix the hang. Given a task that raises then early-returns on an error, explain the hang and rewrite it to drop on every path.
- Distinguish two premature ends. Explain how to tell never-raised from raised-too-late by their symptoms.
- Apply the habits. List the four prevention habits and the bug class each closes off.
Summary
- Objection bugs fall into two opposite poles plus a distinct error. Phase ends too early (work lost): never raised (zero-time), raised too late (intermittent end), dropped too early (final work unchecked — silent), missing/too-short drain. Phase never ends (hang): never dropped or drop skipped on a path, unbalanced under-drop, re-raise forever. Over-drop: count below zero → error.
- The poles have opposite, recognizable symptoms: ends too early → count zero while work remains (often silent); never ends → count never zero (a loud hang) — so recognizing the pole is most of the diagnosis.
- The symptom narrows the bug: hang → skipped drop; zero-time → never raised; intermittent end → raised too late; passes-but-final-unchecked → dropped early / drain short; count < 0 error → over-drop.
- The most dangerous is dropped too early — silent (the test passes while skipping the final checks, hiding a bug); the loudest is the hang (a missing drop on some path).
- The durable rule of thumb: recognize the objection bug from its pole — a hang is a never-dropped objection (a drop skipped on some path), a zero-time or under-checked test is a raise/drop timing problem — and prevent whole classes with four habits: raise synchronously first, drop after the work drains, balance every raise with a drop on every path, and prefer the auto-objection, which does all of that for you.
Next — Debugging Objections: the module closes with the tools — how to actually find a stuck or mistimed objection: objection tracing (+UVM_OBJECTION_TRACE), the objection display and callbacks, reading the hierarchical objection count, and a systematic method for locating which objection, in which component, is holding a phase open or letting it end too soon.