Skip to content

UVM

Phase Debugging

Diagnosing phasing problems with +UVM_PHASE_TRACE, +UVM_OBJECTION_TRACE, and print_topology, and the symptom-to-phase signatures that localize phase bugs.

UVM Phasing · Module 5 · Page 5.12

The Engineering Problem

Across this module you met a phase bug in nearly every chapter — a component never built (orphan), time-consuming work in a function phase, a run that ends at time zero, a run-phase race, a connection wired too early. Each was diagnosable, but only if you know how to look. This chapter is the practical counterpart to the whole module: how to debug phasing problems — the tools UVM gives you, and the symptom-to-phase signatures that turn a confusing failure into a located bug.

The core insight is that phasing problems are unusually localisable, because the schedule is fixed and observable. UVM provides tracing facilities that make the otherwise-invisible phase machinery visible: +UVM_PHASE_TRACE shows every phase transition (when each phase starts and ends, in what order), +UVM_OBJECTION_TRACE shows every objection raise and drop (the end-of-test mechanism), and print_topology shows the constructed component tree. Combined with knowing each bug's signature — what symptom maps to which phase — these tools let you answer "which phase, which component, what went wrong" quickly. This chapter assembles the phase-debugging toolkit and the diagnostic playbook.

How do you debug phasing problems — what tracing facilities (+UVM_PHASE_TRACE, +UVM_OBJECTION_TRACE, print_topology) make the phase machinery visible, and what symptom-to-phase signatures localize a phase bug to its cause?

Motivation — why phasing is so debuggable

Phasing problems are among the most systematically diagnosable bugs in UVM, because the framework makes the schedule observable:

  • The schedule is fixed, so the trace is meaningful. Because phases always run in the same order (Module 5.11), a +UVM_PHASE_TRACE log is a reliable map — you can see exactly which phase was running when something happened, and whether each phase started and ended as expected. A deviation in the trace points straight at the problem.
  • Objections are the end-of-test mechanism, and they're traceable. Most "the test did nothing" and "the test stopped too early" bugs are objection problems (Module 5.6). +UVM_OBJECTION_TRACE shows every raise and drop with timestamps and the objecting component, so you can see why the run ended when it did — the single most useful trace for end-of-test bugs.
  • The topology reveals structural bugs. Orphans (Module 3.3), misplacements (wrong parent), and missing components show up in print_topology — the constructed tree, printed. A component that should exist but is absent, or sits at the wrong path, is visible at a glance.
  • Symptoms map to phases. Phase bugs have distinctive signatures: an instant finish, a null handle, a compile error on a time statement, an intermittent near-time-zero failure. Each points to a specific phase and cause, so triage is "match the symptom, confirm with the trace" rather than reading everything.

The motivation, in one line: the fixed, observable phase schedule plus UVM's tracing facilities make phasing bugs unusually localisable — so phase debugging is a systematic process of matching a symptom to its phase signature and confirming with a trace, not a hunt.

Mental Model

Hold phase debugging as reading the flight recorder against a known flight plan:

Phase debugging is reading the flight recorder against a fixed flight plan. The flight plan is the phase schedule — always the same stages in the same order (Module 5.11). When something goes wrong, you don't guess; you pull the recorders. The phase trace (+UVM_PHASE_TRACE) is the timeline of which stage the flight was in and when each began and ended — did it reach cruise (run_phase)? did it skip a stage? The objection trace (+UVM_OBJECTION_TRACE) is the record of what kept the engines running and when they were cut — why did the flight end when it did? The topology (print_topology) is the manifest of what was actually on board — is every component present and in its right place? And you bring prior knowledge of failure signatures — "landed instantly" means the engines were never started (no objection); "a part missing from the manifest" means it was never loaded (orphan). You read the recorders against the plan, match the anomaly to a known signature, and you've found the cause.

So to debug a phasing problem, ask: which phase? (phase trace), why did the run end? (objection trace), is the tree right? (topology) — and match the symptom to a known signature. The fixed plan makes the recorders interpretable.

Visual Explanation — the phase-debugging toolkit

Phase debugging rests on three tracing facilities plus a signature playbook. Each tool exposes a different facet of the phase machinery; the playbook maps symptoms to causes.

Phase debugging tools: +UVM_PHASE_TRACE, +UVM_OBJECTION_TRACE, print_topology, and symptom signaturesThree traces + a signature playbookThree traces + a signature playbook+UVM_PHASE_TRACE — phase transitionsevery phase start/end, in order — see which phase a problem is in and whether phases ranevery phase start/end, in order — see which phase a problem is in and whether phases ran+UVM_OBJECTION_TRACE — objection raise/dropevery objection event with timestamp and component — debug end-of-test (time-zero / early end)every objection event with timestamp and component — debug end-of-test (time-zero / early end)print_topology — the component treethe constructed hierarchy — find orphans (missing) and misplacements (wrong path)the constructed hierarchy — find orphans (missing) and misplacements (wrong path)Signature playbook — symptom → causeinstant finish→objection; null→build; compile error on @/#→function phase; intermittent t0→run raceinstant finish→objection; null→build; compile error on @/#→function phase; intermittent t0→run race
Figure 1 — the phase-debugging toolkit. +UVM_PHASE_TRACE exposes phase transitions (which phase, when, in what order). +UVM_OBJECTION_TRACE exposes objection raise/drop (the end-of-test mechanism). print_topology exposes the constructed component tree (orphans, misplacements). The signature playbook maps symptoms (instant finish, null component, won't-compile time statement, intermittent near-time-zero) to their phase and cause. Together they localize any phase bug.

The three traces each reveal a different layer of the phase machinery. +UVM_PHASE_TRACE makes the schedule visible — a log of every phase beginning and ending, so you see which phase was active when a symptom occurred and whether the expected phases ran in the expected order. +UVM_OBJECTION_TRACE makes the end-of-test mechanism visible — every raise and drop, with timestamp and component, which is the decisive trace for "ran nothing" or "stopped early" bugs. print_topology makes the structure visible — the constructed tree, where orphans and wrong-parent placements are obvious. And the signature playbook is the prior knowledge that turns a symptom into a hypothesis before you even read a trace: an instant finish is an objection bug, a null handle is a build/connect bug, and so on. The workflow is to form the hypothesis from the signature, then confirm it with the relevant trace — fast, systematic, and grounded in the fixed schedule.

RTL / Simulation Perspective — turning on the traces

The traces are enabled with command-line plusargs; print_topology is a call. Knowing how to turn each on — and what its output looks like — is the practical core of phase debugging.

enabling the phase-debugging traces
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// COMMAND LINE — enable tracing (no code change needed):
//   +UVM_PHASE_TRACE        → logs every phase transition (start/end, order)
//   +UVM_OBJECTION_TRACE    → logs every objection raise/drop (component, count, time)
//
//   e.g.:  simv +UVM_TESTNAME=my_test +UVM_PHASE_TRACE +UVM_OBJECTION_TRACE
 
// print_topology — call it (commonly in end_of_elaboration_phase, Module 5.4):
function void my_env::end_of_elaboration_phase(uvm_phase phase);
  super.end_of_elaboration_phase(phase);
  uvm_top.print_topology();      // dump the constructed component tree
endfunction
 
// example +UVM_OBJECTION_TRACE output (debugging a run that ended too early):
//   @  0: raise_objection  by uvm_test_top.env.agt.seqr ... count=1
//   @ 40: drop_objection   by uvm_test_top.env.agt.seqr ... count=0   ← run ends here
//   → if the run ended sooner than expected, the trace shows WHO dropped and WHEN

The tooling is deliberately low-friction. The +UVM_PHASE_TRACE and +UVM_OBJECTION_TRACE plusargs are command-line switches — you add them to the simulation invocation with no code change, so you can turn them on for a failing run and off for normal runs. +UVM_PHASE_TRACE produces a chronological log of phase transitions (each phase's start and end), letting you confirm the schedule ran as expected and see which phase was active at a symptom. +UVM_OBJECTION_TRACE produces a log of every objection raise and drop, annotated with the component, the count, and the time — which is exactly what you need to answer "why did the run end when it did?" (the example shows a drop at time 40 ending the run; if that's too early, the trace names who dropped it). And print_topology is a one-line call (typically in end_of_elaboration_phase) that dumps the constructed tree, exposing orphans and misplacements. Three switches and a call — that's the entire toolkit, and it makes the phase machinery fully observable.

Verification Perspective — the symptom-to-phase signature playbook

The fastest phase debugging starts from the symptom, because phase bugs have distinctive signatures that point straight at a phase and cause — before you read any trace. This playbook is the diagnostic core of the module.

Symptom to phase mapping: instant finish to objection, null to build, compile error to function phase, intermittent to run race, no data to connectInstant finish (t=0)symptomNo objection in run_phase→ check +UVM_OBJECTION_TRACENull component handlesymptomNot built / orphan (build)→ check print_topologyCompile error on@/#/waitsymptomTime work in a functionphase→ move to run_phaseIntermittent near t=0symptomrun_phase race (no order)→ sync / use ordered phase12
Figure 2 — the symptom-to-phase signature playbook. Each common phase-bug symptom maps to a phase and cause: instant finish (time zero) → no objection raised in run_phase; null component handle → not built / orphan (build_phase); compile error on a time statement → time-consuming work in a function phase; intermittent near-time-zero failure → run_phase race (no ordering); scoreboard receives nothing → unconnected analysis port (connect_phase). Match the symptom, then confirm with the matching trace.

This playbook is the heart of efficient phase debugging: each symptom has a signature that names its phase and likely cause. An instant finish at time zero means no objection was raised in run_phase (Module 5.6) — confirm with +UVM_OBJECTION_TRACE, which will show no raise. A null component handle means a component wasn't built or is an orphan (Module 3.3) — confirm with print_topology, where it'll be absent or misplaced. A compile error on a time-controlling statement (@, #, wait) means time-consuming work is in a function phase (Module 5.1) — move it to run_phase. An intermittent failure near time zero means a run_phase race (no ordering between components, Module 5.6) — fix with synchronisation or an ordered phase. A scoreboard receiving nothing means an unconnected analysis port (Module 3.4/5.3) — check the connect_phase wiring. The workflow is always the same: read the symptom, recall its signature (phase + cause), and confirm with the matching trace. Knowing the signatures is what makes phase debugging fast.

Runtime / Execution Flow — the debugging workflow

Phase debugging follows a repeatable workflow: identify the symptom, match it to a phase signature, enable the relevant trace, and confirm the cause. The fixed schedule makes each step deterministic.

Phase debugging workflow: identify symptom, match signature, enable trace, confirm and fixSymptom → signature → trace → fixSymptom → signature → trace → fix1Identify the symptominstant finish, null handle, compile error on @/#, intermittentnear t=0, no data received.2Match the signaturerecall the phase + cause: objection / build / function-phase /run-race / connect.3Enable the matching trace+UVM_OBJECTION_TRACE, print_topology, or +UVM_PHASE_TRACE — makethe machinery visible.4Confirm and fixthe trace confirms the hypothesis (who/when/where); apply thephase-correct fix.
Figure 3 — the phase-debugging workflow. (1) Identify the symptom (instant finish, null, compile error, intermittent, no data). (2) Match it to a signature — which phase and likely cause. (3) Enable the matching trace (+UVM_OBJECTION_TRACE for end-of-test, print_topology for structure, +UVM_PHASE_TRACE for order). (4) Confirm the cause from the trace and fix it. The fixed schedule makes each step deterministic — you're confirming a hypothesis, not searching.

This workflow is deterministic because the schedule is fixed. Step 1, identify the symptom — its form (instant vs intermittent, compile vs runtime, null vs no-data) already narrows the cause. Step 2, match the signature — the playbook (Figure 2) turns the symptom into a hypothesis naming the phase and cause, before you run anything. Step 3, enable the matching trace — +UVM_OBJECTION_TRACE for end-of-test hypotheses, print_topology for structural ones, +UVM_PHASE_TRACE for ordering ones — which makes the relevant machinery visible. Step 4, confirm and fix — the trace shows who/when/where (which objection dropped, which component is missing, which phase ran when), confirming the hypothesis, and you apply the phase-correct fix (raise an objection, give the component a parent, move time-work to run_phase, synchronise the race). You are confirming a hypothesis, not searching blindly — which is why phase debugging, done this way, is fast and reliable. The fixed schedule plus the traces plus the playbook turn a class of confusing bugs into a checklist.

Waveform Perspective — what the phase trace shows

+UVM_PHASE_TRACE and +UVM_OBJECTION_TRACE are, in effect, a textual version of the phase-and-objection timeline you've seen as waveforms throughout this module. Reading that timeline is reading the trace.

What the traces show — phase transitions and objection raise/drop over time

10 cycles
What the traces show — phase transitions and objection raise/drop over time+UVM_PHASE_TRACE logs phase transitions: build/connect (t=0), then run, then cleanup+UVM_PHASE_TRACE logs …+UVM_OBJECTION_TRACE logs the raise: run_phase stays alive (who + time)+UVM_OBJECTION_TRACE l…the objection drop ends the run — an EARLY drop here would show as a short runthe objection drop end…clkphaseB/CB/CRUNRUNRUNRUNRUNEXTCHKRPTobjectionvalidt0t1t2t3t4t5t6t7t8t9
Figure 4 — the timeline +UVM_PHASE_TRACE and +UVM_OBJECTION_TRACE expose. The phase track shows transitions (build/connect at t=0, then run, then cleanup) — what +UVM_PHASE_TRACE logs. The objection is raised at the start of run and dropped when stimulus completes — what +UVM_OBJECTION_TRACE logs (with component and time). Reading these traces tells you which phase a symptom is in and why the run ended when it did. A run ending too early shows as an early objection drop; an instant finish shows as no raise at all.

The phase and objection tracks are exactly what the textual traces capture. +UVM_PHASE_TRACE records the phase track — every transition (B/CRUN → cleanup), so you can see which phase was active at any point and whether the schedule ran as expected (a missing phase or wrong order is immediately visible). +UVM_OBJECTION_TRACE records the objection track — the raise at the start of the run and the drop at the end, annotated with the component and time — so you can answer "why did the run end here?" If the run ended too early, the objection track (the trace) shows the drop happening sooner than it should, naming who dropped it; if the run did nothing, the trace shows no raise at all (the instant-finish signature). Reading these traces is reading this timeline — which is why every phase waveform in this module is, in effect, a picture of what +UVM_PHASE_TRACE and +UVM_OBJECTION_TRACE would print.

DebugLab — the run that ended at cycle 40 instead of 4000

A regression that stopped far too early — diagnosed in minutes with +UVM_OBJECTION_TRACE

Symptom

A test that should have run a long sequence finished after only a handful of transactions — the run ended at simulation time 40 when the stimulus needed thousands of cycles. No error was printed; the run just stopped early and reported PASS (of the few transactions it did drive). The stimulus was clearly being cut off, but the code looked correct.

Root cause

An objection dropped too early — found immediately by enabling +UVM_OBJECTION_TRACE. The trace showed the objection being dropped after the first sub-sequence instead of after the whole sequence:

how the objection trace pinpointed it
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ simv +UVM_TESTNAME=my_test +UVM_OBJECTION_TRACE
  @  0: raise_objection  by uvm_test_top.env.agt.seqr (my_seq)  count=1
  @ 40: drop_objection   by uvm_test_top.env.agt.seqr (my_seq)  count=0   ← run ends — TOO EARLY
                                                                            (expected ~4000)
diagnosis:  the objection was dropped after the first sub-sequence, not the full sequence
root cause: drop_objection inside a loop / after the wrong sub-sequence → phase ends early
fix:        raise once before all stimulus, drop once after ALL of it completes

Without the trace, "the run ends early" is a vague symptom that could be many things. With +UVM_OBJECTION_TRACE, the cause was unambiguous in one run: the objection — raised at time 0 — was dropped at time 40, far earlier than the stimulus's completion, and the trace named the exact component and sequence that dropped it. That pinpointed a drop_objection placed after the first sub-sequence rather than after the whole sequence, ending run_phase prematurely. The objection trace turned a confusing early-termination into a located one-line fix.

Diagnosis

The tell is a run that ends earlier than the stimulus needs — an end-of-test (objection) bug, diagnosed via the objection trace. The general method:

  1. Match the symptom to a signature. "Ends too early" (or "ends at time zero") is an objection problem (Module 5.6) — the phase ended because objections dropped. The signature points you at +UVM_OBJECTION_TRACE.
  2. Enable +UVM_OBJECTION_TRACE and read the timeline. It shows every raise and drop with component and time. Find where the count hit zero — that's where the run ended — and check whether that's too early.
  3. Trace the early drop to its source. The trace names the component and sequence that dropped the last objection; inspect that drop's placement (a loop, the wrong sub-sequence) to find the cause.
Prevention

Use the traces routinely, and know the signatures:

  1. Reach for +UVM_OBJECTION_TRACE on any end-of-test oddity. A run that ends at time zero, ends too early, or hangs is an objection problem — the trace shows the raises and drops that explain it, fast.
  2. Match symptom to signature first. Before reading anything, recall the playbook: instant finish → no objection; early end → premature drop; null → not built; compile-error-on-time → function phase; intermittent t0 → run race. The signature tells you which trace to enable.
  3. Keep the traces in your back pocket. +UVM_PHASE_TRACE, +UVM_OBJECTION_TRACE, and print_topology are command-line/one-call switches with no code change — turn them on the moment a phasing symptom appears, rather than adding print statements.

The one-sentence lesson: a run that ends too early is an objection bug — enable +UVM_OBJECTION_TRACE, find where the count hit zero and who dropped it, and you've located the premature drop; match each phase symptom to its signature and confirm with the matching trace.

Common Mistakes

  • Debugging phase bugs with print statements instead of the traces. +UVM_PHASE_TRACE, +UVM_OBJECTION_TRACE, and print_topology expose the machinery directly, with no code change — far faster than scattering uvm_infos. Reach for the traces first.
  • Ignoring the symptom's signature. Each phase symptom (instant finish, null, compile error, intermittent t0, no data) maps to a phase and cause. Skipping that match and reading everything is slow; recall the signature first.
  • Not using +UVM_OBJECTION_TRACE for end-of-test bugs. Time-zero finishes, early ends, and hangs are objection problems, and the objection trace explains them directly. Diagnosing them without it is guessing.
  • Forgetting print_topology for structural bugs. Orphans and misplacements are visible in the topology dump. A null handle to a component that "should exist" is a topology question.
  • Misreading the phase trace's order. Build is top-down, connect/cleanup bottom-up, run concurrent (Module 5.11). Reading the trace without that ordering model leads to false conclusions about what "should" have run when.
  • Adding the traces permanently. They're for debugging; leaving them on clutters normal logs. Enable per failing run, disable otherwise.

Senior Design Review Notes

Interview Insights

By matching the symptom to a phase signature and confirming with one of three tracing facilities. The tools are: +UVM_PHASE_TRACE (a command-line plusarg that logs every phase transition — start, end, and order — so you can see which phase a symptom is in and whether the schedule ran as expected); +UVM_OBJECTION_TRACE (a plusarg that logs every objection raise and drop with the component, count, and time — the decisive trace for end-of-test bugs); and print_topology (a call, often in end_of_elaboration_phase, that dumps the constructed component tree to reveal orphans and misplacements). The method is: identify the symptom, recall its signature (instant finish → no objection in run_phase; null handle → not built; compile error on a time statement → function phase; intermittent near time zero → run_phase race; scoreboard receives nothing → unconnected port), enable the matching trace to confirm, and apply the phase-correct fix. Because the phase schedule is fixed and observable, this is a deterministic process — you're confirming a hypothesis, not searching. The traces are command-line switches or one-line calls with no code change, so you turn them on the moment a phasing symptom appears.

Exercises

  1. Pick the trace. For each symptom, name the trace/tool you'd enable first: (a) the run finishes at time zero; (b) a null handle to a component that should exist; (c) the run ends earlier than the stimulus needs; (d) you're unsure whether connect_phase ran before run_phase.
  2. Read the objection trace. Given a trace showing raise @0 count=1 and drop @40 count=0 for a test that needed ~4000 cycles, state the bug and the fix.
  3. Match the signature. For each, give the phase and cause: (a) compile error on @(posedge clk) in connect_phase; (b) intermittent null near time zero; (c) scoreboard receives nothing all run.
  4. Build the workflow. Write the four-step phase-debugging workflow (symptom → signature → trace → fix) and apply it to a "test did nothing, reported PASS" symptom.

Summary

  • Phase bugs are unusually localisable because the schedule is fixed and observable. The toolkit is three traces plus a signature playbook.
  • +UVM_PHASE_TRACE logs phase transitions (which phase, when, order); +UVM_OBJECTION_TRACE logs objection raise/drop (the end-of-test mechanism — the decisive trace for time-zero/early-end/hang bugs); print_topology dumps the component tree (orphans, misplacements). The first two are command-line plusargs (no code change); the third is a one-line call.
  • The signature playbook maps symptoms to causes: instant finish → no objection (run_phase); null handle → not built/orphan (build_phase); compile error on @/#/wait → time-work in a function phase; intermittent near-t0 → run_phase race; no data received → unconnected port (connect_phase).
  • The workflow is deterministic: identify the symptom, match its signature (phase + cause), enable the matching trace, confirm and fix — you confirm a hypothesis rather than search, because the schedule is fixed.
  • The durable rule of thumb: match the phase symptom to its signature, then confirm with the matching trace — +UVM_OBJECTION_TRACE for end-of-test, print_topology for structure, +UVM_PHASE_TRACE for order — so phasing bugs become a fast checklist, not a hunt.

Next — uvm_root & the Singleton Pattern: the traces and topology all hang off one special component — uvm_top, the root of the hierarchy. The next chapter covers uvm_root and the singleton pattern: the single, global top component that owns the phasing, the factory, and the report server, and why there is exactly one of it.