Debug case: stale IDs after reset
Find why a correct response fails after a mid-test reset, and make the scoreboard state follow the DUT through reset.
- Lesson
- 2.7
- Time
- 25 min
- Runs on
- Verilator
- Check
- 5 questions
A test applies reset in the middle of traffic, as the reset spec requires. Before reset, read ID 3 was waiting for data 0x10, and the DUT drops that read, as it should. After reset, a new read reuses ID 3 and the DUT correctly returns 0x20. The scoreboard still holds the old expectation and reports ID 3: expected 10 got 20, a failure on a DUT that did everything right.
The DUT forgot the first read at reset; the scoreboard did not.
What the scoreboard state stands for
Each entry in the scoreboard's expected-data map stands for a read the DUT still owes a response to. That is a claim about the DUT, and it stays true only while the DUT agrees.
Reset breaks the agreement. The DUT drops every outstanding read, so after reset it owes nothing. Any entry that survives reset describes a read that no longer exists, and the next response with the same ID is compared against it.
Finding the stale entry
At the failure, print the whole queue for the ID, not only the front entry. Here exp_q[3] holds two entries, 0x10 and 0x20, although only one read with ID 3 is outstanding.
To tell old from new, store the request time with each expectation. Comparing it with the time of the last reset settles the question at once. Storing the data and the time together as a packed struct keeps the lookup unchanged and makes every later failure easier to read.
| Entry | Requested at | Last reset at | Verdict |
|---|---|---|---|
| ID 3, data 0x10 | 10 ns | 20 ns | stale: requested before reset |
| ID 3, data 0x20 | 50 ns | 20 ns | valid |
Request times make a stale entry obvious.
The stale entry is not a DUT bug. The DUT dropped the read exactly as the reset spec says; the scoreboard simply never heard about it.
The repair: clear the state on reset
When the monitor sees reset asserted, clear everything that describes outstanding work. For this scoreboard that is a single call, exp_q.delete();, which removes every ID and its queue.
Clear the state when reset starts, not when it ends. No response can arrive while reset is held, so the moment rst_n falls is a clean point to drop the old work.
Reset ignored by the scoreboard
// monitor
@(negedge rst_n);
// nothing happens to exp_q
The first response for a reused ID is compared against a read from before reset.
Reset clears the expectations
// monitor
@(negedge rst_n);
exp_q.delete(); // every ID, every entry
outstanding = 0;
After reset, only reads issued after reset are expected.
Clear by deleting keys, not by emptying each queue. If every key stays behind with an empty queue, num() still reports outstanding IDs, and the end-of-test check fails for reads that were never issued.
Other state that reset must clear
Clearing the map is the most visible step, but not the only one. A monitor that was halfway through collecting a burst must drop the beats it already has, or the first burst after reset starts with stale data.
A driver must discard requests that were queued but not yet sent, and every counter of outstanding work must return to 0.
Do the clearing in one place, for example a task that waits for negedge rst_n and then calls a reset function on each component. Scattered clears are easy to forget when someone adds new state later.
When a response can outlive reset
Some bridges can deliver a response from before reset after reset has ended. On such a system, a reused ID alone cannot tell an old response from a new one, and clearing the map turns the late response into an "unexpected response" error.
If your protocol allows that, the response needs extra identity, or the test must wait until old responses can no longer arrive before it reuses an ID. Write the rule down, because the scoreboard must follow the protocol and not your expectations.
A regression for the reset path
One test can cover every case here. Issue ID 3, reset before it completes, issue ID 3 again, and answer it: that must pass. Then send a second response for ID 3, which must be reported as unexpected.
Finish with the end-of-test check: exp_q.num() must be 0. Without the clear on reset, the first step already fails, and with the clear, every step behaves as specified.
Run the reset at several points: before any read, with one read outstanding, and in the middle of a burst. Each placement leaves different state behind.
Try it
Without the clear, the response for the new read is compared with the stale 0x10, and the new 0x20 is left behind as a read that seems never to complete. With the clear, the response matches and nothing is left over.
The last line shows the duplicate check still works after the fix. In run, move the second request above the clear, as if reset arrived after the new read was issued, and predict the first two lines.
no clear: ID 3 -> expected 10 got 20, outstanding IDs=1 clear on reset: ID 3 -> match, outstanding IDs=0 second response for ID 3 -> unexpected responseCheck yourself
Remember
- Scoreboard entries stand for work the DUT still owes; reset cancels that work.
- Print the whole per-ID queue at a failure, and store request times to spot stale entries.
- On reset, delete every key with
exp_q.delete(), and clear counters and partial transactions too. - If responses can outlive reset, a reused ID needs extra identity or a quiet period.
- Test reset with a reused ID, a duplicate response and a final
num()check.
Practice
- Flush pending requests Implement · Easy
- The stale expectation Debug · Medium