Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Part 0 · What Debugging Is6
    • The Debug Loop
    • Symptom, Fault and Cause
    • The Cost of a Bug by Stage
    • Guessing vs Searching
    • Meet dbglab
    • A Bug Report Others Can Act On
  • Part 1 · Reproduce8
  • Part 2 · Observe9
  • Part 3 · Localise10
  • Part 4 · Hypothesise and Test6
  • Part 5 · Bug Classes16
  • Part 6 · Debug by Stage9
  • Part 7 · Hangs and Deadlocks7
  • Part 8 · Intermittent Failures7
  • Part 9 · Fix, Prove, Prevent7
  • Part 10 · Case Studies12
  • Part 11 · Debug as a Team5
  • Part 12 · Interview Prep5
Loading lesson

‹Part 0 · What Debugging Is
  1. Topics
  2. The Art of Hardware Debug
  3. Part 0 · What Debugging Is
  4. A Bug Report Others Can Act On

Part 0 · What Debugging Is · Working (W)

A Bug Report Others Can Act On

What a bug report must contain so someone else can reproduce and act on it without asking you: the required fields, and a bad and a good report for the same real dbglab failure, with the good report's repro run from a fresh checkout.

On this page

    Why this matters

    A report that says "the irq test is broken, probably the arbiter" costs the next engineer an hour of questions before any debugging starts, and a wrong guess in it can send the hunt the wrong way. A report with the symptom, the exact repro and what was already ruled out lets anyone start at the Reproduce step.

    After this lesson you can: write a bug report whose title states the symptom, whose repro command fails on someone else's machine from a clean checkout, and which separates what was observed from what is guessed.

    Note

    Level: Working (W) · Debug-loop step: Report

    Before this: The Debug Loop, Meet dbglab

    A ticket at 9 a.m.

    You own dbglab's CSR block. A ticket arrives overnight: "irq test broken after the DMA changes, please fix ASAP. Probably the arbiter giving the DMA priority. Fails in regression." You do not know which commit, which seed, which simulator or what "broken" means. You do not even know whether it fails on your machine. How long before you can start debugging, and how much of what it says can you trust?

    What people usually do, and why it fails

    A report is usually written in a hurry, by someone who has just spent an hour on the failure and assumes the reader shares their context. Three things go wrong. The facts are missing: no commit, seed or command, so the reader cannot reproduce it and starts a round of questions. The theory replaces the symptom: "probably the arbiter" in the first line sends the reader to the arbiter, and if the guess is wrong that is where their morning goes. And the work already done is lost: the reporter tried other seeds and the other simulator, but the reader does not know that and repeats it.

    The report is the first step of the debug loop. Its output, the failure as expected versus actual with the command that shows it, is the input to every step after it. When it is wrong, the whole loop starts in the wrong place.

    A report is finished when its reader can complete the next step, Reproduce, without asking anything.

    The method: the fields a report needs

    Field

    What goes in it

    Why the reader needs it

    Title

    The symptom: which test, what was wrong, where. No cause.

    It is what people search, skim and triage on. A cause in the title is believed before it is tested.

    Expected versus actual

    What the specification or the test says should happen; what happened, with the exact message

    "Broken" is not a fact. The gap between the two is the bug's definition.

    Repro command

    A command that can be pasted: repository and commit, test, seed, options, simulator and version

    It turns the report into an experiment anyone can run. It is also the command that will prove the fix.

    First observed

    The first run that failed and the last that passed, with their commits or dates

    It bounds the search in history before anyone opens a waveform.

    Frequency

    Every run, or n out of m, over which seeds and simulators

    A deterministic failure and a 1-in-500 failure are debugged in completely different ways.

    Attachments

    The log, the dump or how to make one, the bisect log if any

    Evidence the reader would otherwise have to regenerate.

    Already ruled out

    What you tried and what each result showed

    It stops the next person repeating your hour, and tells them which explanations are already dead.

    Guesses (optional, labelled)

    A suspected area, marked as a guess, kept below the facts

    Useful context, as long as nobody mistakes it for a finding.

    Four rules for filling them in:

    1. Copy, do not paraphrase. The FAIL line goes in exactly as printed. Paraphrase loses the cycle, the address and the value.

    2. Run the repro from a clean checkout before you file. A command that only fails in your working tree, with your local edits or stale build, is not a repro.

    3. Separate observed from inferred. Facts first; anything you concluded goes under its own heading and says how sure you are.

    4. One bug per report. Two failure signatures are two reports until someone shows they share a cause. They can link to each other.

    The picture: the same failure, reported twice

    Here is the failure behind the 9 a.m. ticket, reported first the way it arrived and then the way it should have.

    Warning

    Bad report (illustrative). Title: irq test broken after DMA changes

    irq test broken after the DMA changes, please fix ASAP. Probably the arbiter giving the DMA priority. Fails in regression.

    Field

    Bad report

    Good report

    Title

    irq test broken after DMA changes

    irq_w1c: irq asserts after the first DMA copy while CTRL enables no interrupt (every seed, HEAD 49c5f04)

    Expected versus actual

    "broken"

    Expected: the register map gives CTRL a reset value of 0, so the copy sets STATUS.done and irq stays low. Actual: FAIL test=irq_w1c seed=1 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt. (CTRL=00 in the message is the bench's model of what software wrote, not a register read.)

    Repro

    none

    build-history.sh <dir> && cd <dir> && git checkout -q 49c5f04 && tb/run.sh irq_w1c 1 with Icarus Verilog 13.0; exits 1

    First observed

    "after the DMA changes"

    Nightly of 2026-03-10 at 026f8bf (fails at cycle 116 there). Passed on 2026-03-09 at 2e63466, the commit just before; 026f8bf is the only change between them.

    Frequency

    "fails in regression"

    Every run: seeds 1, 2 and 3 all fail at cycle 84; same in Verilator 5.048

    Attachments

    none

    build/icarus-irq_w1c-1.log; a dump with DUMP=1 on the same command

    Ruled out

    nothing stated

    Not the simulator (Verilator identical). Not the seed (1 to 3). Not a local setup (fresh build of the history). Not the bench: 026f8bf changes only rtl/csr_block.sv and rtl/dbglab_pkg.sv.

    Guess

    "probably the arbiter", in the first line

    None offered. 026f8bf is a CSR refactor, so the CSR block's reset values are worth a look first (a guess, not a finding).

    Every claim in the bad report is either unfalsifiable ("broken") or a theory ("the arbiter"), and its one hint, "after the DMA changes", is wrong: the commit that changed the result touched no DMA code. The good report is longer, and every line is something the reader would otherwise have had to find out.

    Do it: build the good report from real runs

    Copy the symptom and make the repro

    Start from the regression's FAIL line, then turn it into a command that works from nothing. dbglab rebuilds its history with fixed hashes, so a fresh folder is a fair stand-in for a colleague's machine:

    Terminal
    $ R=$(mktemp -d)/repro
    $ scripts/data/hardware-debug-course/dbglab/build-history.sh $R > /dev/null && cd $R && git checkout -q 49c5f04 && tb/run.sh irq_w1c 1; echo "exit $?"
    dbglab: test=irq_w1c seed=1
    FAIL test=irq_w1c seed=1 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt
    exit 1

    Same message, same cycle, exit status 1, on a tree built from scratch. That one line is the most valuable thing in the report. Note what it pins: the repository (rebuilt from its patches), the commit by hash, the test, the seed and, by default, the simulator. The version of the simulator goes next to it, because a different version can change the result.

    terminal
    iverilog -V 2>&1 | head -1      # Icarus Verilog version 13.0 (stable) (v13_0)
    verilator --version             # Verilator 5.048 2026-04-26

    Measure the frequency

    Terminal
    $ tb/run.sh irq_w1c 2
    dbglab: test=irq_w1c seed=2
    FAIL test=irq_w1c seed=2 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt
    $ tb/run.sh irq_w1c 3
    dbglab: test=irq_w1c seed=3
    FAIL test=irq_w1c seed=3 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt
    $ tb/run.sh irq_w1c 1 verilator
    dbglab: test=irq_w1c seed=1
    FAIL test=irq_w1c seed=1 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt

    Every seed and both simulators, at the same cycle. That is a deterministic failure that does not depend on the stimulus, which already tells the reader something: whatever is wrong happens on the test's fixed opening sequence, before random choices matter.

    Find when it was first seen

    dbglab's regression runs nightly, one commit a day. Run the test at the last nightly that passed and the first that failed:

    Terminal
    $ git checkout -q 2e63466 && tb/run.sh irq_w1c 1
    dbglab: test=irq_w1c seed=1
    PASS test=irq_w1c seed=1
    $ git checkout -q 026f8bf && tb/run.sh irq_w1c 1
    dbglab: test=irq_w1c seed=1
    FAIL test=irq_w1c seed=1 cycle=116: IRQ: irq=1 while CTRL=00 enables no interrupt
    $ git show --stat --format="%h %ad %s" --date=short 026f8bf
    026f8bf 2026-03-10 csr: collect register reset values in dbglab_pkg
    
     rtl/csr_block.sv  | 4 ++--
     rtl/dbglab_pkg.sv | 7 +++++++
     2 files changed, 9 insertions(+), 2 deletions(-)

    Two facts for the report. First observed at 026f8bf, last passed at 2e63466, with no commit between them. And the failing cycle is 116 there and 84 at HEAD: the same message at a different cycle. Say so in the report, so nobody greps for cycle=84 in old logs and concludes the failure is new. The --stat line belongs in ruled out: the change touched no bench file and no DMA file.

    Here the history was short enough to check by hand. With a longer gap you would let bisection in history find the commit and attach its git bisect log.

    Attach the evidence

    Terminal
    $ git checkout -q main && tb/run.sh irq_w1c 1
    dbglab: test=irq_w1c seed=1
    FAIL test=irq_w1c seed=1 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt
    $ cat build/icarus-irq_w1c-1.log
    dbglab: test=irq_w1c seed=1
    FAIL test=irq_w1c seed=1 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt
    FATAL: tb/dbglab_tb.sv:57: test failed
           Time: 845000  Scope: dbglab_tb.fail
    $ DUMP=1 tb/run.sh irq_w1c 1
    dbglab: test=irq_w1c seed=1
    FAIL test=irq_w1c seed=1 cycle=84: IRQ: irq=1 while CTRL=00 enables no interrupt
    $ wc -c < dbglab.vcd
       49131

    Back at HEAD, the log is four lines, so paste it. The dump is small here (49 KB); on a real project, attach the command that makes it rather than gigabytes of waveform.

    What the evidence says

    Look at what the good report already did to the search. Every seed, both simulators rules out the stimulus and the tool. One commit separates pass from fail, touching only two CSR files puts the cause, almost certainly, inside a change of nine added and two removed lines. The reader can start at Observe with a short list of suspects, and the bad report's arbiter theory is dead before anyone looked at the arbiter. None of this needed a waveform. Writing a careful report is often the first half of localising the bug.

    Traps

    Common pitfall

    The repro that only works on your machine. Local edits, a stale build directory, an environment variable or a plusarg you forgot you set. Run the command in a fresh checkout, or a fresh shell at least, before filing. dbglab's XRAND and DUMP variables are exactly the kind of thing that silently changes a run.

    Common pitfall

    Reading a message at face value. CTRL=00 looks like a register read, but it is the bench's model of CTRL. A report that says "CTRL reads 0 yet irq fires" states something nobody measured. Know what each field of your bench's message means before you quote it as a fact.

    Why this matters

    Senior lens. When you triage, send back reports that lack a repro command, politely and at once; an hour of the reporter's time saves a day of yours. When you file, assume the reader is good, busy and has never seen your setup.

    Keep guesses out of titles. Titles are copied into status reports, meeting notes and commit messages, and a wrong cause in a title outlives the bug. Part 11 covers bug tracking fields and severity for a whole team.

    Interview

    Asked: "What makes a good bug report?"

    Weak answer: It should describe the problem clearly and have enough detail for the designer to understand what went wrong, with the log attached.

    Strong answer: Someone else must be able to reproduce it without asking me anything. So: a title that states the symptom, not my theory; expected versus actual with the exact failure message; a command with the commit, test, seed and tool version that I have run from a clean checkout; when it was first seen and last passed; how often it fails across seeds and simulators; the log and how to get a dump; and what I already ruled out. Any guess goes last and is labelled as a guess.

    Follow-ups: (1) What do you do with a report that only fails one run in a thousand? (2) How do you report two failures that might be the same bug?

    Check yourself Your repro command passes on your colleague's machine but fails on yours. Name three things to check before you file.

    Whether you are on the same commit with no local edits (git status, git rev-parse HEAD); whether a stale build or a leftover environment variable or plusarg is changing your run (for dbglab, DUMP, XRAND or an old build/ folder); and whether you use the same simulator and version. Rerun in a fresh checkout and a fresh shell. If it still differs, that difference is a finding and belongs in the report.

    Check yourself A regression shows two failing tests with different messages after the same commit. One report or two?

    Two, linked to each other. Different signatures may be different bugs, and one report invites someone to close both when only one is fixed. If debugging later shows a shared cause, closing one as a duplicate of the other takes a minute.

    Remember

    • Title the symptom, not your theory: test, what was wrong, where it was seen. A guessed cause in the title steers everyone who reads it.

    • The repro is a command anyone can paste: commit, test, seed, tool and version. Run it from a clean checkout before you file.

    • Say what you ruled out and how (other seeds, the other simulator, the last good commit). It saves the next person repeating it.

    Rule of thumb: if the reader has to ask you a question before they can reproduce it, the report is not finished.

    How this was checked

    dbglab at HEAD 49c5f04, git 2.54.0, Icarus Verilog 13.0, 2026-10-17. The good report's repro, run as shown in a fresh folder: build-history.sh <dir> && cd <dir> && git checkout -q 49c5f04 && tb/run.sh irq_w1c 1 failed at cycle 84 with exit status 1.

    Ruled-out checks, same repository: tb/run.sh irq_w1c 2 and 3 fail at cycle 84; tb/run.sh irq_w1c 1 verilator (Verilator 5.048) fails identically; at v0.1 (fa71142) and at 2e63466 it passes; at 026f8bf it fails at cycle 116. git show --stat 026f8bf lists only rtl/csr_block.sv and rtl/dbglab_pkg.sv. DUMP=1 tb/run.sh irq_w1c 1 wrote a 49,131-byte VCD.

    Limits: the bad report and the nightly-regression framing are illustrative, written for this page (dbglab has one commit per day, so a commit stands for a nightly). Field names vary between bug trackers; the content is what matters.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • Part 0 · What Debugging Is

    • The debug loop: Report is step one

    • Part 1: Reproduce, recording everything needed to replay

    • Bisection in history: finding the first bad commit

    • Part 11: debug as a team

    Previous

    6 / 107

    Next: Part 1 · Reproduce

    Up next

    Up nextPart 1 · Reproduce

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Debug Loop
    • Symptom, Fault and Cause
    • The Cost of a Bug by Stage