One environment, many tests. Each test is a thin recipe over scenarios, knobs and length, mapping straight back to plan rows.
TEST VARIANTS
Test │ Recipe │ Plan rows
────────────────┼─────────────────────────────────────┼───────────────
smoke │ 20 pushes, 20 pops, fixed seed │ F-01,04 (sanity)
stress_mixed │ 2000 txns SCN_MIXED, max_gap 0-1 │ F-01,07, wrap
burst_drain │ fill→full, hold, drain→empty, x10 │ F-02,03,05,06,10
corner_simul │ park at full / at empty, OP_BOTH │ F-07,08
reset_storm │ random resets mid-traffic, restart │ F-09
error_probe │ in_valid at full, out_ready at │ F-05,06
│ empty held for windows │
param_min │ rebuild DEPTH=2, run all the above │ F-11
param_w1 │ rebuild WIDTH=1, smoke + stress │ F-11// snippet: a test is a recipe over the generator and scoreboard above
task test_burst_drain(generator #(8) gen, fifo_sb #(8) sb);
repeat (10) begin
gen.run_scenario(SCN_BURST_FILL, 24); // past DEPTH: park at full
gen.run_scenario(SCN_BACKPRESSURE, 10); // hold full under pressure
gen.run_scenario(SCN_DRAIN, 24); // past DEPTH: park at empty
end
endtaskREGRESSION TIERS
per-checkin smoke + 1 seed of stress_mixed ~2 min
nightly all tests x 10 seeds x {DEPTH=16} ~1 hr
+ param_min, param_w1 x 3 seeds
weekly soak stress_mixed x 200 seeds, reset_storm coverage
x 50 seeds, merge ALL coverage closure run
bookkeeping per run: seed, test, RTL version, pass/fail, ucdb path
→ failures reproducible with one command; coverage merge automaticEvery uncovered bin gets a written disposition after the soak merge. Three findings are typical, and the first one runs below.
The bench below runs that finding. It instantiates two FIFOs on the same stimulus: a clean one, and one with the planted bug. The corner test fills both to full and then offers pushes while popping, which is exactly the hole. The bound checker counts violations in an action block instead of stopping, so one run reports both.
module fifo #(parameter int WIDTH = 8, parameter int DEPTH = 16, parameter bit BUG = 1'b0) (
input logic clk, rst_n,
input logic in_valid,
output logic in_ready,
input logic [WIDTH-1:0] in_data,
output logic out_valid,
input logic out_ready,
output logic [WIDTH-1:0] out_data,
output logic full, empty
);
localparam int AW = $clog2(DEPTH);
logic [WIDTH-1:0] mem [DEPTH];
logic [AW-1:0] wr_ptr, rd_ptr;
int count;
assign full = (count == DEPTH);
assign empty = (count == 0);
assign in_ready = BUG ? (!full || out_ready) : !full; // BUG: a pop at full opens in_ready
assign out_valid = !empty;
assign out_data = mem[rd_ptr];
wire push = in_valid && in_ready;
wire pop = out_valid && out_ready;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin wr_ptr <= '0; rd_ptr <= '0; count <= 0; end
else begin
if (push) begin mem[wr_ptr] <= in_data; wr_ptr <= wr_ptr + 1'b1; end
if (pop) rd_ptr <= rd_ptr + 1'b1;
count <= count + (push && !pop) - (pop && !push);
end
endmodule
module fifo_sva #(parameter int DEPTH = 16) (
input logic clk, rst_n, in_valid, in_ready, out_valid, out_ready, full, empty
);
int viol_push_full = 0, viol_ready_def = 0;
wire push = in_valid && in_ready;
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
a_ready_def: assert property (in_ready == !full) else viol_ready_def++;
a_no_push_full: assert property (full |-> !push) else viol_push_full++;
endmodule
bind fifo fifo_sva #(.DEPTH(DEPTH)) u_sva (.*);
Read the summary line. The clean FIFO has zero violations. The bugged FIFO fires a_no_push_full repeatedly, yet the scoreboard sees no data errors: a push accepted at full while a pop frees the same slot loses nothing. Only the assertion notices it, which is why the plan pairs F-08 with a_no_push_full and a corner test. The fix is in_ready = !full, and rerunning the clean FIFO is the regression.
cp_offer_at_full at 0 hits: the generator never offered a push during a pop while parked at full. Fix: corner_simul parks at full, then issues OP_BOTH bursts. Re-run covered it, and a_no_push_full exposed a real RTL bug: in_ready rose at full whenever out_ready was high, so a push was accepted at full, against Q-003.
cp_count.almost_full thin in 200 seeds: mixed traffic crosses the boundary too fast. Fix: SCN_BACKPRESSURE weight raised in stress_mixed. Now hundreds of hits.
param_w1 by reset_storm uncovered nothing new after 3 seeds. Waiver: WIDTH 1 has no width-dependent reset logic. Documented, approved, closed.
Sign-off is an evidence bundle, not a feeling. Plan closure, merged coverage, assertion health, scoreboard volume and a converged bug rate each contribute one line.
SIGN-OFF SUMMARY — fifo v1.2 (example of the bundle)
plan rows 11/11 closed (10 covered+checked, 1 waived: F-11/W1 note)
functional coverage 17/17 bins hit after merge (illegal_bins excluded by design)
assertions 8 properties, 0 failures final regression,
every antecedent cover hit (no vacuous-only passes)
scoreboard every word compared across the soak, 0 differences
bugs found 3 RTL (incl. push accepted at full), 2 TB, all closed
bug-rate 0 new bugs in final 2 weekly soaks
residual risk async use unverified (out of scope: sync FIFO only)A directed fix, not a waiver. Park at full, issue OP_BOTH bursts, re-run to cover. The gap once exposed a real RTL bug: a push accepted at full during a pop.
No. Closure needs the merged soak across seeds and configs. One seed proves the mechanisms run, not that the corners are covered.