UVM
Coverage Bins
The bin vocabulary — automatic versus explicit bins; value, range, list, and array bins; wildcard bins (pattern matching); transition bins (value sequences over time); and ignore and illegal bins, the three jobs of a bin: require, exclude, and forbid — and how bin definitions precisely control what each coverpoint counts.
Functional Coverage · Module 19 · Page 19.4
The Engineering Problem
The previous chapter established that a coverpoint's meaning comes from its partition into bins (Module 19.3) — and that the art is choosing a meaningful partition. But "bin" is not one thing: SystemVerilog gives bins a rich vocabulary, and which kind you use precisely controls what the coverpoint requires, excludes, and forbids. A single range bin (bins b = {[1:4]}) and an array of bins (bins b[] = {[1:4]}) look almost identical but mean opposite things — one bin hit by any value 1–4, versus four bins each requiring its own value. A transition bin (bins t = (1 => 2 => 3)) measures a sequence over time, not a value. An ignore bin removes a value from the coverage denominator; an illegal bin turns a value into a runtime error. Confuse these and the coverpoint measures the wrong thing: it can never close (a required bin for an unreachable value), or it misses a violation (a forbidden value with no illegal bin). The problem this chapter solves is the bin vocabulary: the kinds of bins and the three jobs — require, exclude, forbid — so a coverpoint counts exactly what it should.
A bin is a named bucket with a rule that classifies a sampled value — and bins have three jobs. Require (counting bins): a bin is hit when the value matches its rule, and the coverpoint is covered when all its counting bins are hit. The rule can be a value ({5}), a range ({[1:4]}, hit by any value in range), a list ({1,5,9}), an array (bins b[] — one bin per value, the precise form of 19.3's "carve the cases"), a wildcard ({4'b1??0} — pattern match), or a transition ((1 => 2 => 3) — a value sequence over successive samples). Automatic bins (one per value, capped by auto_bin_max) are the default for small enumerations. Exclude (ignore_bins): values removed from coverage — not counted, not required — for reserved or unreachable values, so the coverpoint can close. Forbid (illegal_bins): values that should never occur — if sampled, a runtime error fails the test (a check, not a measure). This chapter is the bin vocabulary: automatic vs explicit, value/range/list/array, wildcard, transition, and the require/exclude/forbid trichotomy that makes a coverpoint count exactly what it should.
What are the kinds of coverage bins — automatic versus explicit, value, range, list, array, wildcard, and transition — and what are the three jobs of a bin (require with counting bins, exclude with ignore_bins, forbid with illegal_bins) that precisely control what a coverpoint counts?
Motivation — why the bin vocabulary is worth mastering
Bins are small syntax with large consequences — the difference between a coverpoint that means what you think and one that silently misleads. The reasons:
- The bin kind determines what's required.
bins b[](array — one per value) requires every value;bins b(single — one for all) requires just one. The same-looking declaration means opposite things, and getting it wrong gives trivial (false 100%) or unattainable coverage. - Some scenarios are sequences, not values. A state machine transition, a mode change, a back-to-back pattern — these are temporal and can only be measured by transition bins (
a => b => c). Without them, you can't express sequence coverage. - Not every value is reachable or legal. Some values are reserved (the DUT never produces them) — they must be
ignore_bins, or the coverpoint never closes. Some are forbidden (must never occur) — they should beillegal_bins, so a violation fails the test instead of silently passing. - Automatic bins are a trap on wide variables. Auto bins are convenient for small enums but catastrophic on wide fields (capped at
auto_bin_max, lumping values into meaningless buckets) — knowing when auto bins apply prevents the 19.3 anti-patterns. - Bins are how the plan's exclusions and rules are encoded. The coverage plan says not just what to exercise but what to exclude (reserved) and what's illegal (forbidden) — the bin vocabulary (counting / ignore / illegal) is how those are expressed.
The motivation, in one line: bins are small syntax with large consequences — the kind of bin (value/range/array/wildcard/transition) precisely determines what a coverpoint requires, and the three jobs (require / exclude / forbid via counting / ignore_bins / illegal_bins) determine whether a coverpoint can close, measures sequences, and catches forbidden values — so mastering the vocabulary is what makes a coverpoint count exactly what the plan intends.
Mental Model
Hold bins as the rules of a sorting machine — each sample is routed to a bucket, discarded, or trips an alarm:
Bins are the rules of a sorting machine: each sampled value drops in and is routed to the bucket whose rule it matches — an exact value, a range, a pattern, or a completed sequence — and the coverpoint is "covered" when every counting bucket has caught at least one item. Two special rules don't count: an ignore rule quietly discards the item (it's not required and not counted), and an illegal rule trips an alarm (the value should never have appeared, so the test fails). The art is writing the rules so each bucket catches exactly the cases that matter. Picture a sorting machine on a conveyor (the coverpoint). Each sampled value drops onto the belt and is routed by a set of rules (the bins). The counting rules send the item to a labeled bucket: an exact-value rule (this bucket catches only 5), a range rule (this bucket catches anything 1–4 — so one item 1–4 fills it, regardless of which), a list rule (catches 1, 5, or 9), a pattern rule (catches anything matching
1??0— the?bits don't matter), or a sequence rule (catches an item only when it completes a tracked sequence — the belt remembers the last few items, and fires when it sees 1-then-2-then-3). An array of buckets is the key subtlety:bins b[]over 1–4 makes four separate buckets (one per value, each must be filled), whereas a singlebins bover 1–4 makes one bucket (any of the four fills it). The coverpoint is "covered" when every counting bucket has caught at least one item. Then two special rules don't count. An ignore rule quietly discards the item — it's not required and not counted (used for reserved values the machine will never see anyway, so they don't block the "all buckets filled" check). An illegal rule trips an alarm — the value should never have appeared on the belt, so seeing it halts the line (the test fails). The art is writing the rules so each bucket catches exactly the cases that matter — and so the ignore and illegal rules correctly discard the unreachable and flag the forbidden.
So bins are sorting rules: counting rules route each value to a bucket (by exact value, range, list, pattern, or completed sequence), an array of buckets requires each value (versus a single bucket for all), and the coverpoint is covered when every counting bucket is filled. Two rules don't count: ignore discards (unreachable/reserved — so it doesn't block closure) and illegal alarms (forbidden — so it fails the test). Write the rules so each bucket catches exactly what matters, discard the unreachable, and alarm on the forbidden.
Visual Explanation — the three jobs of a bin
The defining picture is the trichotomy: bins require (counting), exclude (ignore), or forbid (illegal) — and the counting job has a vocabulary of rules.
The figure shows the three jobs of a bin. Require — counting bins (the brand-colored top): a bin is hit when the value matches its rule, and the coverpoint is covered when all are hit. The rules: a value bin ({5}), a range bin ({[1:4]}), an array of bins (b[] — one per value), a wildcard bin ({1??0} — pattern), or a transition bin ((1=>2=>3) — a sequence over time). Exclude — ignore_bins (the success-colored middle): values removed from coverage — not counted, not required — for reserved or unreachable values, so the coverpoint can close. Forbid — illegal_bins (the warning-colored bottom): values that must never occur — if sampled, a runtime error fails the test (a check, not a measure). The crucial reading is that these three jobs are distinct and you must pick the right one per value. A value the DUT legally produces and you want to exercise → a counting bin. A value the DUT legally never produces (reserved) → an ignore bin (else the coverpoint never closes, chasing an unreachable value). A value that must never occur (a protocol violation) → an illegal bin (else the violation passes silently). Putting a value in the wrong job breaks the measurement: ignore where illegal was needed misses a violation; a counting bin where ignore was needed blocks closure; illegal where counting was needed fails a legal test. The counting job's vocabulary (value / range / array / wildcard / transition) then precisely specifies what counts. The diagram is the bin decision: for each value of the variable, decide require (and which counting rule), exclude, or forbid — and the coverpoint counts exactly what those rules say. Every value gets a job: require it, exclude it, or forbid it.
RTL / Simulation Perspective — the bin vocabulary in code
In code, the bin kinds are distinct syntax inside the coverpoint braces. The example shows the full vocabulary: auto, value, range, list, array, wildcard, transition, ignore, and illegal.
covergroup txn_cg with function sample(bus_txn t);
// AUTOMATIC bins — one per value, capped by auto_bin_max (default 64). Good ONLY for small enums.
cp_op: coverpoint t.op; // auto: one bin per op value
// EXPLICIT counting bins — VALUE, RANGE, LIST
cp_len: coverpoint t.len {
bins one = {1}; // VALUE bin: hit when len == 1
bins midrange= {[2:14]}; // RANGE bin: hit by ANY len in 2..14 (one bin)
bins powers = {1, 2, 4, 8, 16}; // LIST bin: hit by any listed value
}
// ARRAY of bins — ONE BIN PER VALUE (each must be hit) — the precise "carve every case"
cp_id: coverpoint t.id {
bins each[] = {[0:7]}; // 8 SEPARATE bins id[0]..id[7]; ALL must be hit
}
// WILDCARD bin — pattern with don't-care bits
cp_code: coverpoint t.code {
wildcard bins burst = {4'b1??0}; // matches 1000,1010,1100,1110 (the ? bits don't matter)
}
// TRANSITION bins — a SEQUENCE of values over SUCCESSIVE samples (temporal)
cp_state: coverpoint t.state {
bins idle_to_done = (IDLE => BUSY => DONE); // hit when state goes IDLE then BUSY then DONE
bins ping_pong = (A => B), (B => A); // either transition
}
// IGNORE bins — EXCLUDE reserved/unreachable values (not counted, not required → can close)
cp_mode: coverpoint t.mode {
bins legal[] = {[0:2]};
ignore_bins rsvd = {[3:7]}; // reserved: DUT never produces → don't block closure
}
// ILLEGAL bins — FORBID values that must never occur (sampling one FAILS the test)
cp_resp: coverpoint t.resp {
bins ok = {OKAY}; bins err = {SLVERR, DECERR};
illegal_bins reserved_resp = {2'b10}; // this encoding must never appear → runtime error
}
endgroupThe code shows the full bin vocabulary. Automatic (cp_op: coverpoint t.op; — no braces) — one bin per value, capped by auto_bin_max (default 64); good only for small enums. Value/range/list (cp_len): a value bin (one = {1}, hit when len == 1), a range bin (midrange = {[2:14]}, hit by any len in 2–14 — one bin), a list bin (powers = {1,2,4,8,16}, hit by any listed value). Array (cp_id: bins each[] = {[0:7]}) — eight separate bins id[0]..id[7], all must be hit — the precise "carve every case" of 19.3. Wildcard (cp_code: wildcard bins burst = {4'b1??0}) — pattern match, the ? bits don't matter (matches 1000, 1010, 1100, 1110). Transition (cp_state): a sequence over successive samples — idle_to_done = (IDLE => BUSY => DONE) is hit when state goes through that order; ping_pong = (A => B), (B => A) for either transition. Ignore (cp_mode: ignore_bins rsvd = {[3:7]}) — reserved values excluded (the DUT never produces them, so they don't block closure). Illegal (cp_resp: illegal_bins reserved_resp = {2'b10}) — a forbidden encoding; sampling one is a runtime error that fails the test. The shape to carry: the counting bins (value / range / list / array / wildcard / transition) specify what to require with increasing power (single value → pattern → sequence), ignore_bins specify what to exclude, and illegal_bins specify what to forbid. The array bins b[] vs single bins b distinction is the most error-prone: b[] = one bin per value (each required), b = one bin for all (any one suffices). Pick the bin kind that makes the coverpoint require, exclude, and forbid exactly the right values.
Verification Perspective — array bins versus a single bin
The single most consequential bin distinction is array (b[]) versus single (b) — identical-looking syntax, opposite meaning. Seeing them side by side prevents the most common false-100% bin bug.
The figure shows the array-versus-single distinction. A single bin over a range (bins b = {[0:7]}) creates one bin, hit by any value in the range — so one transaction covers it, regardless of which value occurred → trivial 100%. An array of bins over the same range (bins b[] = {[0:7]}) creates a separate bin per value (b[0]..b[7]) — so every value must occur for the coverpoint to close → meaningful 100% (all 8 exercised). The verification insight is that this near-identical syntax — just the [] — means opposite things: b requires one value (of the range), b[] requires all. The warning-colored single bin is the trap: it looks like it covers "the range 0–7," but it only requires one of them, reaching 100% on the first transaction (the false-100% of 19.3, now pinned to a specific syntax error). The brand/success-colored array bin is usually what you want when the values are an enumeration (IDs, modes, opcodes) where each is a distinct case to exercise. So the choice of b vs b[] is the choice of "require one" vs "require all" — and it's the most common place a coverpoint silently reads a misleading 100%. The skill is asking: "do I need every value of this set exercised (b[]) or just any one (b)?" For an enumeration of distinct functional cases → b[]. For a range that's functionally uniform (and you just want some representative) → b. The diagram is the array decision: b (one bucket, any value, trivial 100%) versus b[] (one bucket per value, all required, meaningful 100%) — the [] is the difference between requiring one and requiring all. When each value is a distinct case, use b[]; a single b over a set requires only one of them.
Runtime / Execution Flow — ignore versus illegal at run time
At run time, ignore and illegal bins behave very differently — one silently excludes, the other actively fails. The flow shows what each does when a value is sampled.
The flow shows ignore versus illegal at run time. Value sampled (step 1): the coverpoint receives the variable's current value and routes it by the bin rules. Counting bin (step 2): if it matches a value/range/array/wildcard/transition bin, that bin is marked hit and contributes to coverage. Ignore bin (step 3): if it matches ignore_bins, it is silently excluded — not counted, not required, removed from the denominator. Illegal bin (step 4): if it matches illegal_bins, a runtime error fires and the test fails — the value should never have occurred. The runtime insight is the opposite behaviors: ignore_bins is passive (it quietly removes a value from consideration, so that value can never block closure and never appears as an unhit requirement), while illegal_bins is active (it watches for a forbidden value and fails the test the instant one appears). This is why the job you assign a value matters so much: a reserved value as ignore_bins lets the coverpoint close (correct); the same value left in a counting bin blocks closure forever (wrong — chasing an unreachable value); a forbidden value as illegal_bins catches the violation (correct); the same value ignored lets the violation pass silently (wrong — a missed check). So ignore_bins and illegal_bins are not interchangeable — one excludes from the measure, the other adds a check. The flow is the routing: a sampled value either counts (counting bin), vanishes from the measure (ignore), or fails the test (illegal) — three fates, chosen by which job you gave that value. Ignore quietly excludes the unreachable; illegal actively fails on the forbidden — never swap them.
Waveform Perspective — transition bins over time
The transition bin is uniquely temporal — it's hit only when the variable passes through a sequence of values over successive samples. The waveform shows a transition completing, and a forbidden value tripping an illegal bin.
A transition bin is hit when the value sequence completes over successive samples; an illegal value fails the test
12 cyclesThe waveform shows the transition bin's temporal nature and the illegal bin's check. The state variable is sampled each transaction. The transition bin (IDLE => BUSY => DONE) is hit only when state passes through that exact sequence over three successive samples — it completes at the third sample (trans_hit at sample 3, when state=DONE after IDLE then BUSY). The crucial reading is that no single value hits a transition bin — the sequence must occur in order across samples: IDLE alone doesn't hit it, nor BUSY, nor DONE — only the completed IDLE→BUSY→DONE does. This is coverage of a temporal pattern — a state-machine path, a protocol sequence — that value bins cannot express. Later, state takes a forbidden value (RSVD, covered by an illegal_bins) — the moment it's sampled, a runtime error fires and the test fails (illegal_fire at sample at cycle 8). The picture to carry is the two distinct behaviors: the transition bin accumulates across samples and fires when the sequence completes (a temporal require), while the illegal bin fires the instant a forbidden value appears (a temporal forbid — a check). Reading the waveform this way — did the sequence complete (transition hit)? did a forbidden value appear (illegal fire)? — is reading the temporal bins. The transition completing at the third sample is the signature of sequence coverage; the illegal firing on RSVD is the signature of a forbidden-value check. Together they show that bins measure not only which values occurred but in what order (transition) and whether a forbidden value ever appeared (illegal). Transition bins require a sequence over time; illegal bins fail the instant a forbidden value occurs.
DebugLab — the coverpoint that could never close
Coverage stuck at 94% forever — a required bin for a value the DUT can never produce
A team's cp_mode coverpoint was stuck at 94% — run after run, seed after seed, it never reached 100%. The unhit bins were modes 5, 6, and 7. The team poured stimulus at the mode field, constraining the randomizer to hit those modes, adding directed tests — and coverage stayed at 94%. Eventually someone read the spec: modes 5, 6, 7 were reserved — the mode field was 3 bits (0–7), but the design only defined modes 0–4; the DUT physically could not produce 5, 6, or 7 (the mode decoder clamped them). The coverpoint had auto bins (one per value, 0–7), so it required hitting all 8 — including the 3 the DUT can never produce. The 94% was as high as it could ever go.
The coverpoint required values the DUT can never produce — they were left as counting bins (auto bins, one per value) when they should have been ignore_bins (excluded from coverage). A coverpoint can only close if every counting bin is reachable:
✗ AUTO bins require ALL 8 values, including 3 the DUT can never produce:
cp_mode: coverpoint t.mode; // auto: bins for 0,1,2,3,4,5,6,7 — ALL required
// modes 5,6,7 are RESERVED — the DUT never produces them → those 3 bins NEVER hit
// coverage capped at 5/8 = 62%... or with weighting, stuck at 94% — NEVER 100%
// the team chases unreachable values forever; closure is impossible
✓ IGNORE the unreachable values so the coverpoint counts only what's reachable:
cp_mode: coverpoint t.mode {
bins legal[] = {[0:4]}; // the 5 modes the DUT actually produces — each required
ignore_bins rsvd = {[5:7]}; // reserved: excluded from coverage → don't block closure
}
// now the denominator is the 5 REACHABLE modes; exercising all 5 → 100% → closure achievable
// (if 5,6,7 must NEVER occur, use illegal_bins instead — then producing one FAILS the test)This is the unreachable-required-value bug — a coverpoint blocked from closing by requiring a value that cannot occur. The cp_mode coverpoint used auto bins (one per value, 0–7), so it required hitting all 8 mode values — but modes 5, 6, 7 were reserved, never produced by the DUT, so those 3 bins could never be hit, capping coverage below 100% forever. The team wasted effort chasing values the DUT physically cannot produce — more stimulus can't help, because the unreachable bins are unreachable by construction. The root issue is the wrong job: the reserved values were left as counting bins (the auto-bin default) when they should have been excluded (ignore_bins) — removed from the denominator so the coverpoint counts only the reachable modes. The fix: bins legal[] = {[0:4]} (the 5 reachable modes, each required) and ignore_bins rsvd = {[5:7]} (the reserved values, excluded) — now the denominator is the 5 reachable modes, and exercising all 5 reaches 100%. (And if modes 5–7 must never occur — a violation if they do — the right job is illegal_bins instead, so producing one fails the test.) The general lesson, and the chapter's thesis: every value of a variable needs the right bin job — a value the DUT legally never produces (reserved, unreachable) must be ignore_bins (excluded from coverage), or the coverpoint can never close and blocks closure forever; a value that must never occur must be illegal_bins (a check that fails the test); and only values the DUT legally produces and you want to exercise belong in counting bins. Auto bins make every value a required, counting bin — which is wrong whenever some values are unreachable or forbidden; ignore the unreachable, forbid the illegal, and require only the reachable. A coverpoint that is stuck below 100% is often requiring a value that can never occur — exclude it, don't chase it.
The tell is a coverpoint stuck below 100% no matter how much stimulus you add. Diagnose an unreachable-required-value:
- Identify the persistently unhit bins. List which bins never fill across many seeds — those are the suspects.
- Check the spec for reachability. For each unhit bin's value, confirm whether the DUT can legally produce it; reserved or unreachable values can't.
- Decide the correct job. Unreachable-but-legal → ignore_bins; must-never-occur → illegal_bins; reachable → keep as a counting bin and fix the stimulus.
- Beware auto bins on a partially-used range. Auto bins require every value of the range, including reserved ones — switch to explicit bins plus ignore/illegal.
Give every value the right bin job:
- Don't use auto bins on a range with reserved values. Use explicit counting bins for the reachable values and ignore_bins for the reserved ones.
- Exclude unreachable values with ignore_bins. Remove from the denominator anything the DUT legally never produces, so closure is attainable.
- Forbid impossible values with illegal_bins. Values that must never occur get an illegal bin so a violation fails the test instead of blocking or passing silently.
- Audit closure blockers early. A coverpoint that won't pass a ceiling is often requiring an unreachable value, not lacking stimulus.
The one-sentence lesson: every value of a variable needs the right bin job — a value the DUT legally never produces must be ignore_bins (excluded from coverage, or the coverpoint can never close), a value that must never occur must be illegal_bins (a check that fails the test), and only reachable values you want to exercise belong in counting bins, so a coverpoint stuck below 100% is often requiring an unreachable value — exclude it, don't chase it.
Common Mistakes
- Confusing
bins bwithbins b[]. A single bin over a range requires one value (trivial 100%); an array requires every value — useb[]when each value is a distinct case. - Auto bins on a wide or partially-reserved variable. Auto bins require every value (including reserved ones) and cap at auto_bin_max — use explicit bins plus ignore_bins.
- Leaving unreachable values as counting bins. A reserved value the DUT never produces blocks closure forever; exclude it with ignore_bins.
- Using ignore_bins where illegal_bins was needed. A forbidden value silently ignored lets a violation pass; use illegal_bins so it fails the test.
- Forgetting transition bins for sequences. A temporal scenario (state path, mode change) can only be measured by transition bins, not value bins.
- Not using wildcard bins for don't-care bits. A pattern with don't-care bits is cleanly expressed as a wildcard bin, not enumerated value by value.
Senior Design Review Notes
Interview Insights
Bins have three jobs — require, exclude, and forbid — and the require job has a vocabulary of kinds. The require job is done by counting bins: a bin is hit when the sampled value matches its rule, and the coverpoint is covered when all counting bins are hit. The rule can be a value bin, hit by one exact value; a range bin, hit by any value in a range, so one value in the range fills the single bin; a list bin, hit by any value in an explicit set; an array of bins, written with empty brackets, which creates a separate bin per value so every value must be hit; a wildcard bin, which matches a bit pattern with don't-care bits; or a transition bin, which is hit when the variable passes through a sequence of values over successive samples. There are also automatic bins, the default when you don't specify any — SystemVerilog creates one bin per value, capped by auto_bin_max, which is fine for small enumerations but meaningless on wide variables. The exclude job is done by ignore_bins: values that are removed from coverage entirely, not counted and not required, used for reserved or unreachable values so the coverpoint can actually close. The forbid job is done by illegal_bins: values that must never occur, and if one is sampled, a runtime error fires and the test fails — so it's a check, not a measurement. The key insight is that every value of a variable should be assigned the right job: reachable values you want to exercise go in counting bins, values the DUT legally never produces go in ignore_bins so they don't block closure, and values that must never occur go in illegal_bins so a violation fails the test. Putting a value in the wrong job breaks the measurement — an unreachable value left in a counting bin blocks closure forever, and a forbidden value left out of illegal_bins lets a violation pass silently. The mental model is a sorting machine: each value is routed to a bucket, discarded, or trips an alarm.
The difference is one creates a single bin for the whole set and the other creates a separate bin per value, and it matters because it's the difference between requiring one value and requiring all of them. If you write bins b equals a range like 0 to 7, you get one bin that is hit by any value in that range. The first transaction with any value 0 through 7 fills it, and the bin reads covered — so the coverpoint reaches 100% trivially, regardless of which specific value occurred. If you write bins b with empty brackets equals the same range 0 to 7, you get an array of eight separate bins, b[0] through b[7], one per value. Now every one of those eight values must occur for the coverpoint to close — 100% means all eight were exercised. So the only syntactic difference, the empty brackets, flips the meaning from require one to require all. This matters enormously because it's one of the most common silent coverage bugs. Suppose you have an ID field with eight distinct IDs, and you want to verify every ID was exercised. If you accidentally write bins b instead of bins b[], the coverpoint reads 100% after a single transaction with any ID, and you'd believe all IDs were tested when only one was. The coverage looks complete but means almost nothing — the false-100% problem pinned to a specific syntax error. The rule of thumb is: when each value of a set is a distinct functional case you need to exercise — IDs, modes, opcodes, source ports — use the array form b[] so each gets its own bin and all are required. When a range is functionally uniform and you just want some representative value from it, a single bin b is fine. So before trusting a coverpoint at 100%, check whether its bins are b or b[], because that determines whether 100% means one value occurred or all of them did. It's the smallest syntax with some of the largest consequences in coverage.
Transition bins measure sequences of values over successive samples rather than individual values, and you need them whenever the scenario you're covering is temporal — a state path, a mode change, a back-to-back pattern. A transition bin is written with the arrow syntax, like bins t equals a then b then c, and it is hit only when the variable takes value a on one sample, then b on the next, then c on the next — the exact sequence in order. No single value hits it; the sequence must complete across samples. This is fundamentally different from value bins, which only record which values occurred, not in what order. Many important verification scenarios are about order. A state machine should be exercised through specific paths — idle to busy to done, or idle to busy to error — and each path is a transition. A protocol might require covering a request immediately followed by another request, a back-to-back case that stresses pipelining, which is a transition from one transaction type to itself. A mode change from low-power to active to low-power is a transition. None of these can be expressed by value bins, because a value bin would just record that each individual state or mode occurred, not that they occurred in the meaningful sequence. You can write multiple transitions in one bin, like a to b or b to a for a ping-pong, and you can write longer sequences and even repetition. So you reach for transition bins whenever the coverage plan says something like exercise this sequence, this path, this ordering, or this back-to-back pattern. The waveform view makes it concrete: the transition bin stays unhit as the variable moves through the early values and only fires at the sample where the full sequence completes. If you find yourself wanting to cover that something happened after something else, that's a transition bin. Without them, your coverage measures presence of values but is blind to order, missing an entire class of temporal scenarios where bugs often live.
You use ignore_bins for values that are legal but you don't want to count — typically reserved or unreachable values — and illegal_bins for values that must never occur, where their occurrence is a bug. They behave oppositely at run time. ignore_bins is passive: a value matching it is silently excluded from coverage — not counted, not required, removed from the denominator. illegal_bins is active: a value matching it triggers a runtime error and fails the test the instant it's sampled. The decision comes from the value's status in the spec. If a value is reserved or unreachable — the DUT legally never produces it, so it simply won't appear — you use ignore_bins. The reason is closure: if such a value were left in a counting bin, that bin could never be hit, and the coverpoint could never reach 100%, blocking closure forever while the team chases a value that can't occur. Excluding it with ignore_bins removes it from the coverage denominator so the coverpoint counts only reachable values and closure is attainable. If instead a value must never occur — its appearance would be a protocol violation or a design error — you use illegal_bins. Here you want the opposite of silence: you want the test to fail loudly the moment it happens, turning the coverpoint into a checker. Leaving such a value in ignore_bins or a normal bin would let the violation pass undetected — either silently ignored or counted as just another covered case. So the distinction is exclude-from-measurement versus add-a-check. A concrete example: a 3-bit mode field where only modes 0 to 4 are defined. If modes 5 to 7 are reserved and the decoder clamps them so they never appear, ignore_bins them so the coverpoint can close on the 5 real modes. But if modes 5 to 7 appearing would indicate a control bug — they should be physically impossible and seeing one means something is broken — illegal_bins them so the test fails. The spec language usually tells you which: reserved-and-harmless leans ignore, must-never-occur leans illegal. Getting this wrong either blocks closure or misses a violation.
Automatic bins are appropriate for small enumerations where every value is a distinct, reachable case, and dangerous on wide variables or ranges with reserved values, because they blindly create one required bin per value with no judgment about meaning or reachability. When you write a coverpoint with no explicit bins, SystemVerilog auto-generates bins: conceptually one per value of the variable, capped at auto_bin_max, which defaults to 64. For a small enum — say an operation type with three values, or a 2-bit field with four — this is perfect: you get one bin per value, all reachable, all meaningful, and 100% means every value was exercised. The danger appears in two cases. First, wide variables: a 32-bit field has billions of values. Auto bins either try to bin per value, which never closes, or cap at auto_bin_max and lump values into arbitrary buckets that don't correspond to anything functional — coverage that's meaningless whether it's high or low. You should never auto-bin a wide field; you partition it into functionally-distinct explicit bins instead. Second, ranges with reserved or unreachable values: auto bins make every value a required counting bin, including ones the DUT can never produce. This is the closure-blocker bug — a mode field that's 3 bits but only uses 5 of 8 values will auto-bin all 8, and the 3 reserved ones can never be hit, so coverage is permanently capped below 100% and the team wastes effort chasing unreachable values. The fix is explicit bins: counting bins for the reachable values, ignore_bins for the reserved ones, illegal_bins for the forbidden ones. So the rule is: auto bins are fine when the variable is a small, fully-used enumeration where every value is reachable and meaningful, and you should switch to explicit bins the moment the variable is wide, has reserved values, has values that must be forbidden, or needs a partition coarser than one-per-value. Auto bins have no idea which values are reachable, meaningful, or forbidden — they treat every value as an equally-required case, which is only correct for the simplest fields.
Exercises
- Choose b or b[]. For an 8-entry ID field where every ID must be exercised, write the bin declaration and explain why the other form would mislead.
- Assign the jobs. For a 3-bit mode field with modes 0–4 used, 5–6 reserved, and 7 forbidden, write the counting, ignore, and illegal bins.
- Write a transition. Express coverage of a state machine path idle → active → flush → idle as a transition bin.
- Fix the stuck coverpoint. A coverpoint is stuck at 88% across all seeds. List the steps to determine whether the unhit bins are unreachable.
Summary
- A bin is a named bucket with a rule that classifies a sampled value, and bins have three jobs: require (counting bins), exclude (
ignore_bins), and forbid (illegal_bins). - Counting bins have a vocabulary: a value (
{5}), a range ({[1:4]}— any value in range), a list ({1,5,9}), an array (bins b[]— one bin per value, all required, vsbins b— one for all, trivially hit), a wildcard ({1??0}— pattern), or a transition ((a => b => c)— a sequence over time). ignore_binsexcludes values from coverage (reserved/unreachable — not counted, not required, so the coverpoint can close);illegal_binsforbids values (must never occur — sampling one fails the test, a check).- Automatic bins (one per value, capped by
auto_bin_max) are the default for small enumerations but meaningless on wide variables and block closure on partially-reserved ranges — use explicit bins with the right jobs. - The durable rule of thumb: every value of a variable needs the right bin job — reachable values you want to exercise go in counting bins (and use
b[]notbwhen each value is a distinct case), values the DUT legally never produces go inignore_binsso the coverpoint can close, and values that must never occur go inillegal_binsso a violation fails the test; a coverpoint stuck below 100% is often requiring an unreachable value, and a missed violation is often a forbidden value with no illegal bin.
Next — Cross Coverage: the most powerful — and most explosive — coverage construct: the cross. Measuring not just each variable alone but their combinations — every operation at every size, every mode in every state — why crosses matter (bugs hide in combinations), how the combination space explodes, and how to control it with bin selection and ignore bins so a cross measures the meaningful combinations rather than an unclosable product.