Part 5 · Functional Coverage · Intermediate
Bin Design Strategy
Bins encode verification intent — boundary values, corner buckets, error classes. Managing bin explosion, a review checklist, and a worked auto-to-intent redesign.
Bins encode intent, not arithmetic
A bin plan is a claim about where bugs live. Uniform value splits — 64 equal buckets, every value its own bin — claim that all values are equally dangerous, which is never true. Real hardware fails at boundaries (zero, max, one-off-each), at discontinuities (page edges, FIFO full/empty, width crossovers), and in error classes (reserved encodings, error responses, abort paths). Good bins put one named bucket on each place the spec changes behavior, and deliberately coarse buckets everywhere behavior is uniform.
WHERE THE BINS SHOULD BE
value space of a length field, DUT behavior annotated:
0 1 MTU-1 MTU MTU+1 max
│ ◄──── normal path ────► │ │ │ reject │
▼ ▼ ▼ ▼ ▼ path ▼
┌──┐ ┌──┐ ┌───────────────┐ ┌──┐ ┌──┐ ┌──────────────┐
│z │ │1 │ │ mid (1 bin) │ │-1│ │==│ │ over (1 bin) │
└──┘ └──┘ └───────────────┘ └──┘ └──┘ └──────────────┘
▲ ▲ ▲ ▲ ▲ ▲
one bin each where behavior CHANGES; one coarse bin
where behavior is uniform. 7 bins ≈ the whole story.
uniform 64-way split: 64 bins, boundaries buried mid-bucket,
closure slow, report unreadable. MORE bins, LESS information.Managing bin explosion
Bin count multiplies fast: a [] array here, a wide cross there, and the goal swells to thousands of bins that random stimulus will never finish hitting. Explosion is a planning failure, not a tooling problem — every bin you declare is a promise to hit it.
Budget first: decide a rough goal-bin budget per covergroup (tens, not thousands) before writing bins.
Per-value [] arrays only for genuinely enumerable spaces: opcodes, modes, small ID pools.
Wide fields get boundary bins plus coarse interior buckets — never per-value resolution.
Control crosses with binsof selection and ignore_bins; cross the plan's combinations, not the full product (see the Cross Coverage topic).
Audit regularly: sort the coverage report by unhit bins; a long tail of structurally similar unhit bins means a [] array or cross needs redesign.
Bin review checklist
Does every bin trace to a spec sentence or plan item? Name says which.
Is every boundary value (0, 1, max-1, max, size crossovers) in its OWN bin, not buried in a range?
Are error/reserved encodings handled deliberately — ignore_bins with a comment, or assertion + illegal_bins?
Could any bin never hit (empty with() filter, unreachable config value)? Unhittable bins cap coverage forever.
Is the goal-bin count proportionate to stimulus budget — can the regression realistically close this?
Does any default bin exist as a tripwire for values the plan forgot?
Worked redesign: packet-length field
An 8-bit packet-length field, spec: length 0 is invalid and dropped, 1 is the minimum packet, 64 is the MTU, anything above 64 is rejected with an error status, and 255 is a reserved escape value. First the naive model, then the intent-driven redesign.
// 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
// Both bury len==64 vs len==65 — the MTU edge, the ONE
// boundary the spec spends a paragraph on — inside shared
// buckets, while spending bins on 137 vs 138 (identical paths).
// 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
}
endgroup
// 8 goal bins. Every spec behavior change has a named bin;
// both uniform regions cost exactly one bin each.BEFORE AFTER
64 (or 256) anonymous bins 8 named goal bins
MTU edge shared with 3 other mtu / mtu_plus1 individually
values in auto[16] provable
closure: thousands of random closure: a short constraint
packets, still holes list hits all 8 quickly
report: auto[0]..auto[63] report: reads like the specInterview angle: “design bins for a length field” is among the most common coverage questions, and the expected shape of the answer is exactly this redesign — boundaries as singleton bins, uniform regions as single coarse bins, reserved values explicitly excluded, and a sentence about why per-value bins waste the goal budget.
Key takeaways
Bins are a bug-location hypothesis: singleton bins at every behavior change, coarse bins across uniform regions.
Bin explosion is self-inflicted goal inflation — budget goal bins before writing them, audit the unhit tail.
Review bins like code: traceability, boundaries isolated, exclusions justified, no unhittable bins.
The naive-to-intent redesign (8 named bins replacing 64+ anonymous ones) yields more information from fewer bins.
Common pitfalls
Uniform splits as a substitute for thinking — boundaries land mid-bucket where a hit proves nothing.
Per-value [] arrays on wide fields because “more bins is more coverage” — it is more goal, less meaning.
Bins copied from a previous project's field with different semantics — the boundaries moved; the bins didn't.
No default tripwire bin — values outside the plan flow by unnoticed instead of flagging a plan hole.