UVM
Driver Debugging
A waveform-centric methodology for debugging drivers — localizing the symptom (wrong value, nondeterministic race, reset corruption, no stimulus, wrong timing) to its mechanism, and confirming on the waveform.
Drivers · Module 12 · Page 12.6
The Engineering Problem
The Drivers module exposed the driver's concerns — responsibilities (12.1), conversion (12.2), signal driving (12.3), timing (12.4), architecture (12.5) — and each had its own failure mode: an unsampled read, a clock-edge race, a mishandled reset, a coupled monolith. In practice you don't get a labeled bug; you get a symptom: "the scoreboard sees wrong data," "it passes locally but fails on the farm," "it breaks after a reset," "nothing drives." Mapping a symptom to its mechanism across all the things a driver can get wrong is the hard part — and a method beats guessing.
But the driver has a crucial advantage the sequencer didn't: it acts on signals, so its bugs are visible on the waveform. Where the sequencer's state was invisible class-level data (debugged with messages, Module 11.5), the driver's behavior is out in the open on the pins — so the waveform is the primary debug tool: you see the wrong value, the signal changing at the edge (a race), the non-idle reset. Driver debugging is therefore a waveform-centric symptom-to-mechanism diagnosis: localize the symptom (wrong value → conversion; nondeterministic → race; breaks on reset → reset handling; no stimulus → not pulling/null-vif), discriminate with a few questions (value or timing? deterministic? breaks on reset?), and confirm on the waveform (and transaction recording). This chapter is the method, the question set, and the waveform-reading skill.
How do you debug a driver systematically — localizing a symptom (wrong value, nondeterministic race, reset corruption, no stimulus, wrong timing) to its mechanism with discriminating questions, and confirming on the waveform, which makes the driver's signal-level behavior visible?
Motivation — why driver debugging is waveform-centric
Driver bugs call for a waveform-centric method because of where the driver lives and how its failures present:
- The driver acts on signals, so the bug is visible. Unlike the sequencer's class-level state (invisible, needing messages, Module 11.5), the driver drives pins — so its behavior is on the waveform: the value driven, the timing, the handshake. The primary tool is looking at the pins, because the evidence is there.
- Many symptoms map to a few mechanisms, so you discriminate. "Wrong data" could be a conversion bug, a race, or a timing bug; "no stimulus" could be a null vif, not pulling, or not completing. Discriminating questions — deterministic? breaks on reset? — narrow the cause fast.
- The race is uniquely driver, so determinism is the key discriminator. A clock-edge race (Module 12.3) presents as simulator-dependent nondeterminism — a signature no other driver bug shares. "Does it pass on one simulator and fail on another?" instantly separates the race from a deterministic logic bug.
- Reset is uniquely timing, so the reset correlation matters. A reset-handling bug (Module 12.4) presents as breaks only on a mid-traffic reset — another distinctive signature. "Does it correlate with a reset?" separates reset bugs from steady-state ones.
- The waveform localizes value vs timing, so it's the confirmation tool. Whether the wrong value is on the pins (driver drove wrong) or only in the transaction (sampling failed) is visible on the waveform — so the waveform confirms the localized hypothesis directly.
The motivation, in one line: the driver's behavior is visible on the waveform (it acts on pins), and its failures have distinctive signatures (nondeterminism = race, reset-correlation = reset bug, pins-vs-transaction = conversion vs sampling) — so driver debugging is a waveform-centric method: localize on the waveform, discriminate with signature questions, confirm on the waveform.
Mental Model
Hold driver debugging as signal-level forensics on the waveform — the evidence is out in the open:
Debugging a driver is forensics where the crime scene is fully visible: the driver acts on the pins, so the waveform shows exactly what it did, and you read the evidence to find the mechanism. A sequencer bug is like a locked-room mystery — the state is hidden inside class objects, so you interrogate (add messages) to reconstruct what happened (Module 11.5). A driver bug is the opposite: the driver's whole job is on the pins, so the waveform is a full recording of the crime — you can see the wrong value driven, the signal that changed at the bell (a race), the bus that wasn't idled during reset. The forensic method is read the scene: what's on the pins (the driven values, the timing), compared to what was intended (the transaction), tells you what the driver did wrong. And the signatures point to the culprit: a value that's X or flickering at the edge is a race; a value that's wrong but stable and deterministic is a conversion mis-map; a bus still active during reset is a reset-handling failure; nothing on the pins is not driving at all. You don't guess — you look: the waveform shows the bug, and the signature names the mechanism.
So driver debugging is reading the visible scene: the waveform shows what the driver did, the comparison to intent shows what's wrong, and the signature (raced / mis-mapped / non-idle-on-reset / absent) names the mechanism. The driver's bugs are not hidden — they're on the pins, so look first.
Visual Explanation — the driver debugging decision tree
The defining tool is a decision tree keyed to the driver's distinctive signatures: start from the symptom, branch on the signature, arrive at the mechanism.
The decision tree routes each symptom to a mechanism via its signature, read mostly from the waveform. Wrong value → the key sub-question is where: is the wrong value on the pins (the driver drove wrong — a conversion mis-map, or a race) or only in the transaction (the pins are right but the transaction is wrong — sampling failed, the unsampled read of Module 12.2)? This pins-vs-transaction split — visible on the waveform vs the recorded transaction — is the most useful driver discriminator. Nondeterministic / simulator-dependent → a clock-edge race (Module 12.3): the signature is passes on one simulator, fails on another (or moves with the seed), which no other driver bug produces. Breaks on a mid-traffic reset → reset handling (Module 12.4): the signature is correct in steady state, broken when reset hits during traffic. No stimulus → the driver isn't driving: a null virtual interface (build-phase config, Module 7), not pulling (get_next_item not called), or not completing (item_done skipped). Wrong timing → wait states (not waiting for ready) or read latency (sampling at the wrong cycle, Module 12.4). The tree's value is that each signature — read from the waveform (value on pins, edge-aligned change) or the determinism/reset correlation — routes to a small set of mechanisms, so the signature does most of the narrowing. And the waveform is the first place to look for every branch, because the driver's behavior is visible there.
RTL / Simulation Perspective — the waveform-centric debug toolkit
The driver's primary debug tool is the waveform, supplemented by transaction recording and targeted messages. The code shows the instrumentation that, with the waveform, localizes a driver bug.
// ── PRIMARY TOOL: the WAVEFORM — the driver acts on pins, so its bugs are VISIBLE ──
// look at: the driven VALUES (right or wrong?), the TIMING (change AT the edge = race?),
// the HANDSHAKE (waiting for ready?), the IDLE (driven during reset?)
// ── transaction recording (8.7): compare the TRANSACTION to the PINS ──
// begin_tr/end_tr around drive_transaction → see the txn box ABOVE the pins
// pins right but txn wrong → SAMPLING failed; pins wrong → CONVERSION/race
// ── targeted MESSAGES: confirm what the driver did (value, timing) ──
task my_driver::drive_transaction(bus_item tr);
`uvm_info("DBG", $sformatf("driving %s addr=%h data=%h", tr.is_read?"RD":"WR", tr.addr, tr.data[0]), UVM_HIGH)
@(vif.drv_cb);
vif.drv_cb.addr <= tr.addr; vif.drv_cb.valid <= 1; // (drive through cb — race-free, 12.3)
...
if (tr.is_read) begin
tr.rdata = vif.drv_cb.rdata; // sample (12.2)
`uvm_info("DBG", $sformatf("sampled rdata=%h (pins=%h)", tr.rdata, vif.drv_cb.rdata), UVM_HIGH)
end
endtask
// ── the discriminating questions (what to ask of the symptom) ──
// wrong value: on the PINS (waveform) or only in the TXN (recording)? → conversion vs sampling
// nondeterministic across simulators? → clock-edge RACE
// breaks on a mid-traffic reset? → reset handling
// nothing drives? → vif null (build_phase)? get_next_item called? item_done reached?The toolkit is waveform-first. The waveform is the primary tool because the driver acts on pins — so you look at the driven values (right or wrong?), the timing (does a signal change at the edge — a race? Module 12.3), the handshake (is the driver waiting for ready? Module 12.4), and the idle (are signals driven during reset? Module 12.4). Transaction recording (Module 8.7) is the key companion: begin_tr/end_tr around the drive shows the transaction box above the pins, so you can compare them — pins right but transaction wrong means sampling failed (the unsampled read, Module 12.2); pins wrong means conversion (or a race). Targeted messages confirm what the driver did: a message printing the driven values and the sampled rdata vs the pins pins down whether the value is driven or sampled wrong. And the discriminating questions (the final comments) route the symptom: pins or transaction (conversion vs sampling), nondeterministic (race), breaks on reset (reset handling), nothing drives (vif null / not pulling / not completing). The shape to carry: the waveform shows the bug (the driver's signal-level behavior is visible), transaction recording aligns the transaction with the pins (localizing conversion vs sampling), and messages + signature questions confirm the mechanism — a waveform-centric method, because for a driver, you can see it.
Verification Perspective — the discriminating questions
The method turns on a few discriminating questions, each keyed to a driver signature. Knowing the questions — and what each answer means — is the diagnostic core.
The discriminating questions are the fast path, each keyed to a driver signature. "Is the wrong value on the pins or only in the transaction?" — the most useful driver question, answered by comparing the waveform to the recorded transaction: on the pins (the driver drove the wrong value) → conversion (mis-mapped field) or a race; only in the transaction (the pins are right, the transaction is wrong) → sampling failed (the unsampled or mistimed read, Module 12.2). This pins-vs-transaction split immediately separates driving bugs from sampling bugs. "Is it deterministic across simulators?" — the race discriminator: nondeterministic / simulator-dependent → a clock-edge race (Module 12.3, raw driving at the edge); deterministic → a logic bug (conversion or timing), reproducible, so traceable by the values. "Does it break on a mid-traffic reset?" — the reset discriminator: breaks only when reset hits during traffic → reset handling (Module 12.4, no abort/idle); steady-state → not a reset bug. "Does anything drive at all?" — for no stimulus: nothing on the pins → a null vif (build-phase config missing, Module 7), the driver not pulling (get_next_item not running), or not completing (item_done skipped). The power of the question set is that each answer — read from the waveform (value-on-pins, determinism) or the correlation (reset) — eliminates a chunk of the cause space, so a few questions pin the mechanism. The verification skill is asking the right question for the symptom, and — for a driver — most answers come from looking at the waveform: it shows whether the value is on the pins, whether a signal changed at the edge, whether the bus idled on reset. The questions direct the looking.
Runtime / Execution Flow — the localize-discriminate-confirm loop
At run time, driver debugging follows the same localize-discriminate-confirm loop as the sequencer (Module 11.5) — but centered on the waveform. The flow shows the loop with the waveform at its core.
The loop is the same disciplined narrowing as the sequencer's (Module 11.5), but the waveform is at every step. Localize (step 1): look at the waveform — the pins show what the driver did: is the value right? does a signal change at the edge? is the bus idled during reset? — and where the symptom is. For a driver, localization is primarily visual, because the behavior is on the pins. Discriminate (step 2): ask the signature question — pins or transaction? (conversion vs sampling), deterministic? (race vs logic), breaks on reset? (reset handling) — to pin the mechanism. Confirm (step 3): verify on the waveform and transaction recording — and here the driver has the advantage: confirmation is direct (you see the wrong value on the pins, the edge-aligned change, the non-idle reset), not inferred from messages as the sequencer required. Transaction recording (Module 8.7) lets you compare the transaction to the pins directly. Fix (step 4): apply the mechanism-specific fix — fix the field mapping, add the read sample, use the clocking block, add reset handling, get the vif, pair item_done — then re-run. The runtime insight is that the loop converges (localize narrows, discriminate pins, confirm verifies), and for a driver it's faster than for a sequencer because the waveform makes the behavior visible — you look rather than interrogate. So the method is the same (localize-discriminate-confirm), but the tool is the waveform, and the advantage is visibility: the driver's bugs are out in the open, so the loop confirms by seeing.
Waveform Perspective — the bug visible on the pins
The driver's advantage is that bugs are visible — so a debug waveform shows the mechanism directly: a wrong value, a raced (X-at-edge) signal, a non-idle reset are all readable from the pins.
The driver's bug, visible on the waveform: correct vs three failure signatures
12 cyclesThe waveform shows why the driver's visibility is the debugging advantage — each bug has a readable signature. The correct trace is the reference: a clean, stable driven value (40), settled (not changing at the edge), and idled (00) during reset (rst_n low at cycles 4–5). The failure signatures are each directly visible: race shows the value as XX at the edge — the raced signal (Module 12.3), and the tell is the X (undefined) at the active edge, plus the nondeterminism (it'd resolve differently per simulator); mismap shows a wrong-but-stable value (04 instead of 40) — a conversion mis-map (Module 12.2), and the tell is deterministic (the same wrong value every run) and stable (not X), so it's a logic bug traceable to the field mapping; and a reset-handling bug (described in the marker) would show the bus still active (valid high) during reset — no idle (Module 12.4). The crucial skill is reading the signature off the pins: X at the edge = race; wrong-but-stable = conversion mis-map; active during reset = reset bug; nothing = not driving. The waveform makes the mechanism visible — you don't infer it (as you would for the sequencer's hidden state), you see it. This is the chapter's payoff: the driver's bugs are out in the open on the pins, so the waveform is the primary tool, and reading the signatures — X-at-edge, wrong-but-stable, active-on-reset, absent — names the mechanism directly. Driver debugging is waveform reading, and the signatures are the vocabulary.
DebugLab — localizing a scoreboard mismatch to its mechanism
Applying the method: a scoreboard mismatch, narrowed on the waveform to its mechanism
The scoreboard reported a data mismatch — the value it checked was wrong. The symptom alone was ambiguous: a wrong checked value could come from a conversion mis-map (the driver drove wrong), a clock-edge race (nondeterministic driving), a sampling failure (an unsampled/mistimed read), a timing bug (wrong read latency), or even a reset issue. The task was to find which, systematically, using the waveform.
Applying the localize-discriminate-confirm loop on the waveform narrowed the ambiguous mismatch to a clock-edge race — but the value is the method, which would have found any of the candidates:
SYMPTOM: scoreboard data mismatch (wrong checked value) — ambiguous
(1) LOCALIZE on the waveform — look at the driven signal:
the value is XX (undefined) AT the active clock edge → not a stable wrong value
⇒ the value is RACED, not mis-mapped (a mis-map would be a stable wrong value)
(2) DISCRIMINATE — ask the signature questions:
On the pins or only in the txn? on the PINS (XX visible) → driving, not sampling
Deterministic across simulators? NO — passes on sim A, fails on sim B → RACE (not logic)
Breaks on reset? no → not a reset bug
⇒ a CLOCK-EDGE RACE (Module 12.3)
(3) CONFIRM on the waveform:
the driver drives the RAW signal (vif.valid <= ... ) AT @(posedge vif.clk) → races the DUT sample
⇒ CONFIRMED: raw driving at the edge
FIX: drive through the clocking block (vif.cb.sig) — output skew lands the drive AFTER the edge.This is the method in operation, and the point is the process, not the specific cause. The symptom — "wrong checked value" — had five plausible causes, and guessing would be slow. Instead, the localize-discriminate-confirm loop, centered on the waveform, narrowed it. Localize: looking at the waveform, the driven value was XX (undefined) at the active edge — not a stable wrong value — which immediately says raced, not mis-mapped (a conversion mis-map would be a stable wrong value, Module 12.2). Discriminate: the signature questions each eliminated a candidate — the wrong value was on the pins (so driving, not sampling), it was nondeterministic across simulators (so a race, not a deterministic logic bug), and it didn't correlate with reset (so not a reset bug) — pointing at a clock-edge race (Module 12.3). Confirm: inspecting the driver found it driving the raw signal (vif.valid <= ...) at @(posedge vif.clk) — racing the DUT's sample (the confirmed mechanism). The fix is mechanism-specific (drive through the clocking block, Module 12.3) — but the value is that the method reached it in a handful of waveform checks, and the same method would have landed on any of the others had the signatures differed: a stable wrong value → conversion mis-map; pins right but transaction wrong → sampling; breaks on reset → reset handling; nothing on the pins → not driving. The general lesson, and the chapter's thesis: don't guess among the causes of an ambiguous driver symptom — localize on the waveform (what did the driver do?), discriminate by signature (raced/mis-mapped/sampling/reset?), and confirm on the waveform — and because the driver's behavior is visible, the method converges by looking, faster than the sequencer's interrogation, regardless of which mechanism it is.
The tell is any ambiguous driver symptom (wrong value, no stimulus, breaks on reset). Apply the method:
- Localize on the waveform first. Look at the driven values, the timing (change at the edge?), the idle-on-reset — the driver's behavior is visible, so see what it did before theorizing.
- Use the pins-vs-transaction split. Compare the waveform to the recorded transaction: pins wrong → driving (conversion/race); pins right, transaction wrong → sampling.
- Apply the signature questions. Deterministic across simulators? (race vs logic); breaks on a mid-traffic reset? (reset handling); anything drives at all? (null vif / not pulling / not completing).
- Confirm on the waveform. Verify the mechanism directly — the driver's bugs are visible, so confirmation is looking, not inferring.
Build the waveform-centric method into your workflow:
- Look at the waveform first. For any driver symptom, examine the pins before theorizing — the behavior is visible, so localization starts there.
- Keep the signature questions handy. Pins-or-txn, deterministic, breaks-on-reset, drives-at-all — these split most driver cause spaces fast.
- Use transaction recording to compare txn and pins. It directly separates conversion/race (pins wrong) from sampling (pins right, txn wrong) — Module 8.7.
- Test across simulators and with mid-traffic resets. Races are simulator-dependent and reset bugs need mid-traffic resets — exercise both to expose latent driver bugs.
The one-sentence lesson: debug a driver waveform-first — localize on the waveform (the driver's signal-level behavior is visible), discriminate by signature (value on pins vs in txn → conversion vs sampling; nondeterministic → race; breaks on reset → reset handling; nothing drives → vif/pulling/completing), and confirm on the waveform — because the driver's bugs are out in the open on the pins, so the method converges by looking, regardless of which mechanism it is.
Common Mistakes
- Not looking at the waveform first. The driver's behavior is visible on the pins; theorizing before looking wastes time when the bug is right there.
- Not using the pins-vs-transaction split. Whether the wrong value is on the pins (driving) or only in the transaction (sampling) is the key driver discriminator — compare them.
- Ignoring the determinism signature. Nondeterministic / simulator-dependent is the race signature; a deterministic wrong value is a logic (conversion/timing) bug — the two have different fixes.
- Overlooking the reset correlation. A bug that breaks only on a mid-traffic reset is reset handling; don't chase it as a steady-state bug.
- Not checking the basics for no-stimulus. No driving means a null vif, not pulling (
get_next_item), or not completing (item_done) — check these directly. - Testing on one simulator. A race passes on your simulator and fails elsewhere; exercise multiple simulators and seeds to expose it.
Senior Design Review Notes
Interview Insights
With a localize-discriminate-confirm loop centered on the waveform, because the driver acts on signals, so its bugs are visible on the pins. First localize: look at the waveform to see what the driver did — is the driven value right or wrong, does a signal change at the clock edge, is the bus idled during reset. The waveform shows the driver's behavior directly, so localization is primarily visual, unlike the sequencer whose state is hidden and needs messages. Second, discriminate by signature. The most useful question is whether the wrong value is on the pins or only in the transaction: if it's on the pins, the driver drove wrong, which is a conversion mis-map or a race; if the pins are right but the transaction is wrong, sampling failed, like an unsampled read. Then, is it nondeterministic across simulators — yes means a clock-edge race, no means a deterministic logic bug. Does it break only on a mid-traffic reset — that's reset handling. Does anything drive at all — no means a null virtual interface, not pulling with get_next_item, or not completing with item_done. Third, confirm on the waveform and transaction recording, which is direct because the behavior is visible — you see the wrong value, the edge-aligned change, the non-idle reset, rather than inferring it. Then apply the mechanism-specific fix and re-run. The method is the same localize-discriminate-confirm as for sequencers, but the tool is the waveform, and the driver's advantage is visibility: you look rather than interrogate, so it converges faster. The signatures — X at the edge is a race, wrong-but-stable is a conversion mis-map, pins-right-txn-wrong is sampling, nothing on the pins is not driving — are the vocabulary you read off the waveform.
Because the driver acts on the pins, so its entire behavior is recorded on the waveform and visible directly, unlike a sequencer whose state is hidden inside class objects. A sequencer bug is like a locked-room mystery — the arbitration queue, the grants, the relevance are internal class state, so you have to add messages to reconstruct what happened. A driver bug is the opposite: the driver's whole job is driving signals, so the waveform is a full recording of what it did, and you can see the wrong value, the signal that changed at the clock edge indicating a race, the bus that wasn't idled during reset. So the natural first step is to look at the pins, because the evidence is there. The waveform also directly answers the key driver questions: whether the wrong value is on the pins or only in the transaction, by comparing the waveform to the recorded transaction; whether a signal is racing, by seeing it change at the edge or showing X; whether reset is handled, by seeing if the bus idles during reset. Each bug has a readable signature on the waveform — X at the edge is a race, wrong-but-stable is a conversion mis-map, active-during-reset is a reset bug, nothing-on-the-pins is not driving — so reading the signature names the mechanism. This is why driver debugging is waveform-centric: confirmation is looking, not inferring, which makes it faster than the message-based interrogation a sequencer requires. Transaction recording complements the waveform by showing the transaction box above the pins, so you can compare what the transaction holds against what's actually on the wires, which separates conversion and race bugs, where the pins are wrong, from sampling bugs, where the pins are right but the transaction is wrong. So the waveform is primary because the driver's bugs are out in the open on the signals.
Exercises
- Read the signature. For a driven signal that shows (a) X at the edge, (b) a wrong-but-stable value, (c) the bus active during reset, (d) nothing — name the mechanism each indicates.
- Split pins vs txn. A scoreboard sees wrong read data; the pins show the correct DUT data. Name the mechanism and the fix.
- Discriminate the race. A driver passes locally but fails on the farm with correct logic. Name the signature, the mechanism, and the fix.
- Walk the loop. Describe how you'd localize, discriminate, and confirm an ambiguous "wrong data" driver symptom on the waveform.
Summary
- Driver debugging is a waveform-centric symptom-to-mechanism method — because the driver acts on signals, its bugs are visible on the waveform (unlike the sequencer's hidden state, debugged with messages, Module 11.5).
- Localize on the waveform and route by signature: wrong value → on the pins (conversion/race) vs only in the transaction (sampling); nondeterministic → clock-edge race; breaks on a mid-traffic reset → reset handling; no stimulus → null vif / not pulling / not completing; wrong timing → wait states / latency.
- Discriminate with signature questions — value on pins or in txn? deterministic across simulators? breaks on reset? drives at all? — each answered from the waveform or the determinism/reset correlation, splitting the cause space fast.
- Read the signatures off the pins: X at the edge = race; wrong-but-stable = conversion mis-map; active during reset = reset bug; absent = not driving — the waveform shows the mechanism, so confirmation is looking, not inferring.
- The durable rule of thumb: debug a driver waveform-first — localize on the waveform (the behavior is visible), discriminate by signature (pins-vs-txn, determinism, reset-correlation, drives-at-all), and confirm on the waveform (and transaction recording) — because the driver's bugs are out in the open on the pins, so the localize-discriminate-confirm method converges by looking, regardless of which mechanism it is.
Next — Passive Observation: the driver drives the pins; the monitor does the opposite — it observes them, never driving. The next module opens with the monitor's defining principle: passive observation, and why a monitor must watch the interface without ever influencing it.