Reset, wrap and reproducible stress
Stress the FIFO at a depth that is not a power of two, with resets in the middle of traffic, and make every random run reproducible from a printed seed.
- Lesson
- 7.6
- Time
- 30 min
- Runs on
- Verilator
- Check
- 5 questions
A random regression failed once in 2,000 runs: a read returned an item written before a reset. The failure needed three things at once: a reset while the FIFO held data, a write pointer that had already wrapped, and a depth of 3. The log did not show the seed, so nobody could run that test again, and the bug stayed open for a month.
One column per rising edge. The model expects the item written after the reset; the FIFO returns an older one.
Depth 3 on purpose
A FIFO of depth 4 hides pointer bugs, because a 2-bit counter that overflows happens to wrap at the right place. Depth 3 is the smallest depth where that shortcut breaks, so the stress test in this lesson uses it.
A depth only tests wrapping if the stimulus actually wraps. fifo_scenarios counts laps: every DEPTH accepted writes since the last reset, any correct design's write pointer is back at slot 0. It works this out from the model's own counts, without looking inside the FIFO.
Stress also needs a mix that reaches the boundaries often. With writes and reads each requested about half the time at depth 3, the FIFO spends many cycles empty or full, which is where most FIFO bugs live. The counts in the example confirm it, with dozens of refused writes and reads per run.
The scoreboard needs no change for depth 3. It reads depth from its constructor, and nothing in it assumes a power of two.
Reset in the middle of traffic
The generator's constraint reset dist {0 := 40, 1 := 1} produces a reset in about one operation in 41, at random moments. Many of those resets land while the FIFO holds data, which is the case that matters. Everything stored must be discarded, and a read accepted just before the reset must still be answered after it.
The scoreboard handles reset as one more edge. It deletes exp_q, and because it clears due before deciding the edge, it expects no answer after the reset edge itself. fifo_scenarios counts resets that happen while holding data, so a run where every reset found the FIFO empty is visible in the report.
A seed you can replay
Each run in the example starts by printing its seed. The generator seeds its own process with that number, so all the operations it creates follow from it. As Module 5 explained, the same seed, code and simulator version give the same stimulus.
Run a fixed list of seeds in the nightly regression, so a failure can be repeated at once, and add a few new seeds each night to explore. Either way, print the seed before any stimulus, so a run that crashes or hangs still names it.
Keep the list of seeds in the regression script rather than in the testbench, so adding a seed never changes the code. Vary the depth as well: the same seeds at depths 1, 3 and 5 cover the extreme case, where every accepted write fills the FIFO, as well as ordinary ones.
When a failure appears, rerun its seed with the same code first. Only change the stimulus once the failure repeats, because any change, even an added debug object, can shift the random sequence.
Drain, then decide
A stress run ends in a fixed order. The generator sends its last random operation and five reads to drain the FIFO. The test then waits until the driver has driven everything, and for two more edges, so the last operation is sampled and its answer checked.
Stop when the generator is done
do @(posedge clk);
while (!gen.done);
report();
Reports while the last operations are still in the mailbox or on the pins, so their checks never run.
Stop when everything was checked
do @(posedge clk);
while (!(gen.done && drv.driven == gen.sent));
repeat (2) @(posedge clk);
#1 report();
Every operation has been driven, sampled and answered before the report.
The #1 steps off the clock edge, so the report never races with the monitor and scoreboard working on that same edge. Two more conditions finish the job: the model must be empty after the drain, and a watchdog must be running.
The watchdog is a separate process that waits a fixed time, here 100,000 time units, and calls $fatal if the test has not finished by then. It costs one line, and it turns a hung simulation into a clear failure with a message instead of a regression timeout.
| Layer | Component | Job | Reuses | Status |
|---|---|---|---|---|
| Test | fifo_test, fifo_config | chooses depth, seed and length, runs the environment, gives the verdict | Module 4 classes | lesson 7.7 |
| Generator | fifo_generator | creates fifo_op objects: directed, then seeded random | Modules 4 and 5 | built in 7.3 |
| Mailboxes | mailbox #(fifo_op), mailbox #(fifo_obs) | carry operations and observations between processes | Module 3 | built in 7.3 |
| Driver | fifo_driver | drives one operation per clock through drv_cb | Module 6 | built in 7.3 |
| Monitor | fifo_monitor | samples the pins through mon_cb into a new fifo_obs | Module 6 | built in 7.4 |
| Scoreboard | fifo_scoreboard | predicts each edge with the queue exp_q and compares | Modules 2 and 4 | built in 7.4 |
| Assertions | fifo_props | checks timing rules on the pins at every edge | Module 6 | built in 7.4 |
| Coverage | fifo_coverage, fifo_scenarios | measures which situations the stimulus reached | Module 5 stimulus | built in 7.5 |
| DUT | fifo | the design under test | lesson 7.1 rules | built in 7.2 |
The layered testbench after lesson 7.6.
Try it
Two seeds, 300 random operations each, at depth 3: about 38 laps of the ring and 4 to 7 resets while holding data per run, with no errors and an empty model after the drain. The writes that were never read back are the items discarded by those resets.
Change seeds to '{11, 11} and predict the second pair of lines. Then set DEPTH to 4 and predict whether laps goes up or down.
seed 11: 315 edges, 125 writes, 119 reads, 0 errors, 0 left after drain laps=39 write_blocked=24 read_blocked=38 both_at_full=7 both_at_empty=16 reset_holding=4 seed 12: 315 edges, 123 writes, 113 reads, 0 errors, 0 left after drain laps=38 write_blocked=29 read_blocked=30 both_at_full=12 both_at_empty=11 reset_holding=7Check yourself
Remember
- Stress at a depth that is not a power of two, and count laps to prove the pointers really wrapped.
- Reset at random moments, and require at least one reset while the FIFO holds data.
- Seed the generator from a number printed before any stimulus, and rerun a failing seed unchanged first.
- End only after every operation is driven, sampled and answered, then check that the model is empty.
- A watchdog turns a hang into a clear failure.
Practice
- Pointers that catch broken wraps Break it · Easy
- Catch the FIFOs that break at wrap and reset Verify · Hard