Cross bin counts multiply, and the numbers get away fast. Three coverpoints that each look reasonable in isolation can produce a cross no regression will ever close, and every empty cell costs triage time at sign-off.
Bins | |
|---|---|
| 16 |
| 16 |
| 8 |
| 16 × 16 × 8 = 2048 |
Restructured: region (4 coarse) × length (3) × QoS (2 groups) | 24 |
Plus named precision bins for the plan-critical exact combinations | + 6 = 30 |
A regression of 100 seeds at 50,000 transactions each gives 5 million samples, about 2,441 per cell on average. The average misleads: random stimulus clusters, corner cells may see one sample per million, and the last 5% of cells dominate the closure schedule. The 30-bin version closes in days and reviews in minutes.
The fix is never more seeds. Restructure the cross so the goal holds only cells the plan actually demands.
The listings below are not run in this course's checks: the open simulators do not count covergroups, so the lesson's bench counts them with the bins model.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
// STRATEGY 1: coarser source bins — shrink the factors
cp_len : coverpoint burst_len {
bins single = {1};
bins short_b = {[2:4]};
bins long_b = {[5:15]};
bins max_b = {16}; // 16 bins -> 4 bins
}
// STRATEGY 2: keep sources fine, but grade only the named bins
// the plan demands. Named bins absorb only the cells they select;
// every other auto cell stays in the goal. The ignore_bins
// catch-all is REQUIRED to make the goal exactly these two bins.
x_len_qos : cross cp_len, cp_qos {
bins max_hiqos = binsof(cp_len.max_b) && binsof(cp_qos) intersect {[6:7]};
bins single_any = binsof(cp_len.single);
ignore_bins rest = (!binsof(cp_len.max_b) || !binsof(cp_qos) intersect {[6:7]})
&& !binsof(cp_len.single);
}Strategy 3 splits by verification concern. One covergroup crossing everything against everything is a smell; small two-dimensional crosses stay reviewable, with option.weight keeping the rollup meaningful.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
covergroup perf_cg @(posedge clk iff txn_done);
cp_qos : coverpoint qos { bins lo = {[0:3]}; bins hi = {[4:7]}; }
cp_len : coverpoint burst_len{ bins s = {1}; bins m = {[2:8]}; bins l = {[9:16]}; }
x_perf : cross cp_qos, cp_len; // 6 cells
endgroup
covergroup addrmap_cg @(posedge clk iff txn_done);
cp_reg : coverpoint region { bins r[4] = {[0:15]}; } // 4 coarse bins
cp_dir : coverpoint is_write { bins rd = {0}; bins wr = {1}; }
x_map : cross cp_reg, cp_dir; // 8 cells
endgroup
// 6 + 8 = 14 goal cells instead of one 2048-cell monster.Strategy | What shrinks | Keep in mind |
|---|---|---|
Coarsen source bins | Each factor: 16 len values become 4 bins | Stay coarse enough to still separate the plan's corners |
Name only target bins | The product: 2048 auto cells become a few named bins | Unselected auto cells stay in the goal unless an ignore_bins catch-all removes them |
Split covergroups by concern | Dimensionality: one 3-D cross becomes two 2-D crosses | Rebalance option.weight so the rollup reflects priorities |
Any cross over about 50 cells without a written justification line in the plan.
Crosses of 3 or more coverpoints where no design logic examines all three fields together.
An auto product accepted wholesale, with no named bins and no pruning, on any non-trivial cross.
A coverpoint with per-value bins feeding a cross, where the factor explodes silently.
Goal cells at zero hits across three consecutive regressions: either unreachable and prunable, or unplanned and untested.
The bench sends the same 1,000 clustered samples (70% of bursts 1 to 4 beats, 80% low QoS) to three crosses built with the bins model: 16 length bins × 8 QoS bins, the coarse 4 × 2 version, and the fine sources with only two named target bins and a catch-all.
No. It reads 115 of 128: the clustering starves the long, high-QoS cells, and 13 stay empty. The 8 coarse cells and the 2 named bins all close on the same samples.
// uses: covmodel
// Cross explosion: fine bins, coarse bins and named target bins over the same 1000 skewed samples.
module tb_cross_explosion;
cm_point len_fine = new("len (16 bins)"), qos_fine = new("qos (8 bins)");
cm_point len_coarse = new("len (4 bins)"), qos_coarse = new("qos (2 bins)");
cm_cross x_fine, x_coarse, x_named;
int unsigned r = 32'd2026;
function int unsigned rnd(int unsigned n); // a fixed generator, so every simulator sees the same stream
r = r * 32'd1103515245 + 32'd12345;
return (r >> 8) % n;
endfunction
initial begin
len_fine.add_each("len", 1, 16); qos_fine.add_each("qos", 0, 7);
len_coarse.add("single", 1, 1); len_coarse.add("short_b", 2, 4); len_coarse.add("long_b", 5, 15); len_coarse.add("max_b", 16, 16);
qos_coarse.add("lo", 0, 3); qos_coarse.add("hi", 4, 7);
x_fine = new("x_fine", len_fine, qos_fine);
x_coarse = new("x_coarse", len_coarse, qos_coarse);
// Fine sources, but only the two combinations the plan names, and a catch-all for the rest.
x_named = new("x_named", len_fine, qos_fine);
x_named.add_bin("max_hiqos", x_named.sel_and(x_named.sel("len[16]", ""), x_named.sel_values('{6, 7}, 1)));
x_named.add_bin("single_any", x_named.sel("len[1]", ""));
x_named.ignore(x_named.sel_not(x_named.sel_or(x_named.nb_cells[0], x_named.nb_cells[1])));
// Random traffic clusters: 70% of bursts are 1 to 4 beats, 80% of them low QoS.
repeat (1000) begin
int len, qos;
len = rnd(10) < 7 ? 1 + rnd(4) : 5 + rnd(12);
qos = rnd(10) < 8 ? rnd(4) : 4 + rnd(4);
len_fine.sample(len); qos_fine.sample(qos); len_coarse.sample(len); qos_coarse.sample(qos);
x_fine.sample(); x_coarse.sample(); x_named.sample();
end
$display(" cross x_fine: %0.2f%% (%0d of %0d bins), %0d holes", x_fine.get_coverage(), x_fine.n_covered(), x_fine.n_bins(),
x_fine.n_bins() - x_fine.n_covered());
x_coarse.report(); x_named.report();
if (x_fine.n_bins() != 128 || x_fine.get_coverage() == 100.0 || x_coarse.get_coverage() != 100.0 || x_named.n_bins() != 2)
$fatal(1, "FAIL: explosion math");
$display("PASS: 1000 samples left %0d of 128 fine cells empty; the 8 coarse cells and 2 named bins closed",
x_fine.n_bins() - x_fine.n_covered());
$finish;
end
endmodule cross x_fine: 89.84% (115 of 128 bins), 13 holes
cross x_coarse: 100.00% (8 of 8 bins)
cross x_named: 100.00% (2 of 2 bins)
max_hiqos 2
single_any 147
PASS: 1000 samples left 13 of 128 fine cells empty; the 8 coarse cells and 2 named bins closedWhen design logic examines all three fields together. Without shared logic across all three, split the concern into smaller crosses.
Keep the coarse product and add named precision bins for the exact critical combinations. The goal stays small while the corners stay graded.
Counted 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.
1,000 clustered samples left 13 of 128 fine cells empty, while the 8 coarse cells and the 2 named target bins closed.