UVM
Phase Issues
Applying the debugging methodology to phasing and objections — why the test ends in zero time with no stimulus, or hangs forever at the end of a phase, or does work in the wrong order: the objection count is a reference counter that keeps a phase alive while non-zero and ends it at zero, so no objection raised ends the phase immediately while an objection raised but never dropped keeps it alive forever — two opposite symptoms from one counter; plus time-consuming work in a function phase; how to observe with the objection trace and fix each so phases sequence and complete correctly.
UVM Debugging · Module 27 · Page 27.6
The Engineering Problem
The test ends in zero simulation time — passing, but having driven nothing. Or the opposite: the test hangs forever, never completing, stuck at the end of a phase that won't close. Or components do work in the wrong order, or a function phase throws a time-consuming-work error. These are the phasing's characteristic failures (from 27.1's map: the test ends early → phasing/objections), and the two headline symptoms — zero-time finish and infinite hang — are opposite manifestations of the same mechanism: the objection count. A UVM time-consuming phase (like run_phase) stays alive while its objection count is non-zero and ends the moment the count reaches zero. And the count starts at zero. So if no component raises an objection, the phase ends immediately — before any stimulus runs — and the test finishes in zero time (the zero-time finish). Conversely, if a component raises an objection but never drops it, the count never returns to zero, the phase never ends, and the test hangs forever (the infinite hang). One counter, two directions of imbalance, two opposite symptoms. The trap with the zero-time finish is especially insidious: the test passes green (no checks failed, because nothing ran) — false confidence, a verification that verified nothing. The problem this chapter solves is phase issues: applying the methodology to phasing and objections — reading the symptom (zero-time finish vs infinite hang vs wrong-order/wrong-phase work), observing with the objection trace, and confirming and fixing each so phases sequence and complete correctly.
Phase issues are failures in UVM's phasing and objection mechanism — a phase ending too early, never ending, or work happening in the wrong phase — rooted in the objection count that keeps a phase alive while non-zero. The root causes: no objection raised (the run-phase objection count is zero from the start, so the phase ends immediately — zero-time finish, no stimulus, false green); an objection raised but never dropped (the count never returns to zero, so the phase never ends — infinite hang); a raise/drop imbalance (raised twice dropped once → hang; dropped more than raised → error or premature end); and work in the wrong phase (time-consuming work in a function phase like build/connect — illegal; or expecting children before they're built; or raising the objection on the wrong phase handle). The crucial mechanism: the objection count is a reference counter — phase alive while count > 0, ends at count == 0, starting at 0 — so too few objections (none) ends the phase immediately and too many (never dropped) keeps it alive forever — the two opposite symptoms are the two directions of imbalance in one counter. The methodology applied: the symptom is zero-time finish, infinite hang, or wrong-order/wrong-phase work; localize to phasing; observe with +UVM_OBJECTION_TRACE (every raise and drop with the component and the running count — no raise = immediate end; a raise with no drop = the held objection, which UVM names at the phase-end timeout); confirm the direction of imbalance; and fix (raise an objection to keep the phase alive and drop it when done, balance raise/drop, do time-consuming work only in task phases). This chapter is phase issues: the objection count, the two-directional failure, the observation, and the fixes.
Why does the test end in zero time with no stimulus, or hang forever at the end of a phase — and how are these opposite symptoms the two directions of imbalance in the objection count (none raised ends the phase immediately, never dropped keeps it alive forever), observed with the objection trace and fixed by balancing raise and drop?
Motivation — why one counter produces two opposite failures
The objection count is a single reference counter, and both the zero-time finish and the infinite hang come from imbalance in it — understanding the counter explains both and their opposite fixes. The reasons:
- The count starts at zero, so the phase would end immediately by default. A time-consuming phase ends when the count is zero, and it's zero at the start — so something must raise an objection to keep the phase open. Forgetting to raise means the phase ends in zero time — the default outcome, not a crash.
- The zero-time finish passes green — false confidence. A test that ends before stimulus runs no checks, so it reports no failures and passes. A green test that ran no stimulus is worse than a red one — it ships the illusion of verification, like the silent scoreboard (27.4).
- The infinite hang is a never-dropped objection. The opposite imbalance: a raise without a matching drop leaves the count above zero forever, so the phase never ends. The test hangs — and UVM can name the component still holding the objection.
- Raise/drop must balance exactly. The counter is incremented by raise and decremented by drop; every raise needs a matching drop. A raise in a branch with the drop in an unreached branch, or a drop skipped by an early return, imbalances the count — hang or error.
- Phase order and kind are easy to confuse. Function phases (
build/connect) can't consume time; task phases (run) can. Build is top-down; connect bottom-up. Raising on the wrong phase handle keeps the wrong phase alive. Mis-placing work across phases breaks ordering.
The motivation, in one line: the objection count is a reference counter that ends the phase at zero and starts at zero — so a missing raise ends the phase immediately (zero-time finish, false green) and a missing drop keeps it alive forever (infinite hang) — two opposite symptoms from one counter's two directions of imbalance — so you observe the objection trace to see the raises, drops, and running count and determine which direction broke.
Mental Model
Hold the objection count as a building's occupancy counter — open while someone is badged in, closed when the last person leaves:
Think of a building with an automatic occupancy system. Every person badges in when they enter and badges out when they leave, and the system keeps a running count of how many people are inside. The rule for the building is simple: it stays open as long as the count is above zero — as long as at least one person is inside — and it closes automatically the instant the count reaches zero, when the last person has badged out. Now think about the two ways this goes wrong, which are opposite. First: nobody ever badges in. The count is zero from the start, so by its own rule the building closes immediately — it never really opens, because there was never anyone inside to keep it open. If you expected work to happen in that building, none did; it shut the moment it started. Second: someone badges in but never badges out. Maybe they left through a side door, maybe they forgot, maybe they're stuck — whatever the reason, the count never returns to zero, so the building never closes. It stays open indefinitely, lights on, waiting for a person who, as far as the counter knows, is still inside. And the system can tell you exactly who: it has a record of every badge-in without a matching badge-out, so it can name the person it's still waiting on. Both failures are the same counter, off in opposite directions: too few badge-ins and it closes instantly; a badge-in with no badge-out and it never closes. The discipline is that every badge-in must be paired with a badge-out, or the count never balances and the building either never opens or never closes. Think of a building with an occupancy system. Everyone badges in to enter, badges out to leave, and the system keeps a running count of people inside. The rule: it stays open while the count is above zero and closes the instant the count reaches zero. The two opposite failures: First, nobody ever badges in — the count is zero from the start, so the building closes immediately, never really opening; any work expected inside never happened. Second, someone badges in but never badges out — the count never returns to zero, so the building never closes, staying open indefinitely, waiting for a person who is still inside as far as the counter knows — and the system can name exactly who, from its record of every badge-in without a matching badge-out. Both failures are the same counter, off in opposite directions: too few badge-ins and it closes instantly; a badge-in with no badge-out and it never closes. The discipline: every badge-in must be paired with a badge-out, or the count never balances and the building either never opens or never closes.
So the objection count is a building's occupancy counter: raise_objection is badging in ("keep the phase open, I'm working"), drop_objection is badging out ("I'm done"), and the phase is the building — open (alive) while the count is above zero, closing (ending) the instant it reaches zero. The two opposite failures map exactly: no objection raised = nobody badges in = the phase closes immediately (zero-time finish — and it "passes" green because nothing ran, like a building that shut before anyone worked); objection raised but never dropped = badged in, never out = the phase never closes (infinite hang — and UVM names the component still holding the objection, like the occupancy system naming who's still inside). The discipline is the same: every raise paired with a drop, or the count never balances. Diagnose a phase failure by the occupancy counter: a zero-time finish means nobody badged in (no objection raised — keep the phase open with a raise), an infinite hang means somebody badged in and never out (an objection never dropped — UVM names the holder) — read the objection trace to see the badge-ins and badge-outs and the running count. Every raise needs a drop, or the phase never opens or never closes.
Visual Explanation — the phase failure causes
The defining picture is the causes organized by the direction of objection-count imbalance and the resulting symptom.
The figure shows the phase failure causes by objection-count direction. No objection raised → zero-time finish (warning-colored): the count is zero from the start, so the phase ends immediately — no stimulus, and the test passes green (false confidence). Raised but never dropped → infinite hang (warning-colored): the count never returns to zero, so the phase never ends — UVM names the component holding the objection. Raise/drop imbalance (default-colored): raised more than dropped hangs; dropped more than raised errors — the same counter off by a count. Work in the wrong phase (brand-colored): time-consuming work in a function phase (build/connect), or raising on the wrong phase handle, breaks phasing. The crucial reading is the symmetry of the two headline failures (both warning-colored): they are opposite directions of imbalance in the same objection count — too few (none raised → count stuck at zero → ends immediately) versus too many (raised, never dropped → count stuck above zero → never ends). This is why understanding the counter explains both: the phase ends exactly when the count is zero, so whether the count is zero too soon (no raise) or never zero (no drop) determines which failure you get. The imbalance cause is the same mechanism off by one or more (a raise without a drop trends toward the hang; a drop without a raise toward the error/early-end). The wrong-phase cause is different in kind — it's about which phase does what and what a phase may do (a function phase can't consume time) — but it's also a phasing failure. The crucial subtlety is that the zero-time finish is the more dangerous of the two headline failures: the hang is loud (the test obviously never finishes), but the zero-time finish is silent (the test passes green), so it hides — like the silent scoreboard of 27.4. The diagram is the phase failure map: no-raise (early end) vs no-drop (hang) — opposite directions of one counter — plus imbalance and wrong-phase. The two headline phase failures are opposite directions of imbalance in the objection count — none raised ends the phase immediately (zero-time, false green), never dropped keeps it alive forever (hang) — with the zero-time finish the more dangerous because it passes green.
RTL / Simulation Perspective — the objection count and the fixes
In code, the objection count is raised and dropped explicitly, and each failure is a missing or mismatched call. The example shows the correct pattern and each failure.
// === CORRECT: raise before stimulus, drop when done — keep the phase alive exactly as long as needed ===
class my_test extends uvm_test;
task run_phase(uvm_phase phase);
phase.raise_objection(this); // badge in — keep the phase open
my_seq seq = my_seq::type_id::create("seq");
seq.start(env.agent.sequencer); // stimulus runs while the objection is held
phase.drop_objection(this); // badge out — phase ends when count returns to zero
endtask
endclass
// ✗ CAUSE 1 — NO objection raised: the count is 0 from the start → phase ends in ZERO TIME
class bad_test1 extends uvm_test;
task run_phase(uvm_phase phase);
my_seq seq = my_seq::type_id::create("seq");
seq.start(env.agent.sequencer); // (no raise) → run_phase ends immediately, seq killed
endtask // → zero-time finish, no stimulus, test PASSES green (DebugLab)
endclass
// ✗ CAUSE 2 — raised but NEVER dropped: the count never returns to 0 → phase HANGS forever
class bad_test2 extends uvm_test;
task run_phase(uvm_phase phase);
phase.raise_objection(this);
my_seq seq = my_seq::type_id::create("seq");
seq.start(env.agent.sequencer);
// (forgot drop_objection) → count stays at 1 → phase never ends → test hangs
endtask
endclass
// ✗ CAUSE 3 — IMBALANCE: raise in a branch, drop skipped by an early return
// if (cond) phase.raise_objection(this); ... return; // raised but the drop after is skipped → hang
// ✗ CAUSE 4 — WRONG PHASE: time-consuming work in a function phase (illegal)
// function void build_phase(uvm_phase phase); #100; ... endfunction // functions can't consume time
// === OBSERVE: the objection trace shows every raise/drop and the running count ===
// simulate with: +UVM_OBJECTION_TRACE
// → "raised ... count=1 by uvm_test_top" / "dropped ... count=0"
// → NO raise ever = zero-time end ; a raise with no matching drop = the held objection (UVM names it)The code shows the objection count and its failures. The correct pattern: phase.raise_objection(this) before stimulus (badge in), seq.start(...) runs while the objection is held, phase.drop_objection(this) when done (badge out) — the phase ends when the count returns to zero. Cause 1 (no raise): bad_test1 starts the sequence but never raises — the count is zero from the start, so run_phase ends immediately, the sequence is killed → zero-time finish, no stimulus, passes green (the DebugLab). Cause 2 (never dropped): bad_test2 raises but forgot the drop → the count stays at 1 → the phase never ends → hang. Cause 3 (imbalance): a raise in a branch with the drop skipped by an early return → hang. Cause 4 (wrong phase): time-consuming work (#100) in a function phase (build_phase) — illegal (functions can't consume time). Observe: +UVM_OBJECTION_TRACE prints every raise and drop with the running count — no raise means zero-time end; a raise with no matching drop is the held objection, which UVM names. The shape to carry: the objection count is raised and dropped explicitly to bracket the time the phase must stay alive, and each failure is a missing or unbalanced call — no raise (ends early), no drop (hangs), imbalance (hangs/errors), wrong phase (illegal/out-of-order). Bracket time-consuming stimulus with a raise and a matching drop on every path, do time-consuming work only in task phases, and read the objection trace to see the raises, drops, and running count.
Verification Perspective — zero-time green is false confidence
The defining danger is that the zero-time finish passes green — a test that ran no stimulus reports no failures. Seeing why clarifies why you check the simulation time and stimulus count, not just the green result.
The figure shows the zero-time finish producing false confidence. With no objection raised, the run-phase objection count is zero from the start, so the phase ends immediately at time zero. The sequence never runs, so no stimulus is driven, so the scoreboard compares nothing and no check fails. The test reports no failures and passes green. But it ran no stimulus and verified nothing — a passing test that did nothing. The verification insight is the same as the silent scoreboard (27.4): a test that checked nothing and a test that checked correctly both report no failure and both pass — so the green result is indistinguishable between "verified, all correct" and "ran nothing." From the pass alone, you cannot tell whether the test did its job. This is why the zero-time finish is the dangerous half of the phasing failures: the hang is loud (the test obviously never ends, so you can't ignore it), but the zero-time finish is silent (it passes), so it hides in a green regression — the test appears present and passing, but runs nothing. The warning no-raise → default chain (ends at 0 → no stimulus → no checks) → warning green shows the failure flowing silently to a false pass. The crucial tell is the simulation time and the stimulus count: a test that finishes at time zero (or near it), or that drives zero transactions, ran nothing — regardless of the green result. So the defense is to check the sim time and the transaction/comparison count, not just the pass: a real run takes non-zero time and drives a non-zero, expected number of transactions, and a zero (or suspiciously small) value is a red flag that the phase ended too early. The crucial point is that you cannot trust a green result without confirming the test actually ran — check the sim time and stimulus count, don't trust the absence of failures. The diagram is the false-confidence mechanism: no raise → zero-time end → no stimulus → no checks → green. A zero-time finish passes green having run no stimulus — check the simulation time and transaction count, because a test that ended at time zero verified nothing regardless of the pass.
Runtime / Execution Flow — diagnosing a phase failure
At run time, diagnosing a phase failure follows the methodology, branching on the symptom direction and using the objection trace. The flow shows it.
The flow shows the symptom-direction-driven diagnostic. Read the symptom (step 1): zero-time finish (ends at ~0), infinite hang (never ends), or wrong-order/function-phase-time error. Enable the trace (step 2): +UVM_OBJECTION_TRACE prints every raise and drop with the component and the running count. Read by direction (step 3): zero-time — no raise ever issued; hang — a raise with no matching drop, UVM names the holder. Confirm → fix (step 4): raise an objection to keep the phase alive, drop it when done, or balance the unmatched call. The runtime insight is that the symptom direction immediately tells you which way the count is imbalanced, and the objection trace confirms it: a zero-time finish means the count was zero (no raise) — so you look for the missing raise; a hang means the count never reached zero (a missing drop) — so you look for the unmatched raise, which UVM helpfully names at the phase-end timeout (it reports the components still holding objections). So the objection trace is the introspection that makes the count's history visible — who raised, who dropped, when, and the running total — turning a "the test ended weird" symptom into a precise "no raise was ever issued" or "component X raised at time T and never dropped." This is the methodology (27.1) specialized: the symptom direction localizes the imbalance, the objection trace is the observation, and you confirm before fixing. The brand (symptom) → success (trace + read) → warning (fix) flow shows evidence preceding the fix. The crucial point is that the hang's holder is named for you (UVM's phase-end timeout reports outstanding objections), so the hang, while loud, is also quickly localized; the zero-time finish, being silent, requires you to notice it first (the sim time / stimulus count tell) before you even reach for the trace. The flow is the phase diagnostic: symptom direction → objection trace → no-raise (early end) or raise-without-drop (hang, holder named) → fix. Diagnose a phase failure by symptom direction and the objection trace — a zero-time finish shows no raise was issued, a hang shows a raise with no matching drop (UVM names the holder) — then balance the raise and drop.
Waveform Perspective — the phase that ended before it began
The zero-time finish has a stark shape: the run phase ends almost immediately, with no stimulus. The waveform shows it.
The run phase ends almost immediately with no stimulus — no objection was raised
12 cyclesThe waveform shows the zero-time finish. The run phase begins, but because no objection was raised, the objection count is zero from the start (obj_count flat at 0), so the phase ends almost immediately (phase_active is high only momentarily, then drops). No stimulus is ever driven — valid stays flat — because the sequence is killed when the phase ends before it can run. The crucial reading is the near-zero width of phase_active: the run phase opens and closes at essentially the same time, because the count it checks is already zero. The interface never activates (valid flat) — not because nothing was started (a sequence was started), but because the phase ended before the started sequence could drive anything. This distinguishes the zero-time finish from the other idle-looking failures: a missing sequence (27.5) leaves the phase running (waiting in get_next_item) while valid is flat; here, the phase itself ends almost immediately. And it's distinct from a hang (where phase_active would stay high forever, the count stuck above zero) and from a normal run (where phase_active stays high while stimulus drives, then ends after the objection is dropped). So the three phase shapes are distinguishable: phase_active near-zero-width → no raise (zero-time finish); phase_active high forever → no drop (hang); phase_active high while valid drives, then ends → normal. Reading the symptom this way — did the phase even stay open? did any stimulus drive? — localizes the zero-time finish to the missing objection. The picture to carry is that the phase ended before it began — it opened and closed in the same instant because nothing held it open — and the test passed green having driven nothing. The run phase opening and closing immediately with the interface never active is the waveform signature of the *missing-objection zero-time finish. A run phase that opens and closes at essentially the same time, with no stimulus ever driven, is the signature of no objection raised — distinct from a hang (phase stays open forever) and a normal run (phase stays open while stimulus drives).
DebugLab — the test that passed in zero time having driven nothing
A test that passed green in zero simulation time because no objection kept the run phase alive
A new test was added to a regression. It passed — green — and the regression moved on. Weeks later, while investigating coverage that wasn't closing, an engineer noticed something odd about that test: its log showed a final simulation time of 0 ns. The test had "run" and "passed" in zero simulation time. Looking closer, the log had no transaction activity — no driver messages, no scoreboard comparisons, nothing — between the start of the run phase and the end of the test. The test's run_phase created a sequence and called seq.start(...) — and yet no stimulus had been driven. The test reported no failures (the scoreboard found no mismatches — because it received nothing to compare), so it had passed, and nobody had questioned a green result. The test had been in the regression for weeks, passing every night, having verified absolutely nothing — a zero-time, zero-stimulus pass.
The test's run_phase never raised an objection, so the run-phase objection count was zero from the start, the phase ended immediately at time zero, and the started sequence was killed before it could drive any stimulus — the test passed green having run nothing:
✗ NO OBJECTION RAISED — the run phase ends immediately:
class my_test extends uvm_test;
task run_phase(uvm_phase phase);
my_seq seq = my_seq::type_id::create("seq");
seq.start(env.agent.sequencer); // started, but...
// (no phase.raise_objection) → obj_count = 0 from the start → run_phase ENDS at time 0
endtask // → seq killed before driving → 0 stimulus → 0 mismatches → GREEN
endclass
// objection trace (+UVM_OBJECTION_TRACE): NO raise ever issued → phase ended immediately
// sim log: final time = 0 ns, no transactions → the tell
✓ RAISE before stimulus, DROP when done — keep the phase alive while the sequence runs:
task run_phase(uvm_phase phase);
phase.raise_objection(this); // keep the phase open
my_seq seq = my_seq::type_id::create("seq");
seq.start(env.agent.sequencer); // now runs to completion
phase.drop_objection(this); // phase ends after stimulus, count returns to 0
endtask
// (or set seq as the phase default_sequence, which manages the objection via starting_phase)This is the no-objection zero-time-finish bug — a cardinal phasing failure, and a false-confidence trap like the silent scoreboard (27.4). The test's run_phase created and started a sequence — which looks like running stimulus — but never raised an objection. Because the run-phase objection count is zero at the start and the phase ends the moment the count is zero, the phase ended immediately at time zero — before the forked sequence could drive anything. The sequence was killed with the phase, no stimulus ran, the scoreboard received nothing and so found no mismatches, and the test passed green. The deepest danger is that this passed silently: unlike a hang (which would have obviously never finished and forced attention), the zero-time finish produced a green result, indistinguishable from a real pass, so it sat in the regression for weeks passing every night having verified nothing. The tells were visible — the final simulation time of 0 ns and the absence of any transaction activity — but nobody looks at sim time on a green test. The cause was immediately visible in the objection trace: +UVM_OBJECTION_TRACE would have shown no raise was ever issued, so the phase had nothing keeping it open. The fix is to raise an objection before the stimulus and drop it when done — phase.raise_objection(this) before seq.start(...) and phase.drop_objection(this) after — so the phase stays alive while the sequence runs and ends when stimulus completes (or, equivalently, set the sequence as the phase's default sequence, which manages the objection automatically via starting_phase). The general lesson, and the chapter's thesis: a time-consuming phase ends when its objection count reaches zero, and the count starts at zero — so without a raise, the run phase ends in zero time, killing the started stimulus before it runs and passing the test green having done nothing; raise an objection to keep the phase alive while stimulus runs and drop it when done, and check the simulation time and stimulus count, not just the green result, because a test that finishes at time zero verified nothing, and a zero-time green is false confidence indistinguishable from a real pass. A test that passes in zero simulation time ran no stimulus — raise an objection to keep the phase alive, and never trust a green result without confirming the test actually ran.
The tell is a test that passes but finishes at or near time zero, or drives no transactions. Diagnose the missing objection:
- Check the final simulation time and stimulus count. A test finishing at time zero, or with zero driven transactions, ran nothing regardless of the green result.
- Enable the objection trace. +UVM_OBJECTION_TRACE shows whether any raise was ever issued; none means the phase ended immediately.
- Confirm the run_phase raises an objection. A run_phase that starts a sequence but never raises an objection ends before the sequence runs.
- Don't trust a green result without confirming the test ran. A passing test that drove no stimulus verified nothing; gate on non-zero sim time and expected stimulus counts.
Keep the phase alive while stimulus runs:
- Raise an objection before stimulus and drop it after. Bracket the time-consuming work so the phase stays alive exactly as long as needed.
- Or use a default sequence. Setting the sequence as the phase default sequence manages the objection automatically via starting_phase.
- Gate sign-off on non-zero sim time and stimulus counts. Treat a zero-time finish or zero transactions as a failure, regardless of the green result.
- Balance every raise with a drop on every path. Ensure the drop is reached even after early returns or in every branch, to avoid the opposite hang.
The one-sentence lesson: a time-consuming phase ends when its objection count reaches zero and the count starts at zero, so without a raise the run phase ends in zero time and kills the started stimulus before it runs — passing the test green having verified nothing — so raise an objection to keep the phase alive while stimulus runs and drop it when done, and check the simulation time and stimulus count rather than trusting a green result.
Common Mistakes
- Not raising an objection in run_phase. The phase ends in zero time and the test passes green having driven nothing; raise an objection to keep the phase alive while stimulus runs.
- Raising but never dropping. The phase never ends and the test hangs; every raise needs a matching drop, on every path.
- Trusting a zero-time green result. A test that finished at time zero ran nothing; check the sim time and stimulus count, not just the pass.
- Time-consuming work in a function phase. Build and connect are function phases and cannot consume time; do time-consuming work in run or the task sub-phases.
- Dropping the objection on only one path. An early return or untaken branch that skips the drop hangs the phase; ensure the drop is always reached.
- Raising on the wrong phase handle. Raising an objection on a phase other than the one the work runs in keeps the wrong phase alive; raise on the phase whose lifetime you mean to extend.
Senior Design Review Notes
Interview Insights
Because the objection count is a single reference counter that keeps a phase alive while it's non-zero and ends the phase the instant it reaches zero, and the count starts at zero — so the two failures are opposite directions of imbalance in that one counter. Here's the mechanism. A time-consuming phase like run_phase stays alive as long as its objection count is above zero, and ends the moment the count returns to zero. Components call raise_objection to increment the count and drop_objection to decrement it, bracketing the time they need the phase to stay alive. Crucially, the count is zero at the start of the phase. So consider the two ways it goes wrong. First direction: nobody raises an objection. The count is zero from the start, and the phase's rule is to end when the count is zero, so the phase ends immediately, at time zero. Any stimulus that was about to run is killed before it runs. That's the zero-time finish — the phase ended too early because the count was already at its ending value. Second direction: a component raises an objection but never drops it. The count goes to one and stays there, never returning to zero, so the phase never ends. That's the infinite hang — the phase stays alive forever because the count never reached its ending value. Same counter, opposite directions: too few raises and the count is stuck at zero so the phase ends instantly; a raise without a drop and the count is stuck above zero so the phase never ends. The building-occupancy analogy captures it exactly: the building stays open while someone is badged in and closes when the last person badges out, so if nobody badges in it closes immediately, and if someone badges in but never out it never closes. Understanding the counter explains both symptoms and their opposite fixes: the zero-time finish is fixed by adding a raise to keep the phase open, the hang is fixed by adding the missing drop so the count can return to zero. And both are diagnosed the same way, with the objection trace, which shows every raise and drop and the running count — no raise means the early end, a raise with no matching drop means the hang.
A zero-time finish is dangerous because it passes green — the test ends before any stimulus runs, so no checks run, so nothing fails, so it reports a pass — and a passing test that verified nothing is false confidence, worse than a failure, and it hides in a green regression. Walk through it. If the run_phase never raises an objection, the objection count is zero from the start, so the phase ends immediately at time zero. The sequence that was started gets killed before driving anything, so no stimulus runs. With no stimulus, the scoreboard receives nothing to compare, so it finds no mismatches, so it reports no failure. The test passes. The crucial problem, exactly like the silent scoreboard with an unconnected analysis port, is that a test that checked nothing and a test that checked correctly both report no failure and both pass — from the green result alone you cannot tell them apart. So the zero-time finish is indistinguishable from a real pass, and that's why it's the dangerous half of the phasing failures. The hang is loud — the test obviously never finishes, so someone has to deal with it. But the zero-time finish is silent — it passes — so it can sit in a regression for weeks, passing every night, having verified absolutely nothing, until someone happens to notice. How you catch it: the tells are the simulation time and the stimulus count. A real test takes non-zero simulation time and drives a non-zero, expected number of transactions. A test that finishes at or near time zero, or that drives zero transactions, ran nothing — regardless of the green result. So you check the final sim time and the transaction or comparison count, not just the pass. Concretely, you can gate sign-off on non-zero sim time and on scoreboards reporting an expected, non-zero number of comparisons, treating a zero-time finish or zero stimulus as a failure even though no check failed. And once you suspect it, the objection trace confirms it immediately — it shows no raise was ever issued, so the phase had nothing keeping it open. The deeper principle is that you cannot trust a green result from a test you haven't confirmed actually ran; absence of failures can mean correct, or can mean nothing executed.
raise_objection and drop_objection bracket the time a phase must stay alive: raise increments the phase's objection count to keep the phase from ending, and drop decrements it, so the phase ends when the count returns to zero — which means you raise before your stimulus starts and drop when it's done, keeping the phase alive exactly as long as the stimulus needs. The reason this is necessary is that a time-consuming phase ends as soon as its objection count is zero, and the count is zero at the start. So if you just start a sequence in run_phase without raising an objection, the phase ends immediately and your sequence is killed. To keep the phase alive while the sequence runs, you raise an objection before starting it and drop the objection after it completes. The typical pattern in a test's run_phase is: phase.raise_objection(this), then create and start the sequence and wait for it to finish, then phase.drop_objection(this). While the objection is held, the count is above zero, so the phase stays alive; when you drop it and the count returns to zero, the phase ends. There are some important details. The raise and drop must balance — every raise needs a matching drop — or you get the opposite failure, a hang, if you raise without dropping. The drop must be reached on every path, so watch for early returns or branches that skip it. You raise on the specific phase handle for the phase whose lifetime you mean to extend; raising on the wrong phase keeps the wrong phase alive. An alternative to managing the objection manually is to set the sequence as the phase's default sequence, in which case UVM manages the objection for you through the sequence's starting_phase — the sequence raises an objection when it starts and drops it when it ends, automatically. Multiple components can raise objections on the same phase, and the phase stays alive until all of them have dropped — so for instance several agents driving traffic can each hold an objection, and the phase ends when the last one is done. So the role is essentially flow control over the phase's lifetime: objections are how components tell the phasing system I still need this phase to be running, and the phase ends when nobody needs it anymore. Forgetting to raise ends the phase too early; forgetting to drop never ends it.
You diagnose a hanging phase by recognizing it as an objection that was raised but never dropped, then using the objection trace and UVM's phase-end timeout report to find the component still holding the objection. The symptom is that the test runs but never completes — it makes no further progress and never reaches the end. That localizes to phasing: the phase isn't ending, which means its objection count never returned to zero, which means some objection was raised and never dropped. First, enable the objection trace with +UVM_OBJECTION_TRACE, which prints every raise and drop with the component and the running count. You look for a raise that has no matching drop — a count that went up and never came back down. The component that issued that raise is your culprit. UVM also helps here directly: when a phase is stuck, UVM's phase-end handling will, after a timeout, report the objections that are still outstanding, naming the components that are still holding them. So the holder is often named for you, which makes the hang, though it manifests as the test never finishing, actually quick to localize. Once you've identified the component and the unmatched raise, you find why the drop never happened. Common reasons: the drop was simply forgotten; the drop is in a branch or after an early return that wasn't reached; or the code that was supposed to run between the raise and drop is itself hung — for instance a sequence that's stuck, which ties back to sequencer problems, so the drop after it never executes. That last case is important: a hung sequence and a never-dropped objection can be the same incident — the sequence hangs, so the run_phase never reaches its drop_objection, so the phase hangs. So you may need to diagnose the underlying hang first. The fix is to ensure the objection is dropped — add the missing drop, move it so it's always reached, or fix the upstream hang that's preventing the drop. The general approach is the methodology specialized to phasing: the hang symptom localizes to an unbalanced objection, the objection trace and the timeout report are the observation that names the holder, and you confirm the unmatched raise before fixing by restoring the balance.
Doing work in the wrong phase breaks things in a few distinct ways, depending on the mismatch: time-consuming work in a function phase is illegal because function phases cannot consume time; work that depends on components or connections that aren't ready yet fails because of phase ordering; and raising objections or doing stimulus in the wrong phase keeps or runs the wrong phase. Start with the function-versus-task distinction. UVM phases come in two kinds. Function phases — build_phase, connect_phase, end_of_elaboration_phase, and others — execute in zero time and cannot consume simulation time; they're for constructing, wiring, and configuring. Task phases — run_phase and the scheduled sub-phases like reset, configure, main, and shutdown — can consume time and are where stimulus runs. If you try to do time-consuming work in a function phase, like a delay or waiting on an event in build_phase, it's an error, because functions can't block or advance time. So time-consuming work must go in a task phase. Next, phase ordering. The function phases run in a defined order: build top-down, so a parent builds before its children; connect bottom-up, after everything is built; then end_of_elaboration. If you do something in build_phase that depends on a child that isn't constructed yet, or try to connect ports in build_phase before the components exist, it fails because of the ordering — the thing you need isn't ready. Connections belong in connect_phase precisely because build has finished creating everything by then. Then there's putting work in the wrong task phase or on the wrong phase handle. If you raise an objection on run_phase but your stimulus is in main_phase, you're keeping the wrong phase alive. If you mix run_phase and the scheduled sub-phases, which run in parallel, you can get ordering surprises. The symptoms vary — an explicit error about consuming time in a function, a null handle or missing connection from depending on something not yet built, or stimulus that runs at the wrong time or a phase kept alive that shouldn't be. The fixes follow the cause: move time-consuming work to a task phase, do construction in build and wiring in connect respecting the ordering, and raise objections and run stimulus in the phase you actually mean. The general principle is that each phase has a specific purpose and constraints — function versus task, a position in the order — and work has to be placed in the phase whose purpose and timing it matches.
Exercises
- Classify the symptom. For each — the test ends at time zero, the test never finishes, a delay in build_phase errors — name the cause and the fix.
- Bracket the stimulus. Given a run_phase that starts a sequence with no objection, add the raise and drop and explain what each prevents.
- Find the unmatched call. Given an objection trace showing a raise at time T with no matching drop, explain how UVM names the holder and how you'd fix it.
- Catch the false green. Describe the sign-off check on sim time and stimulus count that would have caught a zero-time finish before it sat in the regression.
Summary
- Phase issues are failures in UVM's phasing and objection mechanism — a phase ending too early, never ending, or work in the wrong phase — rooted in the objection count that keeps a phase alive while non-zero.
- The objection count is a reference counter: the phase is alive while count > 0, ends at count == 0, and starts at 0 — so the two headline failures are opposite directions of imbalance: no objection raised → ends immediately (zero-time finish); raised but never dropped → never ends (infinite hang).
- The zero-time finish is the dangerous half: the test passes green having run no stimulus (false confidence, like the silent scoreboard) — check the simulation time and stimulus count, not just the green result.
- Observe with
+UVM_OBJECTION_TRACE(every raise and drop with the running count) — no raise = immediate end; a raise with no matching drop = the held objection, which UVM names at the phase-end timeout; and do time-consuming work only in task phases. - The durable rule of thumb: a time-consuming phase ends when its objection count reaches zero and starts at zero, so the two opposite failures are one counter's two imbalances — no objection raised ends the phase immediately (a zero-time finish that passes green having driven nothing) and an objection never dropped keeps it alive forever (an infinite hang UVM can attribute to the holder); raise an objection to keep the phase alive while stimulus runs and drop it on every path when done, do time-consuming work only in task phases, and check the simulation time and stimulus count rather than trusting a green result that may have run nothing.
Next — RAL Issues: the methodology turns to the register abstraction layer. Why a register read or write through the model misbehaves — a mirror that diverges from hardware, an address or field map that's wrong, a missing or misconfigured adapter, or front-door versus back-door access confusion — how to read the symptom (a register mismatch, or a value that won't update), how to observe it (the RAL prediction and the bus transaction), and how to fix each so the register model tracks the hardware.