A bin plan claims where bugs live. Real hardware fails at boundaries, at discontinuities like page edges and FIFO full or empty, and in error classes like reserved encodings.
Good bins put one named bucket on each place the spec changes behavior. Uniform regions share one coarse bucket.
One. Uniform behavior earns one coarse bin; the budget goes to the boundaries.
Bin count multiplies fast: one [] array and one wide cross can push the goal into thousands of bins. Every declared bin is a promise to hit it, so budget goal bins before writing them.
Budget first: tens of goal bins per covergroup, not thousands.
Per-value arrays only for enumerable spaces: opcodes, modes, small ID pools.
Wide fields get boundary bins plus coarse interior buckets, never per-value resolution.
Crosses cover the plan's combinations through binsof selection, not the full product.
Audit the tail: a long run of similar unhit bins means an array or cross needs redesign.
Planning. The array promised resolution the regression cannot deliver; redesign the bins before running more seeds.
The $ token stands for the coverpoint type minimum or maximum. $-bounded edge bins survive width changes that would orphan hand-written constants.
A default bin catches every value no other bin claims, and it never counts toward coverage. A nonzero hit count on it means the plan missed real behavior, so review it like a failure.
No. It sits outside the goal math in both directions; its job is flagging plan holes.
An 8-bit length field: 0 is invalid and dropped, 1 is the minimum packet, 64 is the MTU, above 64 is rejected, and 255 is a reserved escape value.
The listing below is not run in this course's checks: the open simulators do not count covergroups, so the lesson's bench counts it with the bins model.
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
// BEFORE — naive: no bins (auto), or per-value spray
covergroup cg_before with function sample(bit [7:0] len);
cp_len : coverpoint len; // 64 silent range-buckets
// ...or the other reflex:
// cp_len : coverpoint len { bins all[] = {[0:255]}; } // 256 bins.
endgroup
// Auto bins bury len==64 vs len==65 (the MTU edge, the ONE
// boundary the spec spends a paragraph on) inside auto[16].
// The per-value array separates them, but spends 256 bins,
// most on identical paths such as 137 vs 138.
// AFTER — intent-driven: bins mirror the spec's behavior regions
covergroup cg_after with function sample(bit [7:0] len);
cp_len : coverpoint len {
bins invalid_zero = {0}; // dropped-packet path
bins min_pkt = {1}; // minimum legal
bins typical = {[2:62]}; // uniform normal path
bins mtu_minus1 = {63}; // boundary approach
bins mtu = {64}; // exactly at limit
bins mtu_plus1 = {65}; // first rejected value
bins oversize = {[66:254]}; // uniform reject path
ignore_bins escape = {255}; // reserved, out of scope (spec 4.3)
bins unexpected = default; // tripwire (see below)
}
endgroup
// 7 goal bins. Every spec behavior change has a named bin;
// both uniform regions cost exactly one bin each.
// Here every value 0..255 is already claimed, so the default
// bin shows the syntax only. It fires on a coverpoint whose
// named bins leave part of the range uncovered.Before | After | |
|---|---|---|
Bins | 64 (or 256) anonymous bins | 7 named goal bins |
The MTU edge | Shared with three other values in |
|
Closure | Thousands of random packets, still holes | A short constraint list hits all 7 quickly |
The report |
| Reads like the spec |
The spec changes behavior at 64 versus 65, the accept-or-reject edge. Values 2 to 62 share one path, so one bin proves the region.
The bench runs both designs on the same regression: every length from 0 to 255 except 65, the one value just past the MTU.
The auto bins read 100%: auto[16] holds 64 to 67 and was hit by the others. The planned bins read 6 of 7 and name mtu_plus1 as the hole.
// uses: covmodel
// The packet-length redesign: auto bins against intent-driven bins, on the same stimulus.
module tb_bin_design;
cm_point cp_before = new("cp_len before (auto bins)"), cp_after = new("cp_len after (planned bins)");
initial begin
cp_before.add_split("auto", 0, 255, 64); // no bins: 64 auto bins of 4 values
cp_after.add("invalid_zero", 0, 0); cp_after.add("min_pkt", 1, 1);
cp_after.add("typical", 2, 62); cp_after.add("mtu_minus1", 63, 63);
cp_after.add("mtu", 64, 64); cp_after.add("mtu_plus1", 65, 65);
cp_after.add("oversize", 66, 254); cp_after.add("escape", 255, 255, CM_IGNORE);
cp_after.add_default("unexpected");
// A regression that never sends a 65-byte packet: the one value past the MTU.
for (int len = 0; len <= 255; len++) if (len != 65) begin cp_before.sample(len); cp_after.sample(len); end
$display(" before: %0.2f%%, and auto[16] holds 64 to 67 with %0d hits", cp_before.get_coverage(), cp_before.bin_list[16].hits);
cp_after.report();
if (cp_before.get_coverage() != 100.0 || cp_after.bin_list[5].hits != 0 || cp_after.n_covered() != 6) $fatal(1, "FAIL: redesign");
$display("PASS: the auto bins read 100%% with 65 never sent; the planned bins named the missing mtu_plus1");
$finish;
end
endmodule before: 100.00%, and auto[16] holds 64 to 67 with 3 hits
coverpoint cp_len after (planned bins): 85.71% (6 of 7 bins) below goal 100
invalid_zero 1
min_pkt 1
typical 61
mtu_minus1 1
mtu 1
mtu_plus1 0 <- hole
oversize 189
escape 1 (ignore)
unexpected 0 (default)
PASS: the auto bins read 100% with 65 never sent; the planned bins named the missing mtu_plus1Counted 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.
With every length but 65 sent, the auto bins read 100% and the planned bins read 6 of 7, naming mtu_plus1.