Skip to content

UVM

UVM Debug Checklist

The condensed sign-off list for debugging a UVM failure systematically — capturing and reproducing it, localizing by phase and component, reading the UVM_INFO log and topology, and recognizing the signatures of the common factory, config_db, connection, and phasing bugs.

UVM Design Review Checklist · Module 31 · Page 31.6

The prior checklists confirm the environment is correct, reusable, well-covered, register-honest, and fast. This one is for when something fails: a systematic approach that turns "it does not work" into a localized root cause. UVM failures have a small set of recurring signatures — a factory override that does nothing, a null virtual interface, a scoreboard that checks nothing, a test that hangs or ends in zero time — and each maps to a specific cause and a specific place to look. This checklist is the disciplined pass — capture, localize, read the evidence, match the signature — that replaces guessing with a method.

1. Why a Debug Checklist: Method Beats Guessing

You have learned why UVM works as it does; a debug checklist turns that understanding into a systematic localization of a failure rather than a guess-and-poke. Under pressure, the instinct is to stare at a waveform or add random prints, but UVM failures cluster into recognizable signatures, each with a known cause and a known place to look — so the fast path is to capture the failure reproducibly, localize it by phase and component, read the structured evidence UVM already provides (the UVM_INFO log, the topology, the trace switches), and match the symptom to its signature. The checklist catches the debugging-by-guessing trap and replaces it with the method that an experienced engineer applies almost reflexively.

The categories of the UVM debug checklist: capture, localize, read evidence, recognize signaturesCapture and reproduceseed, log, waveform; replay the failure deterministically before debuggingseed, log, waveform; replay the failure deterministically before debuggingLocalize by phase + componentwhich phase (build/connect/run/end-of-test) and which component, top-downwhich phase (build/connect/run/end-of-test) and which component, top-downRead the evidenceUVM_INFO log, print_topology, +UVM_PHASE/OBJECTION/CONFIG trace switchesUVM_INFO log, print_topology, +UVM_PHASE/OBJECTION/CONFIG trace switchesRecognize the signaturesno-op override, null vif, unconnected port, item_done hang, zero-time endno-op override, null vif, unconnected port, item_done hang, zero-time end
Figure 1 — the categories of the UVM debug checklist. Capture and reproduce: get the seed, log, and waveform, and replay the failure deterministically before debugging. Localize by phase and component: identify which phase the failure is in (build, connect, run, end-of-test) and which component, working top-down. Read the evidence: the UVM_INFO log with its IDs and severities, print_topology for the built tree, and the trace switches for phasing, objections, and config_db. Recognize the signatures: match the symptom to the common bug — a no-op factory override, a null vif, an unconnected analysis port, a missing item_done hang, a missing objection zero-time end. Each category narrows from where to why.

2. Capture and Reproduce

The first category is making the failure debuggable — the items that turn an intermittent or one-off into something you can study.

  • Capture the seed and command line. A failure you cannot rerun you cannot debug; record the random seed and the full invocation so the failure replays deterministically.
  • Capture the log and the waveform. The UVM_INFO log and a waveform dump from the failing run are the primary evidence; ensure the failing run produced both.
  • Reproduce deterministically before debugging. Rerun the exact seed and confirm the failure reproduces identically; a failure that does not reproduce with the same seed is itself a signal (a race or tool non-determinism).
  • Narrow the window. For a long run, checkpoint near the failure or bisect in time so each debug iteration is fast — do not re-run hours per attempt.

3. Localize by Phase and Component

The second category is pinning down where the failure lives — the items that narrow the search before you look at why.

  • Identify the phase. Is the failure in build_phase (construction/config), connect_phase (wiring), run_phase (stimulus/checking), or at end-of-test (objections)? Each phase has a distinct bug family, so the phase narrows the cause.
  • Identify the component. Which component's message, null handle, or missing activity is at the failure point? The hierarchical name in the UVM_INFO tag tells you exactly where in the tree.
  • Work top-down. A child fails because its parent built or configured it wrong; trace from the symptom up the hierarchy to the first thing that is actually wrong, distinguishing the symptom from the cause.
  • Separate producer from consumer. An idle interface points at the producer side (sequence/sequencer), a wrong result at the consumer/checking side — localize to the half of the testbench the symptom implicates.

4. Read the Evidence

The third category is using the structured information UVM already provides — the items that replace guessing with reading.

  • Read the UVM_INFO log by ID and severity. Messages carry an ID, a severity, and a hierarchical name; filter and read them rather than scrolling raw output — a UVM_FATAL/UVM_ERROR and its source component are the starting point.
  • print_topology (or uvm_top.print_topology) to confirm the built tree. The actual constructed hierarchy reveals a missing component, a wrong type (a factory override that did or did not take), or a mis-parented node.
  • +UVM_PHASE_TRACE for phasing problems. When a phase hangs or ends early, the phase trace shows the schedule and where it stalled.
  • +UVM_OBJECTION_TRACE for end-of-test problems. A hang or a zero-time end is an objection issue; the objection trace shows who raised and dropped, and what kept the phase alive or let it die.
  • +UVM_CONFIG_DB_TRACE for configuration problems. A null handle or unconfigured component is usually a config_db mismatch; the config trace shows every set and get and where they failed to match.

5. Recognize the Signatures

The fourth category is matching the symptom to the known bug — the items that turn evidence into a root cause.

  • A registered factory override that does nothing → a new instead of type_id::create at the creation site (the override is bypassed). Confirm with print_topology showing the base type was built.
  • A null virtual interface crashing in run_phase → a config_db set/get mismatch (path, field name, or type). Confirm with +UVM_CONFIG_DB_TRACE.
  • A scoreboard reporting nothing / passing green with no comparisons → an unconnected analysis port (silent). Confirm the monitor's port reaches the scoreboard.
  • A sequence that drives one item then hangs → a missing item_done (the sequence blocks in finish_item). Confirm in the driver's loop.
  • A test that ends in zero time having driven nothing → a missing raise_objection (the run phase ends immediately). Confirm with +UVM_OBJECTION_TRACE.
  • A test that never ends → a missing drop_objection (or a sequence that never completes). The objection trace shows the count never returning to zero.
  • An intermittent / tool-dependent failure → a race or X (a result that changes with the simulator, verbosity, or an added print). Chase the race or uninitialized value, not the tool.

6. Common Misconceptions

7. Sign-off Insight

8. Interview Questions

I localize to the producer side, because an idle interface means the driver is blocked with no item arriving, and I check, in order, whether a sequence was started, on which sequencer, whether an objection keeps the phase alive, and whether the driver reaches its loop. First the most common cause: was a sequence actually started? A sequence must be started on a sequencer, explicitly or as a default sequence, and if nothing started one the driver waits forever. Second, if a sequence is started, is it on the right sequencer? Started on a different agent's sequencer, its items go elsewhere and my interface stays idle. Third, phasing: if no objection was raised, the run phase ends in zero time before anything drives, so I check an objection is raised, using UVM_OBJECTION_TRACE. Fourth, is the driver reaching get_next_item — is it built, is its vif non-null? I raise verbosity on the driver and sequencer to see the handshake, and I read the UVM_INFO log for the relevant components rather than guessing. The method is to recognize the symptom — an idle interface while the clock runs — as pointing at the producer side, the sequence and sequencer, not the DUT, because the DUT cannot respond to stimulus that never arrives, and then to use the structured evidence, the log and the objection and phase traces, to confirm which of the producer-side causes it is. The understanding to convey is the systematic producer-side localization and the use of UVM's trace switches and log, which shows debugging by method rather than by staring.

Almost always the component being overridden was created with new instead of type_id::create, so construction bypassed the factory and the override was never consulted — and I confirm it by checking the creation site and by printing the topology. A factory override works by intercepting type_id::create and returning the overriding type; if a component is built with new or a direct constructor call, the factory is never involved, so no override — type or instance — can take effect, even though the override was registered correctly. The symptom is exactly this: a correctly registered override that does nothing, the simulation running with the base type. The first thing I check is how the component is created: it must be type::type_id::create with the name and parent; if it is type::new, that is the bug, and the one-line fix is to route it through the factory. I confirm by printing the topology with print_topology and looking at the actual constructed type of that component — if it is the base type rather than the override, the create was bypassed; if registration were the problem the type would also be base, so I also confirm the class uses uvm_component_utils so it has a type_id, and that the override target names the exact type. The tell that distinguishes this from other override issues is that the override is registered and still ignored, which points at the creation, not the registration. The understanding to convey is that a no-op override almost always means a new bypassing the factory at the creation site, and that print_topology plus checking create-versus-new confirms it, which is one of the most recognizable UVM signatures.

A hang is almost always an objection that is never dropped or a sequence or driver blocked forever, and I find it with the objection trace and by localizing where execution is stuck. The run phase ends when its objection count returns to zero, so a test that never ends usually has an objection that was raised and never dropped — a test or sequence that raised an objection and then blocked, or a component holding an objection it never releases. I turn on UVM_OBJECTION_TRACE, which shows every raise and drop and the running count, and look for the objection whose drop never comes and who holds it; that points me at the component stuck before its drop. The common underlying blocks are a sequence waiting in finish_item because the driver never called item_done — so the sequence drove one item and stalled, holding its objection — or a driver or monitor stuck in a wait that never completes, or a sequence whose body never returns. So after the objection trace tells me which objection is outstanding, I localize to the component holding it and look at why it is blocked — typically a missing item_done in the driver loop, a wait on an event that never fires, or a handshake that deadlocked. The contrast with the opposite bug is useful: a missing raise ends the test in zero time, a missing drop hangs it forever, and the objection trace distinguishes them immediately. The understanding to convey is that a hang is an objection-or-block problem found with the objection trace plus localizing the stuck component, and the common cause of a missing item_done, which is the systematic way to find a hang rather than guessing.

It means the testbench or design depends on something not guaranteed — almost always a race or an uninitialized X — because correct deterministic logic gives the same functional result regardless of the simulator's scheduling or whether you added an observation. When a result changes with something that should not affect behavior — a different simulator, a higher verbosity, an added display, an optimization level — the design or testbench is relying on unspecified ordering or on an undefined value. The classic case is a race: two processes read or write the same thing in the same time step with no ordering guarantee, so the result depends on which the scheduler runs first, and a different tool or an added print that shifts scheduling resolves it differently — the added-print-changes-the-result symptom is the textbook signature of a race, the verification heisenbug. A second cause is X-optimism versus pessimism: an uninitialized value or an X on a control path can pass in a tool that resolves it optimistically and fail in one that propagates it, pointing at a real reset or initialization bug. So I treat the tool- or observation-sensitivity as a diagnosis, not a nuisance — it is telling me where the nondeterminism is — and I chase the underlying race or uninitialized value rather than picking the tool that passes, because the tool that passes today is hiding a real hazard. I would localize the racing signals or the uninitialized state and fix the cause. The understanding to convey is that change-under-observation is the signature of a race or X-dependence and that you fix the cause, not the symptom, which is exactly the kind of intermittent bug a debug checklist trains you to recognize on sight.

A scoreboard that reports nothing almost always has an unconnected analysis port, so the monitor's transactions never reach it, and I confirm it by checking the connection in connect_phase and the topology. The analysis path is one-to-many and silent: a monitor writes observed transactions to its analysis port, and a scoreboard connects its analysis export or imp to receive them, but an analysis port tolerates zero subscribers without any error. So if the connection from the monitor's port to the scoreboard was never made — forgotten in connect_phase — the monitor writes into the void, the scoreboard's write method is never called, and it compares nothing while the test passes green. The symptom is exactly a scoreboard that received zero transactions and reports no comparisons, or simply a suspiciously clean pass. I confirm by checking that connect_phase contains the connection — monitor.ap.connect(scoreboard.export) — and by printing the topology and the connections to see the port is wired, and I would add a check or a count in the scoreboard of how many transactions it received, since a count of zero is the giveaway. The key recognition is that an unconnected analysis port is silent, unlike a required port which fatals, so a scoreboard checking nothing does not announce itself; you must confirm the connection rather than trust the green result. The understanding to convey is the unconnected-analysis-port signature, why it is silent, and confirming the connection and the received-transaction count, which is how you debug a scoreboard that is not actually checking.

9. Summary

The UVM debug checklist turns "it does not work" into a systematic localization rather than a guess, because UVM failures cluster into recognizable signatures. It is organized into four categories that narrow from where to why. Capture and reproduce: the seed, log, and waveform, and a deterministic replay, before debugging. Localize by phase and component: identify the phase (build/connect/run/end-of-test) and the component from the UVM_INFO name, working top-down and separating producer from consumer. Read the evidence: the UVM_INFO log by ID and severity, print_topology for the built tree, and +UVM_PHASE_TRACE/+UVM_OBJECTION_TRACE/+UVM_CONFIG_DB_TRACE for phasing, end-of-test, and configuration. Recognize the signatures: a no-op override (a new bypassing the factory), a null vif (a config_db mismatch), an unconnected analysis port (a silent green scoreboard), a missing item_done (a hung sequence), a missing objection (a zero-time end), and a tool-dependent result (a race or X).

The disciplines: run the method in order — capture, localize, read, match — rather than staring or guessing; read the highest-leverage evidence first (the fatal/error and its component, then topology and the relevant trace); and keep the signature table in mind, because most UVM failures are one of a handful of known bugs, each with a cause and a confirmation step. The checklist replaces debugging-by-guessing with the method an experienced engineer applies reflexively.

10. What Comes Next

You can now debug a failure systematically; next, the interview-readiness pass:

Next — Interview Checklist: the final checklist gathers what you should have ready for a UVM interview — the core constructs and mechanisms to explain, the bug signatures to recognize, and the judgment topics where there is no single right answer — so you can answer with the mechanism, the pitfall, and the calibrated reasoning that distinguish real experience from memorized definitions.