Chapter 15 of 15
Writing the Bug Down, and Closing the Loop
A confirmed root cause isn't the end of a debugging session -- a good write-up is, using uvm-advanced ch1's own already-published bug callout as the worked example. Closes this module, and the whole dv-methodology track, by tracing spec to plan to coverage to test to bug to written record and back into the plan.
Ch3 ended with a confirmed root cause and a minimal, targeted fix. That's not actually the end of a debugging session — a fix nobody wrote up is a diff with no memory attached to it, ready to be rediscovered the hard way by someone (possibly the same someone) six months later. This closing chapter is two things at once: how to write a bug up well, and — since this is the last chapter of the last module — the place where three modules that read like three separate topics turn out to be one skill, applied at three different points in the same cycle.
The anatomy of a good bug report
uvm-advanced ch1 already published one, as a callout inside the chapter that introduced axil_regfile, not as a separate document — worth reading again with an eye for its structure rather than its content this time:
A real bug this exact design hit, worth knowing about: an earlier version of
s_axi_awready/s_axi_wreadyalso gated on!write_fire, meaning "don't accept a new AW/W the instant the current one fires." That reads as reasonable — butwrite_firedepends onaw_fire, which depends ons_axi_awreadyitself. Simulating it (not just reading it) turned up the problem immediately: it's a genuine combinational loop,awready → write_fire → awready, with no stable solution — exactly the "no combinatorial paths between input and output signals" rule the spec states outright (A3.2). The fix above depends only on registered state (aw_have/w_have/s_axi_bvalid), which turns out to already be enough to prevent overlap. The lesson generalizes past this one signal: a handshake-ready signal that feels like it should react to "did a transfer just happen" is a natural place to accidentally close a combinational loop, because "did a transfer just happen" is usually computed from that same ready signal.
Six things, in order, each doing a different job:
- What looked reasonable — the buggy version's own justification, stated fairly, not as a strawman: "don't accept a new AW/W the instant the current one fires" is a sensible-sounding intent.
- The root cause — the exact dependency chain, named precisely:
write_firedepends onaw_fire, which depends ons_axi_awready. - Why it wasn't obvious from reading alone — "simulating it (not just reading it) turned up the problem immediately." The bug hid successfully in a code review; it didn't hide from a simulator.
- Grounding in the spec — not just "this broke," but which specific rule it violated (A3.2), citable rather than asserted.
- The fix — stated plainly, and precise about why it's sufficient, not just that it happened to work.
- The generalizable lesson — the sentence that outlives this one signal: any handshake-ready signal computed from "did a transfer just happen" is a structurally similar trap, on this DUT or any other.
Every one of those six is doing real work. Cut any single one and the write-up gets meaningfully worse: drop the "what looked reasonable" and the next reader can't tell why anyone wrote it that way in the first place; drop the generalizable lesson and the write-up protects exactly one signal, on exactly one DUT, forever.
Why write it down at all
The same argument Test-Planning ch1 made for a plan applies here, aimed at a different artifact: a plan lets someone other than its author check the strategy before code gets written; a bug write-up lets someone other than its finder learn the lesson before the same mistake gets made again. A fix with no explanation attached is invisible the moment nobody remembers the story behind it — the diff says what changed, never why it needed to, and the next engineer looking at a different ready/valid signal that happens to reference "did a transfer just happen" has no way to know this exact trap already has a name.
Closing the loop
Three modules, read end to end, turn out to trace one cycle rather than cover three unrelated topics:
Spec → plan. Test-Planning ch2 decomposed axil_regfile's spec into a feature list; ch3 generated scenarios systematically rather than by whatever came to mind; ch4 ranked them by risk. AXI4-Lite's A3.2 rule — no combinational paths between input and output signals — is part of that same spec, though worth being honest about its shape: it's a structural constraint on how the RTL is built, not a stimulus scenario a directed test drives and checks the way a register write is. Not every spec requirement fits ch2's feature-list format the same way; this is a real example of the kind that doesn't, and pretending otherwise would be a worse lesson than naming the exception.
Plan → build → measure. uvm-advanced ch1 built axil_regfile; the Coverage module measured what actually got exercised, chapter by chapter, against real code.
Measure/build → diagnose. Simulating the DUT — not just reading it — is what actually surfaced the combinational loop, exactly the discipline ch1-3 of this module named: isolate the dependency chain, let the simulator's own scheduler serve as instrumentation, hypothesize the exact cycle, confirm with the smallest change that removes it.
Diagnose → write down. The callout quoted above — already published, already doing its job, for as long as anyone reads it.
Write down → back into the plan. This is the step that's easy to skip, and where this chapter has something concrete to add rather than only naming the theory: the generalizable lesson from that callout — a handshake-ready signal computed from "did a transfer just happen" is a structural trap — belongs as a standing review item for any future ready/valid logic this site's DUTs ever grow, not as a historical footnote sitting inside one chapter about one signal. That's what "back into the plan" concretely means here: not a new bug this chapter is claiming to have found (nothing in this module's real history supports inventing one), but a lesson already earned turning into a checklist the next design gets checked against before it needs to earn the same lesson twice.
That full cycle — spec, plan, build, measure, diagnose, write down, back into the plan — is what "coverage-driven judgment," "test-planning mindset," and "debugging intuition" turn out to be: three names for the same underlying discipline, looked at from three different points in its loop. None of the three modules in this track needed an invented example to teach it. axil_regfile's own history already had one.
Summary
- A good bug write-up has six parts: what looked reasonable, the exact root cause, why it wasn't obvious from reading alone, which spec rule it violated, the fix and why it's sufficient, and a lesson that generalizes past the one signal it was found on.
- Writing a bug down is a transferability argument, the same shape as Test-Planning ch1's argument for writing a plan down: it lets someone other than the finder learn the lesson, instead of the same mistake waiting to be rediscovered.
- Not every spec requirement fits neatly into a feature-list scenario — AXI4-Lite's no-combinational-loops rule (A3.2) is a structural constraint on the RTL itself, not a stimulus a directed test drives, and naming that exception honestly is better than forcing it into a shape it doesn't fit.
- "Back into the plan" doesn't require inventing a new bug — the combinational loop's generalizable lesson becomes a concrete review-checklist item for any future ready/valid signal, turning one incident's lesson into a standing safeguard.
- Coverage-driven judgment, test-planning mindset, and debugging intuition are one discipline applied at three points in the same cycle — spec, plan, build, measure, diagnose, write down, back into the plan — not three separate skills that happen to share a DUT.
The combinational-loop callout states that 'simulating it, not just reading it, turned up the problem immediately.' Which part of the six-part anatomy does this sentence serve?
Why does this chapter argue writing a bug up matters, beyond just fixing it?
This chapter's 'back into the plan' step doesn't claim a new bug was found in axil_regfile. What does it propose instead?
Having read all three dv-methodology modules (Coverage, Test-Planning Mindset, Debugging Intuition), what does this closing chapter argue is the actual relationship between them?