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.

diagram
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.

  1. Budget first: decide a rough goal-bin budget per covergroup (tens, not thousands) before writing bins.

  2. Per-value [] arrays only for genuinely enumerable spaces: opcodes, modes, small ID pools.

  3. Wide fields get boundary bins plus coarse interior buckets — never per-value resolution.

  4. Control crosses with binsof selection and ignore_bins; cross the plan's combinations, not the full product (see the Cross Coverage topic).

  5. 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.

systemverilog
// 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.
diagram
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 spec

Interview 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.