How do you test the cases you did not think of, and how do you reproduce a failure that came from chance?
Every directed step in the last lesson covers a case somebody imagined. The bugs that survive directed testing live in the cases nobody imagined, and nobody imagines 400 cycles of inputs by hand. Random stimulus drives those cycles for you, and a seed makes the randomness replayable: the same seed always gives the same run, so a failure that came from chance can be reproduced exactly.
The testbench draws its inputs from a pseudo-random number generator (PRNG): a deterministic sequence of numbers that starts from a chosen starting value. That starting value is the seed. The simulator's $random updates its working copy in place, so reseeding with the same number replays the identical sequence of inputs, cycle for cycle.
A failing seed is therefore a reproducer, not a rumor. Seed 7 with 60 cycles fails at cycle 43 on the saturating design, and it fails there every time it is replayed through unchanged code. Debugging starts from the seed, not from a description of what the waveform looked like once.
The seed comes as the first line of output, before anything else. Keep that number in the log, including the continuous integration (CI) log, which is the automatic test run that checks every shared change. A random run whose seed was never recorded cannot be replayed, and a failure that cannot be replayed cannot be fixed with confidence.
Uniform random stimulus would reset the counter about every other cycle, and the count would never climb anywhere near 15. The wrap from 15 to 0, the deepest behavior in the spec, needs a long undisturbed climb, so the testbench weights its dice: reset and clear each land about 1 cycle in 32, while a valid event lands about every other cycle. Those odds let the count climb high enough to wrap, yet still visit reset and clear regularly.
Weights come from the shape of the design, not from habit. A deeper counter would need even rarer resets to reach its wrap, and a design with no deep states would need none of this. When random testing misses a corner, the first question is whether the weights ever let the run reach that corner at all.
The figure below shows the whole idea in two rows. In the top row the same seed enters the generator twice and the same stimulus and the same failure come out both times. In the bottom row a different seed gives a different stimulus and a different result. Look at the arrows: replaying means feeding the identical seed through identical code.
`timescale 1ns/1ps
// Counts accepted events, 4 bits, wraps 15 -> 0.
// Priority: reset > clear > count > hold. Reset is synchronous, active low.
module mini_controller (
input logic clk,
input logic rst_n,
input logic event_valid,
input logic clear_count,
output logic [3:0] count
);
always_ff @(posedge clk) begin
if (!rst_n)
count <= 4'd0;
else if (clear_count)
count <= 4'd0;
else if (event_valid)
count <= count + 4'd1;
end
endmodule
`timescale 1ns/1psThis listing is the random testbench next to the design it checks: the same spec model as the strong test, a cycle task with the same before-edge and after-edge checks, and stimulus drawn from $random with weights that let the count climb.
# captured terminal output
$ iverilog -g2012 -s tb_random -o build/ok.vvp rtl/mini_controller.sv tb/tb_random.sv && vvp build/ok.vvp +seed=1 | grep -E '^(seed|PASS)'
seed=1
PASS: seed=1, 401 cyclesThis output was measured on macOS, Icarus Verilog 13.0. The seed comes first so every log entry carries its own reproducer, and the last line is the verdict: one reset edge plus 400 random cycles, all matching the model. Unlike the strong testbench, this one stops at the first mismatch, because the seed replays the exact run and the failure will still be there on the second look.
Only some. Most 60-cycle runs never climb from 0 to 15 without a reset or clear landing first, so the missing wrap never meets exposing stimulus. A single passing seed means that walk got lucky, not that the design wraps.
The sweep below runs the first fourteen seeds against the saturating mutant, and it confirms the prediction above. Read the middle column first: eleven seeds pass on broken code.
# captured terminal output
$ iverilog -g2012 -s tb_random -o build/bug.vvp saturates.sv tb/tb_random.sv && for s in 1 2 3 4 5 6 7 8 9 10 11 12 13 14; do printf 'seed %-3s ' $s; vvp build/bug.vvp +seed=$s +cycles=60 | grep -E '^(PASS|FATAL)' | cut -c1-100; done
seed 1 PASS: seed=1, 61 cycles
seed 2 PASS: seed=2, 61 cycles
seed 3 PASS: seed=3, 61 cycles
seed 4 PASS: seed=4, 61 cycles
seed 5 PASS: seed=5, 61 cycles
seed 6 PASS: seed=6, 61 cycles
seed 7 FATAL: tb/tb_random.sv:41: FAIL: seed=7 cycle=43: rst_n=1 clear=0 valid=1 expected 0 got 15
seed 8 PASS: seed=8, 61 cycles
seed 9 PASS: seed=9, 61 cycles
seed 10 PASS: seed=10, 61 cycles
seed 11 PASS: seed=11, 61 cycles
seed 12 PASS: seed=12, 61 cycles
seed 13 FATAL: tb/tb_random.sv:41: FAIL: seed=13 cycle=25: rst_n=1 clear=0 valid=1 expected 0 got 15
seed 14 FATAL: tb/tb_random.sv:41: FAIL: seed=14 cycle=41: rst_n=1 clear=0 valid=1 expected 0 got 15Only seeds 7, 13 and 14 catch the bug, measured on macOS, Icarus Verilog 13.0. Eleven green seeds on a broken design are the lesson of this page in one table: run many seeds, keep every seed in the log, and prefer longer runs, because a longer climb reaches the wrap more often.
A failing seed earns its keep when it replays bit-identically. The two runs below use the same seed against the same mutant, and every character matches including the cycle number.
# captured terminal output
$ vvp build/bug.vvp +seed=7 +cycles=60 | grep -E '^(seed|FATAL)'; vvp build/bug.vvp +seed=7 +cycles=60 | grep -E '^(seed|FATAL)'
seed=7
FATAL: tb/tb_random.sv:41: FAIL: seed=7 cycle=43: rst_n=1 clear=0 valid=1 expected 0 got 15
seed=7
FATAL: tb/tb_random.sv:41: FAIL: seed=7 cycle=43: rst_n=1 clear=0 valid=1 expected 0 got 15Repeat the experiment yourself on the saturating mutant, then put the design back:
Copy mutants/saturates.sv over rtl/mini_controller.sv, keeping a copy of the original elsewhere.
Run the fourteen-seed sweep and confirm that only seeds 7, 13 and 14 report the wrap failure.
Replay +seed=7 +cycles=60 twice and confirm the identical failure at cycle 43 on both runs.
Restore the original design file and run seed 1 once more to confirm the run is green before you continue.
A seed alone does not replay a failure. The same seed on a different design revision, a different testbench, or a different simulator version can take a different path. When you report or save a failing run, write down all four: the seed, the source revision (the Git commit that holds the design and the testbench), the tool versions (the record from the versions file), and the exact command line. Together they let anyone else, or you next month, replay the failure.
# a complete reproducer, as a template (values differ per run)
seed : 7
cycles : 60
revision : <output of: git rev-parse --short HEAD>
tools : Icarus Verilog 13.0 (from tools/versions.md)
command : vvp build/bug.vvp +seed=7 +cycles=60Put that block in the bug report or in the commit message of the fix, so the failing seed becomes a permanent regression test.
A run that never records its seed. A failure without its seed is a story, not a reproducer. Record the seed first, keep it in every log, and treat a seedless random run as a run that never happened.
Replaying an old seed through a changed testbench. The seed only replays through identical code: editing the testbench changes the stimulus the same seed produces. After any testbench edit, old failing seeds are history and the sweep starts over.
Expecting random to hit rare corners quickly. Short or wrongly weighted runs never reach deep states like the wrap, and each green seed then proves only its own walk. Match the weights to the depth of the design, run many seeds, and make runs long enough to climb.
Rule: no seed in the log means the run never happened.
Build the mutant simulation and run it with +seed=7 +cycles=60: the run reports the identical failure at cycle 43, counting with the counter at 15 and expecting the wrap to 0. Any replay of seed 7 through unchanged code gives the same line, which is what makes the seed a reproducer.
The count rarely climbs past a few before a reset returns it to 0, so the wrap at 15 is almost never reached and the saturating mutant survives sweep after sweep. The weights must fit the depth of the design, or random stimulus exercises only the shallow states.
That one 401-cycle walk matched the model, and nothing more. Other walks may still fail, as the sweep against the mutant shows. One green seed is a start; confidence comes from many seeds, longer runs, and the directed steps beside them.
Random stimulus covers the cases you did not think to write, and the seed makes every run replayable.
Weight rare inputs so the design can reach deep states; uniform random rarely climbs to the wrap.
One green seed proves little: run many seeds, keep each seed in the log, and prefer longer runs.