BUG_1 makes a DCRouter FIFO's full flag rise one entry late, so a ninth word overwrites the oldest. The failing test shows corrupted flits at an output. A quick fix stops the corruption: block the push when the count equals 8, inside the push logic. The test passes. The full flag is still wrong, in_rdy still goes low one push too late, and the next test that relies on in_rdy to throttle a source fails in a different, more confusing way. The symptom was fixed; the cause was not.
How do you verify a fix?
The obvious check is that the failing test now passes. That shows only that this one stimulus no longer triggers this one symptom. A fix can mask the symptom, move the bug elsewhere, or break something nearby, and the failing test is blind to all three. Fixes are design changes, and they deserve the same scrutiny as any other change (lesson 9.7).
Step | Question |
|---|---|
Root cause | Which line, which rule, which assumption is wrong, and why did it happen? |
Fix at the cause | Does the change correct the rule's implementation, not just the symptom? |
1. The failing test | Does it now pass, for the right reason (check the waveform, not just the verdict)? |
2. The rule | Is the rule now true in every case: an assertion or formal property for it? |
3. The neighbours | What else uses the changed logic? Tests and checks for those paths |
4. The regression | Does everything that passed before still pass? |
Sweep | Is the same mistake elsewhere? (lesson 11.7) |
// snippet: BUG_1, symptom fix (wrong) and root-cause fix (right)
// wrong: refuse the ninth push inside the FIFO; the FIFO stays consistent, but full is
// still late, so in_rdy tells the source its flit was accepted and the flit is dropped
wire push_ok = push && (count != 4'd8); // used for the write, the pointer and the count
// right: fix the full flag itself, which in_rdy and push both depend on
assign full = (count == 4'd8);The wrong fix ends the corruption and even satisfies the formal property "count never exceeds 8". But the source sees in_rdy high, believes its ninth flit was accepted, and moves on: the scoreboard now reports a lost flit instead of a corrupted one. The rule broken was R1 (in_rdy low when full); only fixing full itself makes it true for every consumer.
// snippet: BUG_9, symptom fix (wrong) and root-cause fix (right)
// wrong: never send in a cycle where a credit returns, so the buggy counter never sees both
send[o] = lock[o] && !empty[g] && crd_ok && !sleeping && !out_crd[o];
// right: count a send and a return in the same cycle, as R7 says
credits[o] <= credits[o] + (out_crd[o] ? 3'd1 : 3'd0) - (send[o] ? 3'd1 : 3'd0);The wrong fix is the more instructive one. Credits are now conserved: every functional test passes and the formal conservation property (lesson 7.9) passes too. But under streaming traffic a credit returns almost every cycle, so the output stops sending in those cycles and its throughput can halve. No functional check sees it; only a throughput check (lesson 5.9, feature F26 in lesson 9.8) does. A symptom fix can satisfy the rule it was aimed at while breaking a different one.
The fix is in the logic the rule describes, not in a downstream guard.
The explanation is complete: it accounts for every observed symptom.
A property or assertion for the rule passes, for all inputs if formal applies.
Neighbouring paths are re-verified, not just the failing one.
The fix is small: a large fix for a small bug is a warning sign.
Practice | Purpose |
|---|---|
Root-cause analysis recorded with every fix | The cause, not just the change, is reviewed |
A permanent regression test plus a rule check per fixed bug | This bug and its relatives stay fixed |
Fix reviews by someone other than the author | Symptom patches caught before they land |
Fix-induced bug rate tracked | Late fixes judged by how often they break something |
Late, rushed fixes are a familiar source of new bugs. A team (illustrative) fixed a deadlock by adding a timeout that released the lock after 1,000 cycles. The deadlock test passed. The timeout also fired during legitimate long transfers, corrupting them; the real cause, an ordering error in the request logic, remained.
The cause is the full flag; in_rdy depends on it too, so the source is still told the FIFO can accept a word when it cannot.
A throughput check under streaming traffic: the wrong fix conserves credits, so functional checks and the conservation proof pass, but it stops sending whenever a credit returns.
A passing failing-test does not verify a fix.
Find the root cause, fix it there, and check the rule, the neighbours and the regression.
Treat fixes as changes: review them, track the bugs they introduce, and sweep for the same mistake.