Most production crosses are instances of a small number of patterns. Recognizing the pattern says which coverpoints to build, how coarse the bins should be, and which cells to prune.
Pattern | Combination | Bugs it drives out |
|---|---|---|
Opcode × operand-corner | Every operation against zero, max, and minimum | ALU and FPU corner bugs |
Burst-type × length × alignment | Every transfer shape against every alignment | Interconnect and DMA bugs |
State × input-event | Every input in every state where it is legal | FSM bugs |
Error-type × recovery-path | Every error down every recovery route | Error-handling bugs |
The listings below are not run in this course's checks: the open simulators do not count covergroups.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
// PATTERN 1: opcode x operand-corner
covergroup alu_cg @(posedge clk iff op_valid);
cp_op : coverpoint opcode { bins arith[] = {OP_ADD, OP_SUB, OP_MUL, OP_DIV}; }
cp_b : coverpoint operand_b {
bins zero = {0};
bins max = {32'h7FFF_FFFF};
bins min = {32'h8000_0000};
bins mid = default;
}
x_op_corner : cross cp_op, cp_b {
// div-by-zero is the plan's #1 corner: keep it as its own bin
bins div_zero = binsof(cp_op) intersect {OP_DIV} && binsof(cp_b.zero);
}
endgroup
// PATTERN 3: state x input-event
covergroup fsm_cg @(posedge clk);
cp_state : coverpoint ctrl_state { bins s[] = {IDLE, BUSY, DRAIN}; }
cp_evt : coverpoint event_in { bins e[] = {EV_REQ, EV_ABORT, EV_TIMEOUT}; }
x_se : cross cp_state, cp_evt {
// spec: ABORT is ignored in IDLE — impossible to observe an effect
ignore_bins idle_abort = binsof(cp_state.s) intersect {IDLE} &&
binsof(cp_evt.e) intersect {EV_ABORT};
}
endgroupThe default bin collects operand values the named bins do not cover, and it sits outside the coverage goal. Mid-range values still increment it, but they never count toward closure.
The strongest coverage models transcribe a spec table. Every spec cell maps to a cross cell, and the spec's forbidden cells become pruning.
Spec table 7-2: burst type | len 1 | len 2 to 8 | len 9 to 16 | unaligned address |
|---|---|---|---|---|
INCR | yes | yes | yes | yes |
WRAP | NO | pow2 only | pow2 only | NO |
FIXED | yes | yes | NO | yes |
NO is forbidden by spec section 7.2, so it becomes illegal_bins, not ignore_bins. pow2 means only lengths 2, 4, 8 and 16; WRAP with any other length is forbidden too.
Spec column | Coverpoint |
|---|---|
burst type |
|
len |
|
unaligned |
|
Full product | 3 × 5 × 2 = 30 cells |
Forbidden cells |
|
WRAP pow2-only rule | pow2 and non-pow2 length bins, with WRAP × non-pow2 illegal |
The length bins split pow2 lengths from the rest, because the WRAP rule needs them as separate cells. The listing below is not run in this course's checks, for the same reason.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
covergroup dma_cg @(posedge clk iff desc_done);
cp_btype : coverpoint btype { bins incr = {INCR}; bins wrap = {WRAP};
bins fixed = {FIXED}; }
cp_len : coverpoint blen { bins l1 = {1};
bins lpow28 = {2, 4, 8}; // pow2 below 16
bins lnp28 = {3, [5:7]}; // non-pow2 below 9
bins lnp916 = {[9:15]}; // non-pow2, 9 and up
bins l16 = {16}; } // pow2 at 16
cp_align : coverpoint addr[1:0] { bins aligned = {0}; bins unalign = {[1:3]}; }
// 3 x 5 x 2 = 30 cells. WRAP is legal only on pow2 lengths, aligned.
x_shape : cross cp_btype, cp_len, cp_align {
// Spec 7.2 forbidden cells -> runtime checks
illegal_bins wrap_len1 = binsof(cp_btype.wrap) && binsof(cp_len.l1);
illegal_bins wrap_unal = binsof(cp_btype.wrap) && binsof(cp_align.unalign);
illegal_bins wrap_np = binsof(cp_btype.wrap) &&
(binsof(cp_len.lnp28) || binsof(cp_len.lnp916));
illegal_bins fixed_long = binsof(cp_btype.fixed) &&
(binsof(cp_len.lnp916) || binsof(cp_len.l16));
}
endgroupReviewability: a reviewer holds the spec table next to the cross and verifies cell for cell.
Traceability: each illegal_bins comment cites the spec section, so sign-off audits turn mechanical.
Triage speed: any empty cell maps straight to a spec row, which answers reachability from the table.
Change resilience: when the spec table gains a row, the covergroup diff stays obvious and local.
A runnable sketch of the DMA rule: a combinational checker flags the spec-7.2 violations, and the testbench proves it flags exactly the 44 forbidden shapes out of 96 combinations.
module dma_shape_check (
input logic [1:0] btype, // 0 INCR, 1 WRAP, 2 FIXED
input logic [4:0] len, // 1..16
input logic aligned, // 1 when the address is aligned
output logic bad // 1 = spec 7.2 violation
);
timeunit 1ns; timeprecision 1ps;
logic pow2;
always_comb begin
pow2 = (len & (len - 5'd1)) == 5'd0;
bad = 1'b0;
if (btype == 2'd1) begin // WRAP: pow2 length, aligned
if (len == 5'd1 || !pow2 || !aligned) bad = 1'b1;
end
else if (btype == 2'd2 && len > 5'd8) bad = 1'b1; // FIXED: at most 8 beats
end
endmodule
PASS: checker flags all 44 forbidden DMA shapesillegal_bins, with a comment citing the spec section. Forbidden behavior deserves a runtime check, not silence.
Reviewers check the cross against the table cell for cell, holes map to spec rows, and spec changes produce local diffs. Signal-first crosses drift from the plan.
Run on Verilator 5.048 (--binary --timing) on 2026-10-18: the shape checker flagged exactly the 44 forbidden shapes out of 96. The covergroup listing is marked not run: the open simulators do not count covergroups.