Part 2 answers: How do I see what the hardware actually did?
Most debugging time is spent looking at the wrong signals. This part reads waveforms and logs with a question in mind, places probes where they pay, compares a good run with a bad one, and makes designs observable before they need to be.
Read a waveform to answer one question, not to browse.
Find the first difference between a passing and a failing run with a script.
Choose dumps, logs, assertions and counters that will answer the next question.
9 lessons, about 95 min
The part on one page, taken from each lesson's Remember box.
Reading a Waveform with a Question
Before you open the waveform, write the question: which signal, which cycle, what value you expected.
Keep the lanes the questioned signal is computed from. A lane you cannot call right or wrong does not belong on the screen.
If every input is right in the window, or wrong in every slot, the cause is outside the window. Ask a new question about an earlier cycle.
Rule of thumb: if you cannot say what value a lane should have, it is not part of your question.
Dump Strategy
Dump size grows with scope times time. Narrow one so you can afford the other: one word for the whole run, or the whole design for a few hundred cycles.
Open the window at the event your question is about, not a round number of cycles before the failure.
A deterministic failure is cheap to run twice: once without a dump to learn the cycle, once with the window you need.
Rule of thumb: never dump everything from time zero unless the run is short enough that it does not matter.
Logging That Helps
One event per line, starting with the cycle, then level, source and id, then key=value fields in a fixed order.
Use the ids the bench already prints, so a log line and a FAIL message can be joined with one grep.
Log at transaction boundaries, not inside every always block, and choose levels so the default log stays readable.
Rule of thumb: if you cannot grep for it, you did not log it.
Assertions as Always-On Probes
Place a check where a wrong value is first created, such as a register loading from its inputs, not where the value is finally consumed.
A failing assertion's message should be a bug report: the rule, the cycle, the value it saw and the value it expected.
Icarus 13 rejects or silently drops concurrent SVA. Run assertions in a simulator that executes them, or write the same rule as clocked code.
Rule of thumb: an assertion that cannot fire in your simulator is not a probe; prove it fires once before you trust its silence.
Scoreboards Report Late
A scoreboard reports when it next observes the damage, not when the damage is done. The gap depends on the stimulus, not the bug.
"Last written by" in a message is the model's belief. Check that the hardware agrees before you open that transaction.
Check each transaction when it completes, in order where order is defined, and keep the end-of-test compare as the backstop.
Rule of thumb: never start a hunt at the scoreboard's cycle; start at the transaction it blames, and check that it really happened.
Comparing a Good and a Bad Run
Diff runs that share their stimulus: the same test and seed on two commits, or two simulators. Two seeds differ from the first cycle and tell you nothing.
Log the bus, not just the verdict. A diff of PASS against FAIL has one line; a diff of transactions points at a cycle.
The first difference is not always the cause. Sort differences by when they start and when they heal; follow the one still open when it reaches a value something uses.
Rule of thumb: compare like with like: one change between the runs, the same stimulus, and follow the first difference that is still open when something uses it.
X in a Waveform
X means unknown: the chip will have a 0 or a 1 there, and the simulation cannot say which. It never means "don't care".
An if/else on an X condition takes the else branch and shows a clean value. A clean output does not prove a clean input.
Check for X where a value is used (bus write data, enables, selects), not only at the scoreboard, which may read it thousands of cycles later.
Rule of thumb: treat every X that reaches a control or an output as a bug until a reset or a write proves it harmless.
Counters and Performance Probes
Pair every work counter with a wait counter: handshakes and cycles spent waiting for one. Their ratio is where the time goes.
Totals and averages hide tails. Keep a histogram with power-of-two buckets and a maximum for any latency someone cares about.
In a hang, print the counters every few hundred cycles. The ones that stop are the finished work; the one still climbing is the stage that waits.
Rule of thumb: for a performance question, count work, waiting and the worst case before you open a waveform.
Observability by Design
Silicon shows only what you designed to be seen. Decide the questions you will ask of a hung chip, then build the registers that answer them.
Keep the first failure: sticky errors with a first-error record, and a trace that freezes on the trigger. Later errors must not overwrite the evidence.
Debug state must be readable when the main bus is hung, through a side-band port. Debug logic is logic: it needs tests and an area budget.
Rule of thumb: for every way the block can fail, name the register that will show it on silicon, and a path to read it that does not need the block to work.
Practice question: Waveform debug: a write transaction timeout
Practice question: FSM illegal-state debugging: waveform analysis and assertions
Code lab: Plusargs with $value$plusargs
Code lab: String formatting
Practice question: Inline RTL checkers: X detection, handshake liveness and bindable monitors
Interview: run a mock interview on this topic