SystemVerilog · Module 11
Named Bins — Explicit Coverage Partitions
The four explicit-bin forms — single-value, range, multi-value, and array — plus transition bins, per-bin options (at_least, weight, comment), and the discipline that every sign-off coverpoint on a wide field uses named bins mapped one-to-one to verification-plan rows.
Module 11 · Page 11.4
Why Named Bins Replace Auto Bins for Anything That Ships
Automatic bins (Module 11.3) partition a coverpoint's value space by arithmetic — auto_bin_max equal-sized buckets across the type's range. Convenient for a smoke test, useless for sign-off: the bucket boundaries do not align with the spec, so a 100% auto-bin report can coexist with every verification-plan corner case unverified.
Named bins are the explicit-bin partition that makes a coverpoint sign-off-ready. Each bins name = {…}; declaration creates one counted bin with an engineer-chosen identifier and an engineer-chosen value set. The bin name is the verification-plan row; the report becomes a plan-row scoreboard.
Every coverpoint that ships in a sign-off coverage model — every wide field, every protocol-flagged signal, every enum that is part of the plan — uses named bins. Module 11.3 explained why this matters; this page is how to write them.
The Four Explicit Bin Forms
Named bins come in four syntactic shapes. Each handles a different relationship between the bin and the values it tracks.
Form 1 — Single-value bins
One bin covers exactly one value. The right shape for named protocol constants, FSM states, opcodes, error codes, reserved markers — anywhere each distinct value has its own spec-defined meaning.
typedef enum bit [2:0] {
OP_NOP = 3'b000,
OP_ADD = 3'b001,
OP_SUB = 3'b010,
OP_MUL = 3'b011,
OP_DIV = 3'b100
} opcode_e;
covergroup cg_opcode;
cp_op: coverpoint opcode {
bins nop = {OP_NOP}; // 1 value → 1 bin
bins add = {OP_ADD}; // ...
bins sub = {OP_SUB};
bins mul = {OP_MUL};
bins div = {OP_DIV};
}
endgroup
// Five bins total; each must hit option.at_least times for 100% on cp_op.
// Adding a new opcode to opcode_e WITHOUT extending this covergroup is a
// deliberate decision — code review will surface the omission.The discipline rule that follows: for enum types whose spec defines per-value meaning, explicitly enumerate every legal value as its own named bin. The cost is small (one line per value), the audit trail is permanent, and a new enum member becomes a deliberate covergroup edit rather than a silent denominator change (Module 11.3 Pitfall 3).
Form 2 — Range bins
One bin covers a contiguous range of values written as {[lo:hi]}. The right shape for spec-defined regions where the region is the scenario, not each individual value — memory address regions, payload-size categories, latency buckets.
covergroup cg_apb_address;
cp_addr: coverpoint paddr {
bins csr_region = {[32'h0000_0000 : 32'h0000_00FF]}; // 256 values, 1 bin
bins ram_region = {[32'h1000_0000 : 32'h1FFF_FFFF]}; // 256M values, 1 bin
bins mmio_region = {[32'h2000_0000 : 32'h2FFF_FFFF]}; // 256M values, 1 bin
bins boot_region = {[32'hF000_0000 : 32'hF000_0FFF]}; // 4K values, 1 bin
}
endgroup
// Four bins total. Hitting any address in 0x0..0xFF lights csr_region;
// hitting any address in 0x1000_0000..0x1FFF_FFFF lights ram_region; etc.Range bins are the workhorse form for wide fields. The bin partition is intentional — chosen by the engineer to match the spec's memory map, the protocol's documented size classes, or the design's documented latency budgets — rather than arithmetic, the way auto bins are.
Form 3 — Multi-value (mixed) bins
A single bin can list multiple non-contiguous values, multiple ranges, or any mix of the two. All values listed in one bins declaration map to that single bin. The right shape when several logically-related but disjoint values share the same verification-plan meaning.
covergroup cg_priority_classes;
cp_prio: coverpoint priority_level {
bins urgent = {0, 1, 2}; // three specific values → one bin
bins normal = {3, 4, 5}; // three specific values → one bin
bins low = {[6:15]}; // range → one bin
bins reserved_corners = {0, 7, 15}; // any combination — allowed
}
endgroup
// Four bins total. priority_level = 1 lights urgent (not "bin 1");
// priority_level = 7 lights low (it's in [6:15]) AND reserved_corners.
// A single sample can hit multiple bins if its value falls in multiple
// bin definitions — this is sometimes desired (corner-and-region) and
// sometimes accidental. Worth knowing.Multi-value bins are useful for spec rows that group several distinct values under one named scenario (e.g. "the three priority levels that the spec calls urgent"). They are also where most accidental-overlap bugs occur — a single sample hitting both low and reserved_corners increments both counters, which is correct LRM behaviour but surprising if you expected mutually-exclusive bins.
Form 4 — Array bins (bins name[])
The form bins name[] = {[lo:hi]}; (with empty brackets) instructs the simulator to create one separate bin per value in the range, named name[0], name[1], …, name[N-1]. The right shape when a range deserves per-value visibility rather than a single aggregate count.
covergroup cg_burst_length;
// bins burst_len[] — one bin per value in [1:16] → 16 separate bins
cp_burst: coverpoint burst_len {
bins burst_len[] = {[1:16]};
}
endgroup
// Coverpoint cp_burst now has 16 named bins:
// burst_len[1], burst_len[2], ..., burst_len[16]
// EACH must be hit option.at_least times for the coverpoint to read 100%.
//
// Compare with `bins burst_len = {[1:16]};` (no brackets):
// Single bin covering the whole range — hitting any one value in [1:16]
// lights the bin to 100%. Coverage looks great, says little.
// You can also array-name an explicit list:
covergroup cg_irq_lines;
cp_irq: coverpoint active_irq {
bins line[] = {0, 3, 7, 11, 15}; // 5 separate bins, one per IRQ line
}
endgroupArray bins are the explicit-bins answer to the "auto bins are too coarse, single-bin range is too loose" middle ground. Use them when each value in the range deserves its own row in the report — typically for burst lengths, IRQ lines, channel IDs, and short enumerations.
Transition Bins — A Named-Bin Form for Sequences
A transition bin is a named bin whose value is not a static set but a temporal sequence across consecutive samples. Written as bins name = (V1 => V2) (or longer chains), it counts how many times the coverpoint's value moved through the specified sequence.
typedef enum bit [1:0] {
IDLE = 2'b00,
ACTIVE = 2'b01,
DONE = 2'b10,
ERROR = 2'b11
} state_e;
covergroup cg_fsm_transitions @(posedge clk);
cp_state: coverpoint state {
// ── Single-step transitions ────────────────────────────────
bins idle_to_active = (IDLE => ACTIVE);
bins active_to_done = (ACTIVE => DONE);
bins done_to_idle = (DONE => IDLE);
// ── Error-recovery transitions ─────────────────────────────
bins active_to_error = (ACTIVE => ERROR);
bins error_to_idle = (ERROR => IDLE);
// ── Multi-step sequence — happy path captured in one bin ───
bins full_cycle = (IDLE => ACTIVE => DONE => IDLE);
// ── Repeating value — N consecutive samples on the same value
bins stuck_in_idle = (IDLE [* 5]); // IDLE five times in a row
// ── Range transition — any state → ERROR, regardless of source
bins anything_to_err = (IDLE, ACTIVE, DONE => ERROR);
}
endgroupWhat this code does. Defines a covergroup sampled on every posedge clk. The first three bins cover the happy-path single-step transitions; the next two cover error and recovery; the multi-step bin counts the full state-machine cycle as one event; the [* 5] form counts repeated occurrences; the final bin uses the multi-source transition syntax to cover any-state → ERROR without writing each source explicitly.
How to simulate it. Instantiate the covergroup at any layer where state is visible (typically a monitor or interface block). Drive the DUT through realistic and error sequences for ≥ 1000 clock cycles. Dump coverage with the simulator's reporter.
Expected output (urg-style).
Group cg_fsm_transitions coverage 75.0%
cp_state idle_to_active 100.0% ( 142 hits )
active_to_done 100.0% ( 139 hits )
done_to_idle 100.0% ( 138 hits )
active_to_error 100.0% ( 8 hits )
error_to_idle 100.0% ( 8 hits )
full_cycle 100.0% ( 138 hits )
stuck_in_idle 0.0% ( 0 hits ) ← gap
anything_to_err 100.0% ( 8 hits )What to read out of this. The single-cycle and error-recovery transitions all hit. The stuck_in_idle bin (five consecutive IDLE samples) never fired — the random stimulus kept the FSM moving, no test exercised an extended-idle scenario. The report names the gap; the next directed test is "hold the input quiescent for at least five clocks."
A Complete Working Example — Memory Controller with Named Bins Throughout
The pattern below combines every form from the page in one coherent coverage model. It is representative of what production verification code on a memory controller looks like — explicit single-value bins for the command enum, range bins for address regions, array bins for burst lengths, multi-value bins for byte-strobe categories, and transition bins for command sequencing.
typedef enum bit [2:0] {
CMD_NOP = 3'b000,
CMD_READ = 3'b001,
CMD_WRITE = 3'b010,
CMD_RFR = 3'b011, // refresh
CMD_PRECHG = 3'b100, // precharge
CMD_ACT = 3'b101 // activate
} mem_cmd_e;
class mem_txn;
rand mem_cmd_e cmd;
rand bit [15:0] addr;
rand bit [ 3:0] burst_len; // 1..16 beats per burst
rand bit [ 3:0] byte_strobe; // 4-bit strobe for sub-word writes
covergroup cg_mem;
// ── Command coverage — single-value bins per legal opcode ──
cp_cmd: coverpoint cmd {
bins nop = {CMD_NOP};
bins rd = {CMD_READ};
bins wr = {CMD_WRITE};
bins rfr = {CMD_RFR};
bins prchg = {CMD_PRECHG};
bins act = {CMD_ACT};
}
// ── Address coverage — range bins per memory region ────────
cp_addr: coverpoint addr {
bins boot_rom = {[16'h0000 : 16'h0FFF]}; // 4 KB
bins sram = {[16'h1000 : 16'h3FFF]}; // 12 KB
bins dram = {[16'h4000 : 16'hBFFF]}; // 32 KB
bins mmio = {[16'hC000 : 16'hEFFF]};
bins boot_first = {16'h0000}; // explicit corner
bins dram_last = {16'hBFFF}; // explicit corner
}
// ── Burst length coverage — array bins for per-value rows ──
cp_burst: coverpoint burst_len {
bins len[] = {[1:16]}; // 16 individual bins burst_len[1]..burst_len[16]
}
// ── Byte strobe coverage — multi-value bins per shape ──────
cp_strb: coverpoint byte_strobe {
bins full_word = {4'b1111};
bins half_word = {4'b0011, 4'b1100};
bins single_byte = {4'b0001, 4'b0010, 4'b0100, 4'b1000};
bins unaligned = {4'b0101, 4'b1010, 4'b0110, 4'b1001};
}
// ── Command sequence coverage — transition bins ────────────
cp_cmd_seq: coverpoint cmd {
bins wr_then_rd = (CMD_WRITE => CMD_READ);
bins rd_then_wr = (CMD_READ => CMD_WRITE);
bins b2b_writes = (CMD_WRITE => CMD_WRITE);
bins rfr_then_act = (CMD_RFR => CMD_ACT);
bins act_then_rd = (CMD_ACT => CMD_READ);
bins prchg_then_act = (CMD_PRECHG => CMD_ACT);
bins full_rfr_cycle = (CMD_RFR => CMD_NOP => CMD_NOP => CMD_ACT);
}
endgroup
function new();
cg_mem = new();
endfunction
function void post_randomize();
cg_mem.sample();
endfunction
endclassWhat this code does. Models a small memory controller's transaction class and embeds a covergroup that exercises every named-bin form. Six single-value bins for the command enum keep the coverage contract aligned with the documented opcode set. Six range/corner bins for the address space mirror the memory map and the spec-flagged corner addresses. Sixteen array bins force per-burst-length visibility. Four multi-value strobe bins group the spec's documented write shapes. Seven transition bins cover the command sequences that matter for refresh / activate / read interaction.
Synthesisability. Simulation-only. The covergroup compiles into measurement infrastructure that the synthesis tool strips entirely.
Per-Bin Options — option.at_least, option.weight, and the Comment
Named bins can carry per-coverpoint and (via the cross construct) per-cross options. The two that matter most often:
option.at_least = N— a bin is "covered" only after it has been hit N times, not just once. Useful for bins where a single accidental hit would lie about real coverage (single-event upsets, race-condition windows).option.weight = W— the coverpoint contributesWunits to the overall covergroup coverage percentage. Higher weight = more important. Default1.option.comment = "..."— annotates the coverpoint in the report. Use it to cite the verification-plan row this coverpoint implements; the comment becomes the audit trail.
covergroup cg_critical;
// High-importance, requires 10 hits per bin for 100%
cp_critical_op: coverpoint critical_opcode {
option.weight = 10;
option.at_least = 10;
option.comment = "vplan row §7.3: every CRITICAL opcode shall "
+ "be exercised at least 10 times per regression";
bins reset_op = {OP_RESET};
bins shutdown_op = {OP_SHUTDOWN};
bins error_inj = {OP_ERROR_INJ};
}
// Standard-importance, default weight = 1, single hit = covered
cp_standard_op: coverpoint standard_opcode {
option.comment = "vplan row §7.1: standard opcode catalogue";
bins nop = {OP_NOP};
bins add = {OP_ADD};
bins sub = {OP_SUB};
}
endgroupThe option.weight distinction matters at the covergroup level: the covergroup's overall percentage is Σ(coverpoint_pct × weight) / Σ(weight). A coverpoint with weight = 10 carries ten times the influence of a default-weight coverpoint in the headline number. This is the right shape for spec-flagged "critical" sections — the dashboard feels the gap in critical coverage more sharply than the gap in routine coverage.
Common Pitfalls — Where Named Bins Go Wrong
Pitfall 1 — Bins overlap silently
covergroup cg;
cp: coverpoint payload_size {
bins small = {[0:63]};
bins medium = {[64:255]};
bins corner = {0, 255, 256}; // 0 ALSO lives in 'small'; 255 ALSO in 'medium'
}
endgroup
// A sample of payload_size = 0 increments BOTH small AND corner.
// A sample of payload_size = 255 increments BOTH medium AND corner.
// The report shows healthier coverage than the regression actually achieved.Fix: if you intend overlap (corner-and-region), document it in option.comment. If you don't, partition the value space so every sample lights exactly one bin.
Pitfall 2 — Range bins covering values the type can't hold
covergroup cg;
bit [3:0] nibble; // legal range 0..15
cp: coverpoint nibble {
bins zero = {0};
bins mid = {[8:15]};
bins large = {[16:31]}; // ← values nibble can never take
}
endgroup
// 'large' can never be hit. Coverage will never read 100% on cp.
// The simulator does not warn — the bin is syntactically legal.Fix: verify every bin's value set against the coverpoint's type range. Lint tools (Spyglass cov, JasperGold lint) detect this as BIN_OUT_OF_RANGE in default rule sets; turn the rule on.
Pitfall 3 — Single-bin range instead of array bin
covergroup cg;
cp_burst: coverpoint burst_len {
bins all_burst_lengths = {[1:16]}; // ← one aggregate bin
}
endgroup
// A 1000-transaction regression that only ever drives burst_len = 4 reports
// cp_burst at 100% (the bin was hit), while 15 of the 16 documented burst
// lengths were never exercised. Same shape as the auto-bin trap from 11.3.Fix: when each value in a range deserves a separate report row, use bins name[] = {[lo:hi]}; (array bins, Form 4). When the range itself is the scenario, the single-bin form is correct — be explicit about which intent you have.
Pitfall 4 — Bin names that describe values, not scenarios
bins addr_0 = {32'h0000_0000}; // describes the value
bins addr_FF = {32'h0000_00FF};
// vs.
bins reset_vector = {32'h0000_0000}; // describes the scenario
bins csr_last_byte = {32'h0000_00FF};Fix: the bin name is read by the verification-plan audit, the dashboard, the engineer triaging gaps. Name for the scenario the bin verifies, not for the literal value. The good names survive a slug refactor or a memory-map remap; the bad names go stale silently.
Debug Lab — "The Bin Definition Looks Right, the Coverage Says Otherwise"
A real failure mode that named-bin coverage models hide if you don't audit them.
class apb_txn;
rand bit [31:0] paddr;
covergroup cg;
cp_addr: coverpoint paddr {
bins csr_region = {[32'h0000_0000 : 32'h0000_00FF]};
bins ram_region = {[32'h1000_0000 : 32'h1FFF_FFFF]};
// ── BUG: the boot region is documented at 0xF000_0000+
// but the bin says 0xE000_0000+ — typo in the hex
bins boot_region = {[32'hE000_0000 : 32'hE000_0FFF]};
// ── BUG: the CSR last-byte corner is 0x000000FF but the
// bin says 0x000000F0 (one nibble wrong)
bins csr_last_byte = {32'h0000_00F0};
}
endgroup
function new();
cg = new();
endfunction
function void post_randomize();
cg.sample();
endfunction
endclassThe covergroup compiles. The regression runs. The dashboard reports cp_addr 50.0% (2/4 bins hit). The verification engineer believes two real coverage gaps exist (boot_region, csr_last_byte) and writes directed tests to hit the documented boot region (0xF000_0000) and the CSR last byte (0x0000_00FF). The directed tests run, the regression re-runs, the dashboard still shows cp_addr 50.0%. Hours of debugging the tests follow — the tests appear to work, the bins do not light up.
The bin definitions themselves are wrong. The boot_region bin's value range is 0xE000_0000..0xE000_0FFF, not the documented boot region at 0xF000_0000..0xF000_0FFF. The csr_last_byte bin's value is 0x0000_00F0, not the documented corner 0x0000_00FF. The directed tests are hitting the correct addresses; the bins are watching the wrong addresses. The simulator has no way to detect this — every bin's value set is syntactically legal and inside the coverpoint's type range. The mismatch is between the covergroup and the spec, not anywhere visible from inside the simulation.
This is the most expensive class of bug in coverage models: the bin definition is wrong, the test is correct, and every diagnostic tool reports the test as the failure.
Fix the bin definitions to match the spec. Then add an audit step to the verification flow: every bin's value set must be cross-checked against the verification-plan row it implements.
covergroup cg;
cp_addr: coverpoint paddr {
bins csr_region = {[32'h0000_0000 : 32'h0000_00FF]};
bins ram_region = {[32'h1000_0000 : 32'h1FFF_FFFF]};
bins boot_region = {[32'hF000_0000 : 32'hF000_0FFF]}; // fixed
bins csr_last_byte = {32'h0000_00FF}; // fixed
option.comment = "vplan rows §3.1..§3.4 — memory map regions";
}
endgroupThe discipline rules that prevent recurrence:
- Every covergroup carries
option.commentthat cites the verification-plan rows it implements; the comment is the audit anchor. - Bin definitions are reviewed against the spec as part of code review, not as a post-hoc audit. The covergroup is treated as executable verification-plan code, because it is.
- A "coverage gap that survives a directed test" triggers an audit of the bin definition before another test is written. If the test is hitting the documented value and the bin is not lighting up, the bin's value set is the suspect.
The lint rule that detects part of this class: Spyglass cov's BIN_VALUE_NOT_IN_RANGE catches values outside the coverpoint's type range, but it cannot catch values that are inside the type range but disagree with the spec — that audit is human-only.
Industry Context — Where Named-Bin Discipline Pays Off
Named bins are the structural backbone of every shipping verification environment. Three places the discipline is most visible:
- Coverage-plan-to-bin traceability matrices. Production verification environments at major IP vendors (ARM, Synopsys VIP, Cadence VIPC, Mentor Questa VIP) maintain a many-to-one map from verification-plan rows to covergroup bins. The
option.commentfield is the mechanism — every bin cites the plan row it implements; a post-tape-out audit can reconstruct the traceability matrix from the covergroup source. - Regression-quality gates. Continuous-integration regressions in modern flows compare per-bin hit counts across consecutive nightly runs. A bin whose count drops to zero (because a regression-induced change broke its sampling path) raises a coverage regression alert separately from a functional regression alert. Named bins make these alerts actionable because the bin name names the scenario; auto-binned coverage cannot.
- Sign-off coverage reports for IP customers. When a verification IP ships to an integrator, the coverage report is a contract: "these are the scenarios we have verified at this version." Named bins make the report legible to the integrator's review team; auto-binned reports are essentially opaque and unusable as a contract artefact.
The cost of named-bin discipline is one line of bin definition per spec row. The cost of not having it is a coverage model nobody outside the originating team can audit — and the coverage model is the spec-witness in the verification environment, so the audit cost is project-critical.
Interview Q&A — Ten Questions You Will Be Asked
Single-value (bins name = {VAL};), range (bins name = {[lo:hi]};), multi-value (bins name = {a, b, [c:d]};), and array (bins name[] = {[lo:hi]};). The first three create one counted bin per declaration; the array form creates one bin per value in the range. Each form maps to a different relationship between the bin and the values it tracks — single value, contiguous region, grouped disjoint values, per-value visibility across a range.
Coding Guidelines — Seven Rules for Named-Bin Discipline
- Every sign-off coverpoint on a wide field uses named bins. Auto bins on wide fields lie about coverage; they belong only in smoke-test covergroups, never in the sign-off model.
- Bin names describe scenarios, not values.
reset_vector,csr_last_byte,ram_region— notaddr_0,addr_FF,addr_10000000. The good names survive memory-map remaps; the bad names go stale silently. - Partition the value space disjointly inside a coverpoint. Unless overlap is deliberate (and documented in
option.comment), bins should be mutually exclusive — one sample, one bin hit. Overlapping bins inflate the report. - Use array bins (
bins name[]) when each value deserves a separate report row. Range bins (bins name = {[lo:hi]};) report aggregate; array bins report per-value. Burst lengths, IRQ lines, channel IDs, short enumerations — array bins. - Annotate every coverpoint with
option.commentciting the verification-plan rows. The comment becomes the audit anchor; an external review can reconstruct the plan-to-coverage map from the covergroup source. - Use
option.at_leastfor bins where a single hit lies about coverage. Race-condition windows, transient errors, rare protocol states — raise the bar to a count that reflects "actually exercised," not "hit once by accident." - Audit bin value sets against the spec during code review. A wrong hex nibble in a bin definition produces a 0%-bin that the simulator cannot detect — only a human reading the spec alongside the covergroup will catch it. Treat the covergroup as executable verification-plan code, because it is.
Summary
Named bins are the explicit-bin partition that makes a coverpoint sign-off-ready. Four forms cover the cases: single-value for named constants, range for spec-defined regions, multi-value for grouped disjoint scenarios, and array bins for per-value visibility across a range. Transition bins extend the same naming discipline to temporal sequences. Per-bin options (at_least, weight, comment) tune the closure rule, the dashboard priority, and the audit trail. Pair every named bin with a verification-plan citation, partition value spaces disjointly, prefer array bins when each value matters, and audit bin definitions against the spec — that is the entire named-bin discipline. Module 11.5 builds on this with the special-purpose wildcard_bins, illegal_bins, and ignore_bins forms.