When triage says needs-constraint-change, the cheapest fix is usually an inline constraint that biases randomization onto the hole ranges. The base constraints stay untouched, so the steering is additive and visible.
The listing below is not run in this course's checks: it is a fragment that calls the base test's driver.
// not-run: fragment (drive_burst is the base test's driver task)
class dma_txn;
typedef enum {INCR, FIXED, WRAP} btype_e;
rand btype_e btype;
rand int unsigned blen;
rand bit [31:0] addr;
constraint c_basic {
blen inside {[1:16]};
(btype == WRAP) -> (blen inside {2, 4, 8, 16});
}
constraint c_close_fixed {
btype dist {FIXED := 6, INCR := 3, WRAP := 1}; // below-at_least reshaping
}
endclass
// Hole: <incr, max_len, unaligned> never hit — alignment starved it.
// Closure sequence: same transaction class, steered at the call site.
// drive_burst is the existing bus-driver task from the base test.
task automatic run_closure_burst(int n);
dma_txn t = new();
repeat (n) begin
if (!t.randomize() with {
btype == INCR;
blen == 16; // the max_len bin
addr[1:0] != 2'b00; // force unaligned
})
$fatal(1, "closure randomize found no solution");
drive_burst(t);
end
endtaskInline constraints compose with class constraints. A randomize() failure here means the hole may be constraint-unreachable, so go back to triage.
dist reshaping closes below-at_least holes without excluding the rest of the space.
Keep steering in clearly named closure sequences. Reviewers must see that mainline stimulus was not narrowed.
Some corners resist steering: multi-step setups, exact timing races, or error recovery chains. These get directed tests, short and explicit, named after the hole they close.
Before writing one, try the cheaper intermediate: a seed sweep. That is more seeds of an existing test whose stimulus is already in the right neighborhood.
The listing below is not run in this course's checks because it is a fragment of a full DMA testbench: the DUT and the base-test helpers are not shown.
// not-run: fragment: needs the DMA DUT and the base-test helpers, which are not shown
// Directed test for hole: timeout recovery (cp_fsm XFER => IDLE on timeout).
// Random stimulus cannot hold the bus idle long enough, so force it.
// start_burst, stall_ready, and check_abort_status are the base-test helpers.
task automatic test_timeout_recovery();
cfg_timeout_cycles(50); // shortened through config, a legal knob
start_burst(INCR, 8);
stall_ready(100); // hold ready low past the timeout
wait (fsm_state == IDLE); // recovery observed
check_abort_status(); // closure AND checking
start_burst(INCR, 4); // prove the DUT still works after
endtaskPer-seed hit probability | Tool of choice |
|---|---|
About 0: structurally blocked | A directed test, or triage says unreachable |
Tiny: one setup in millions | Steered constraints |
Small: about 1 seed in 10 | A seed sweep |
Fine: already hit, below | Reshaping the dist |
Sometimes the right fix is in the model. Add bins when triage keeps finding interesting scenarios between buckets, and widen or merge bins when distinctions carry no verification meaning.
Widening to delete a hole is legitimate only when the distinction was meaningless. That judgement belongs in review, not in a deadline commit.
Steered constraints. Sweeps only pay when the probability justifies them, and directed tests are for structurally blocked corners.
randomize() at the call site. What does that tell you?The hole may be constraint-unreachable. Go back to triage instead of forcing the constraints harder.
The bench reproduces hole 1 from the triage lesson. The transaction class aligns every address, so 300 random bursts never fill the unaligned column of a burst-type × alignment cross, counted with the bins model. A closure sequence then turns that one constraint off and steers at the call site, and the class goes back to normal afterwards.
c_aligned. What does the cross read, and how many steered bursts close it?2 of 4: the aligned column only. 8 steered bursts close it, 4 INCR and 4 FIXED with addr[1:0] != 0, because the hole was blocked by one constraint, not rare.
// uses: covmodel
// Hole: unaligned INCR bursts were never sent, because one constraint aligned every address.
// The closure sequence turns that constraint off and steers at the call site.
class dma_txn;
typedef enum {INCR, FIXED, WRAP} btype_e;
rand btype_e btype;
rand int unsigned blen;
rand bit [31:0] addr;
constraint c_basic { blen inside {[1:16]}; (btype == WRAP) -> (blen inside {2, 4, 8, 16}); }
constraint c_aligned { addr[1:0] == 2'b00; } // in every sequence: this starved the unaligned cells
endclass
after 300 random bursts:
cross x_align: 50.00% (2 of 4 bins)
hole: <incr, unalign>
hole: <fixed, unalign>
after 8 steered bursts:
cross x_align: 100.00% (4 of 4 bins)
PASS: 300 random bursts left the unaligned column empty; 8 steered bursts closed itCounted with the course's bins model (rich/coverage/cov_model.sv, listed on the Coverage hub) on Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator 5.048 parses covergroups with a COVERIGN warning and ignores their bins, and Icarus 13 has none, so each covergroup listing is marked not run and its bench counts the same bins with the model.
300 random bursts left the unaligned column empty; with c_aligned off for one sequence, 8 steered bursts closed it.
In 1,000 solves Verilator 5.048 drew blen = 16 about a third as often as uniform odds predict, so the bench crosses burst type with alignment instead of length.