Skip to content

SystemVerilog · Module 11

Automatic Bins

The simulator's default bin partition when no bins block is declared. The 1-bin-per-value vs bucketed regimes at the auto_bin_max boundary, the 100%-coverage-that-means-nothing trap on wide fields, and the prototype-to-sign-off lifecycle.

Module 11 · Page 11.3

What Automatic Bins Are

When a coverpoint declares no bins { … } body, the simulator generates automatic bins — a tool-managed partition of the value space, capped by the option.auto_bin_max setting (default 64). For narrow types (≤ 64 distinct values), each distinct value gets its own bin. For wider types, the value space is split into ≤ 64 equally-sized buckets and one bin is created per bucket.

Auto bins are the SystemVerilog answer to "give me some coverage measurement for this signal, right now, with zero up-front design work." They land in a working coverage report in the first minutes of a project. They are also, in any non-trivial design, the wrong thing to ship — for reasons this page makes precise.

SystemVerilog — coverpoints with auto bins (no explicit bins block)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_default;
    // 2-bit state → 4 distinct values → 4 auto bins:
    //   auto[0]  ← state == 2'b00
    //   auto[1]  ← state == 2'b01
    //   auto[2]  ← state == 2'b10
    //   auto[3]  ← state == 2'b11
    cp_state: coverpoint state;
 
    // 8-bit data → 256 distinct values, capped at auto_bin_max = 64:
    //   auto[0]  ← values 0..3      (bucket of 4)
    //   auto[1]  ← values 4..7
    //   ...
    //   auto[63] ← values 252..255
    cp_data: coverpoint data;
 
    // 32-bit address → 2^32 values, capped at auto_bin_max = 64:
    //   each auto bin covers ~67 million values
    cp_addr: coverpoint paddr;
endgroup

Synthesisability. Simulation-only, every line — like every other covergroup construct. Auto bins do not affect what synthesis produces; they only change how a report reads.

The Two Regimes — Narrow vs Wide Types

The behaviour switches sharply at the auto_bin_max boundary. Knowing which regime your coverpoint lands in is the difference between a useful auto-bin report and a misleading one.

Regime A — Narrow (one bin per value)

For enum types and small bit-width fields (state machines, opcodes, modes), 1-bin-per-value is often exactly what you want. The auto-bin name maps to the value, the report is readable, and every distinct value the spec defines becomes its own coverage row.

SystemVerilog — narrow type, one bin per value
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum bit [1:0] {
    IDLE = 2'b00, READY = 2'b01, BUSY = 2'b10, ERR = 2'b11
} state_e;
 
covergroup cg_fsm;
    cp_state: coverpoint state;   // 4 values → 4 auto bins
endgroup
 
// Expected coverage report (urg-style):
//   cp_state   auto[IDLE]     100.0%   ( 412 hits )
//              auto[READY]    100.0%   ( 198 hits )
//              auto[BUSY]      100.0%   (  87 hits )
//              auto[ERR]        0.0%   (   0 hits )  ← coverage hole
//
// The report aligns with the spec: every documented state has a row,
// the unhit state is named and visible, and the missing scenario is
// immediately the next directed test to write.

For narrow types, auto bins are a legitimate ship-ready coverage model, provided the verification plan's interesting partition really is "one bin per value." For an FSM whose plan is "exercise every state," this is the case.

Regime B — Wide (bucket-of-equal-ranges)

For 8-bit and wider fields, the partition is generated mechanically: take the value range, divide by auto_bin_max, emit one bin per equal-sized bucket. The buckets have no relation to the design's interesting regions — they are arithmetic, not architectural.

SystemVerilog — wide type, bucketed auto bins
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_wide;
    cp_paddr: coverpoint paddr;          // 32-bit, 64 buckets of ~67M values each
    cp_data:  coverpoint pwdata;         // 32-bit, 64 buckets of ~67M values each
endgroup
 
// Expected coverage report after 1000 random transactions:
//   cp_paddr   auto[0]      100.0%   (  18 hits )    addr 0x00000000..0x03FFFFFF
//              auto[1]      100.0%   (  14 hits )    addr 0x04000000..0x07FFFFFF
//              auto[2]      100.0%   (  16 hits )    addr 0x08000000..0x0BFFFFFF
//              ...           ...        ...
//              auto[63]     100.0%   (  17 hits )    addr 0xFC000000..0xFFFFFFFF
//
// Aggregate coverage: 100.0% across 64 bins.
//
// What the report does NOT tell you:
//   • Whether 0x00000000 (the spec's reset vector) was ever hit
//   • Whether 0x10000000 (the documented RAM base) was ever hit
//   • Whether 0x1FFFFFFF (the documented RAM last-byte corner) was ever hit
//
// Each of those is one specific address inside a 67-million-value bucket.
// Hitting any address in the bucket lights the bin. The corner case is
// invisible from this report.

This is the trap auto bins set for unwary coverage models: at 100% bucket coverage, the report says "complete" while every interesting corner case in the spec sits unverified inside its bucket. No simulator warning. No regression failure. The bug ships.

Controlling Auto Bins — auto_bin_max

The auto_bin_max option is your single lever for tuning auto-bin behaviour. It can be set at three scopes; the most specific wins.

SystemVerilog — controlling auto_bin_max at three scopes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_tuned;
    // ── Per-coverpoint — narrowest scope, wins over the others ────
    cp_data: coverpoint data {
        option.auto_bin_max = 16;   // 8-bit data → 16 buckets of 16 values
    }
 
    // ── Per-covergroup instance via option ────────────────────────
    cp_addr: coverpoint paddr;
 
    option.auto_bin_max = 8;        // applies to this instance's
                                     // unbinned coverpoints (here, cp_addr)
endgroup
 
// ── Per-covergroup TYPE via type_option ──────────────────────────
//    Applies to every instance of this covergroup type
covergroup cg_globally_tuned;
    cp_addr: coverpoint paddr;
 
    type_option.auto_bin_max = 4;   // every instance of cg_globally_tuned
                                     // gets 4 buckets per unbinned coverpoint
endgroup

The scope rule matters more than the syntax: option is per-instance; type_option is per-type and shared across every instance. Mixing them carelessly leads to per-instance reports that contradict the type-level report — a class of bug that surfaces only at coverage merge time.

Choosing a value

  • auto_bin_max = 64 (the default) — leave it. Anything you change without a reason makes the report drift from documentation.
  • auto_bin_max = N where N ≤ distinct values of the type — equivalent to bucketing into N equal ranges. Useful only when the design genuinely has N equally-interesting regions, which is almost never true. If you want N specific regions, use explicit bins and name them.
  • auto_bin_max = 1 — collapses the coverpoint to "was this field ever sampled?" with no value differentiation. Almost never useful, but occasionally appropriate for boolean-like derived expressions.

If you find yourself tuning auto_bin_max to "make the number look better," stop. The right fix is explicit bins — auto bins are not a knob you tune toward sign-off, they are a placeholder you replace.

A Working Example — Comparing Auto Bins to Explicit Bins on the Same Field

The trap auto bins set is best made concrete. Here is the same APB transaction modelled twice — once with auto bins, once with explicit verification-plan bins — and the coverage reports the two models produce after the identical 1000-transaction regression.

SystemVerilog — same field, two coverage models
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn;
    rand bit [31:0] paddr;
 
    // ── Model A — auto bins (the prototype) ──────────────────────
    covergroup cg_auto;
        cp_addr_auto: coverpoint paddr;          // 64 auto bins, ~67M values each
    endgroup
 
    // ── Model B — explicit bins (the verification plan) ──────────
    covergroup cg_explicit;
        cp_addr_plan: coverpoint paddr {
            bins  csr_region    = {[32'h0000_0000 : 32'h0000_00FF]};
            bins  ram_region    = {[32'h1000_0000 : 32'h1FFF_FFFF]};
            bins  mmio_region   = {[32'h2000_0000 : 32'h2FFF_FFFF]};
            bins  reset_vector  = {32'h0000_0000};                      // single corner
            bins  ram_last_byte = {32'h1FFF_FFFF};                      // single corner
            bins  ram_first_byte= {32'h1000_0000};                      // single corner
        }
    endgroup
 
    function new();
        cg_auto     = new();
        cg_explicit = new();
    endfunction
 
    function void post_randomize();
        cg_auto.sample();
        cg_explicit.sample();
    endfunction
endclass

How to simulate it. Compile under any IEEE 1800-compatible simulator with coverage enabled (vsim -coverage, vcs -cm cond+fsm+tgl+branch+cov, xrun -coverage all). Drive 1000 randomised apb_txn objects.

Expected output — Model A (auto bins, urg-style report).

cg_auto::cp_addr_auto — auto bins after 1000 transactions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Group   cg_auto                              coverage   100.0%
  cp_addr_auto   auto[0]   100.0%  ( 18 hits )   range 0x00000000..0x03FFFFFF
                 auto[1]   100.0%  ( 14 hits )   range 0x04000000..0x07FFFFFF
                 auto[2]   100.0%  ( 16 hits )   range 0x08000000..0x0BFFFFFF
                 ...        ...      ...
                 auto[63]  100.0%  ( 17 hits )   range 0xFC000000..0xFFFFFFFF
 
Aggregate: 100.0% (64/64 bins)

Expected output — Model B (explicit bins, same regression).

cg_explicit::cp_addr_plan — explicit bins after the same 1000 transactions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Group   cg_explicit                          coverage    50.0%
  cp_addr_plan  csr_region        100.0%  ( 348 hits )
                ram_region        100.0%  ( 421 hits )
                mmio_region       100.0%  ( 188 hits )
                reset_vector        0.0%  (   0 hits )   ← gap
                ram_last_byte       0.0%  (   0 hits )   ← gap
                ram_first_byte      0.0%  (   0 hits )   ← gap
 
Aggregate: 50.0% (3/6 bins)

What this proves. The same simulation. The same transactions. The same DUT. Model A reports 100% complete. Model B reports 50% complete, with three named gaps that are the next directed tests. The auto-bin model was not wrong — it answered a question (every bucket got some hit) that does not correspond to verification completeness. The explicit-bin model answered the question that does (every plan row had its specific scenario exercised).

This is the failure mode every team learns the hard way at least once. The remediation is structural, not procedural — replace auto-binned wide coverpoints with explicit bins matched to the plan, before sign-off, every time.

Common Pitfalls — The Auto-Bin Traps

Three patterns recur in every team's first encounter with auto bins.

Pitfall 1 — A 100% auto-bin coverpoint is treated as plan closure

The covergroup compiles, the report says 100%, the verification lead checks the box. The coverpoint had no bins declaration, so the simulator emitted 64 arithmetic buckets, the regression hit each one, the number is 100. None of the verification-plan corner cases have been verified.

Fix: every coverpoint that ships in a sign-off model has an explicit bins body matched to the plan. Auto-binned ship-ready coverpoints are an audit failure.

Pitfall 2 — auto_bin_max is tuned to make the number bigger

A coverpoint reports 12%. The engineer sets option.auto_bin_max = 4 and the same regression now reports 100%. Same scenarios were exercised; the number changed because the partition is now coarser. The metric moved in a way that has nothing to do with verification work.

Fix: auto_bin_max is documentation of intent, not a tuning knob. If the number is bad, the answer is more or better tests, not a different partition.

Pitfall 3 — Auto bins on an enum look fine, then a new enum member ships

The coverpoint cp_state has no bins body. With three enum members it auto-generates three bins, every one of them hits, the report reads 100%. The RTL team adds a fourth state. The covergroup recompiles, now auto-generates four bins, and the fourth bin is at 0%. The coverage number drops because the verification plan has implicitly changed.

Fix: even for enums, explicit bins (one bins name = {VAL}; per member) make the coverage contract explicit — and a new enum value forces a code review touch in the covergroup, which is where it should land.

SystemVerilog — pitfall 3, before and after
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Before — auto bins, looks fine ──────────────────────────────
typedef enum bit [1:0] { IDLE, READY, BUSY } state_e;
 
covergroup cg;
    cp_state: coverpoint state;        // auto: 3 bins, all hit → 100%
endgroup
 
// ── RTL adds an ERR state ───────────────────────────────────────
typedef enum bit [1:0] { IDLE, READY, BUSY, ERR } state_e;
 
// Now auto-generates 4 bins; ERR is unhit. Same covergroup,
// coverage number drops to 75%. Surprise plan change.
 
// ── After — explicit bins, contract is documented ───────────────
covergroup cg;
    cp_state: coverpoint state {
        bins idle  = {IDLE};
        bins ready = {READY};
        bins busy  = {BUSY};
        // Adding ERR is a deliberate covergroup edit — auditable.
    }
endgroup

Debug Lab — "Coverage Is Suspiciously High"

A real failure mode that auto bins enable. The verification lead reviews the dashboard before a tape-out gate review and sees cg_paddr: 100%. The dashboard is correct. The verification is not.

Buggy code
SystemVerilog — 100% coverage that means nothing
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn;
    rand bit [31:0] paddr;
    rand bit         pwrite;
 
    covergroup cg_apb;
        // ── The model the team shipped ────────────────────────────
        cp_addr: coverpoint paddr;            // 32-bit, default auto bins
        cp_dir : coverpoint pwrite {
            bins read  = {1'b0};
            bins write = {1'b1};
        }
    endgroup
 
    function new();
        cg_apb = new();
    endfunction
 
    function void post_randomize();
        cg_apb.sample();
    endfunction
endclass
 
// After a 10,000-transaction regression:
//   cg_apb              coverage 100.0%
//     cp_addr           100.0%  (64/64 auto buckets, all hit)
//     cp_dir            100.0%  (read + write, all hit)
//
// Tape-out gate review: green. Sign-off granted.
Symptom

The dashboard reads 100%. The verification plan has thirty rows on paddr — CSR region, RAM region, MMIO region, reset vector (0x0000_0000), RAM first byte (0x1000_0000), RAM last byte (0x1FFF_FFFF), MMIO sentinel (0x2FFF_FFFE), and so on. None of those rows correspond to a specific bin in the covergroup. The team passes the gate review, integrates, and bring-up discovers that the reset-vector access fault was never exercised in simulation — a silicon respin.

Root cause

The coverpoint cp_addr was declared without an explicit bins body. The simulator generated 64 arithmetic buckets of ~67 million addresses each. The regression's random distribution hit at least one address in every bucket, lighting every bin at 100%. The metric was structurally incapable of expressing the verification plan's rows. Sign-off was granted against a number that had no relationship to the plan.

The structural mistake is using auto bins on a wide field as a sign-off coverage model. The procedural mistake is treating "100% coverage" as a quality gate without inspecting what the bins represent.

Fix

Replace the auto-binned coverpoint with explicit bins matched to the verification plan. Re-run the regression. The new report will read lower — and the new lower number is the true coverage state the team has been at all along.

SystemVerilog — the explicit-bin replacement
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn;
    rand bit [31:0] paddr;
    rand bit         pwrite;
 
    covergroup cg_apb;
        cp_addr: coverpoint paddr {
            bins csr_region     = {[32'h0000_0000 : 32'h0000_00FF]};
            bins ram_region     = {[32'h1000_0000 : 32'h1FFF_FFFF]};
            bins mmio_region    = {[32'h2000_0000 : 32'h2FFF_FFFF]};
            bins reset_vector   = {32'h0000_0000};
            bins ram_first_byte = {32'h1000_0000};
            bins ram_last_byte  = {32'h1FFF_FFFF};
            bins mmio_sentinel  = {32'h2FFF_FFFE};
            // ... one bin per verification-plan row on paddr ...
        }
        cp_dir : coverpoint pwrite {
            bins read  = {1'b0};
            bins write = {1'b1};
        }
    endgroup
endclass

The discipline rule that prevents recurrence: no sign-off coverpoint on a field wider than the auto_bin_max setting may ship without an explicit bins body. Code review enforces it. Adding the rule to the team's coverage-style guide is the fix that scales; fixing the bug in one covergroup is the fix that doesn't.

Industry Context — Where Auto Bins Earn Their Keep

Auto bins are not always wrong. The places they earn their keep are narrow and worth knowing.

  • Prototype-stage verification environments. Week one of a new IP project, no verification plan written, the team needs a quick "is the testbench wired to coverage" smoke test. Auto-binned coverpoints land in the report immediately and prove the plumbing works. They are replaced wholesale within the first month.
  • Enum-typed signals where the plan really is one-bin-per-value. A small FSM with three states, an opcode field with eight legal values, a UVM phase enumeration — these are 1-bin-per-value by design. Auto bins on them happen to coincide with the plan. Explicit bins are still preferable for the "ERR state added in 2026" robustness argument (Pitfall 3 above), but auto bins are not actively wrong here.
  • Coverage smoke tests inside continuous-integration regressions. A small set of auto-binned coverpoints can serve as a "did the regression cover anything" liveness check, surfaced as a separate dashboard from the sign-off coverage report. Production teams sometimes maintain a tiny smoke-test covergroup alongside the real model for exactly this purpose.
  • Coverage-driven test bootstrapping. During early CRV tuning, before the verification plan is finalised, auto bins help discover what the random distribution naturally hits — useful as input to the plan, never as the plan itself.

In every other context — sign-off coverage, regression dashboards, gate reviews, contract delivery to integrators — explicit bins win, and the lifecycle is "auto bins now, explicit bins by the time it matters."

Interview Q&A — Ten Questions You Will Be Asked

A bin generated by the simulator when a coverpoint declares no explicit bins body. The simulator partitions the value space using the option.auto_bin_max setting (default 64) — one bin per value for narrow types (≤ 64 distinct values), or auto_bin_max equal-sized buckets for wider types.

Coding Guidelines — Six Rules for Auto-Bin Discipline

  1. Use auto bins as a prototype, not a product. They land in a working coverage report immediately; that is their entire purpose. Within the first project month, replace every auto-binned coverpoint with the explicit-bin model the verification plan requires.
  2. Never ship a sign-off coverpoint on a wide field with auto bins. "Wide" here means anything where 2^width > auto_bin_max — typically anything wider than 6 bits. The bucket partition is arithmetic, not architectural; the report becomes structurally incapable of expressing the plan.
  3. Prefer explicit bins on enums even when auto bins would coincide with the plan. A new enum member should force a deliberate covergroup edit, not a silent denominator drift. Explicit bins make the contract auditable.
  4. Do not tune auto_bin_max to improve a coverage number. The metric moved because the partition changed, not because verification work happened. The right response to a low number is more or better tests, not a coarser bin partition.
  5. Document any non-default auto_bin_max. A comment above the option setting must state the architectural reason for the value chosen. "Tuned for the dashboard" is not a reason.
  6. Maintain a dedicated smoke-test covergroup separate from the sign-off model. If you want a quick liveness check on the testbench's coverage plumbing, do it in a small named smoke covergroup — never by leaving auto-binned coverpoints in the sign-off model and "trusting nobody will read them as plan closure."

Summary

Automatic bins are the simulator's default partition of a coverpoint's value space — one bin per value for narrow types, auto_bin_max equal-sized buckets for wider types. They are useful as a smoke test and as a prototype, and they are a trap as a sign-off coverage model on any wide field — a 100% auto-bin report can coexist with every spec corner case unverified. The verification-plan-grade coverage model uses explicit bins matched to the plan's rows, which Module 11.4 (Named Bins), Module 11.5 (Wildcard / Illegal / Ignore Bins), and Module 11.7 (Cross Coverage) build out. Treat auto bins as scaffolding and replace them before sign-off.