A test reports that traffic stopped: flits are still pending after thousands of cycles. There is no corrupted data and no assertion message, only silence. Hangs are intimidating because nothing points at the cause. They are also the most systematic failures to debug, because every hang has something waiting, and every wait has a condition that never became true.
Walk me through a stuck valid.
The obvious approach scrolls through the waveform looking for something odd. In a long simulation with many signals, everything looks odd and nothing points anywhere. The method is to start from the one thing known to be stuck and ask a single question repeatedly: what is it waiting for?
Step | Question |
|---|---|
1. Find the waiting handshake | Which valid is high and has been high too long? |
2. Why is it not accepted? | Which condition of ready is false? |
3. Why is that condition false? | Look up its logic; which input to it is holding it false? |
4. Repeat | Follow that input to its own logic, until you reach state that should have changed |
5. Why did that state not change? | The logic that should have changed it is the bug, or its input never came |
The testbench builds DCRouter with Bug Zoo BUG_21, sends one packet, lets the router fall asleep, then offers a packet on input 3 alone, and records what the pins show.
// uses: dcrouter BUG_21
// Lesson 13.3: a hang to debug. DCRouter is built with Bug Zoo BUG_21. One packet crosses the
// router, the router falls asleep after the idle threshold, then input 3 alone offers a
// packet. The test records what the pins show for 40 cycles and passes when the hang is
// reproduced exactly as reported: input 3 holding valid, in_rdy low, the router asleep.
module tb;
`include "dcr_env.svh"
int held = 0;
initial begin
reset_dut();
queue_pkt(0, 1, 0, 2);
wait_drain();
repeat (12) @(posedge clk); // idle long enough to sleep
queue_pkt(3, 2, 0, 2); // input 3 alone offers a packet
repeat (40) @(posedge clk) if (in_vld[3] && !in_rdy[3] && sleeping) held++;
if (held >= 38) $display("PASS: hang reproduced: input 3 held valid for %0d of 40 cycles with in_rdy low while the router stayed asleep", held);
else $display("FAIL: no hang (input 3 stalled for %0d cycles)", held);
$finish;
end
endmoduleIt prints:
PASS: hang reproduced: input 3 held valid for 40 of 40 cycles with in_rdy low while the router stayed asleepStep | Observation | Next question |
|---|---|---|
1 | in_vld = 8: input 3 holds valid from slot 15 to the end | Why is it never accepted? |
2 | in_rdy = 0 for every input | in_rdy[i] = !full && port_en && !sleeping: which term is false? |
3 | FIFOs are empty and ports are enabled; sleeping = 1 | Why does the router stay asleep? |
4 | sleeping clears only when wake is true | What drives wake? |
5 | The RTL computes wake from in_vld | Read it: |
logic wake;
`ifdef BUG_21
assign wake = |in_vld[2:0]; // BUG_21: port 3 cannot wake it
`else
assign wake = |in_vld;
`endifWith BUG_21 defined, wake looks only at in_vld[2:0]. Input 3 can never wake the router. Five questions from a silent hang to one line of RTL, without reading any logic that was not on the path. In this course's testbenches, the R13 wake assertion (lesson 5.3) reports the same thing at the cycle it happens, which is exactly why such assertions are worth writing.
For wrong data, the same method runs backwards from the wrong value: where did this value come from, one register stage at a time, until the stage where right became wrong.
For lost data, follow the item: where was it last seen, and which handshake failed to pass it on?
In all cases, follow one signal's dependencies, not the whole waveform.
Practice | Purpose |
|---|---|
Watchdogs that report which handshakes are stalled at timeout | The starting point of the wait is printed, not searched for |
Assertions on progress ("valid eventually accepted within N cycles") | Hangs reported where and when they begin |
Waveform tools that trace a signal's drivers | Each "why" answered with one click |
A team (illustrative) debugged a system hang by inspecting hundreds of signals in the last thousand cycles of a long simulation. The bus that stalled first had stopped 200,000 cycles earlier; everything near the end was a consequence. A watchdog printing the oldest stalled handshake would have pointed straight at it.
sleeping: the FIFOs are empty and the ports enabled, but the router is asleep.
R13's wake rule: if sleeping and any in_vld is high, sleeping must be low in the next cycle.
Every hang has something waiting; follow what it waits for, one dependency at a time.
For corruption, follow the wrong value backwards stage by stage.
Progress assertions and watchdogs turn hangs into precise reports.