SystemVerilog · Module 11
Covergroups & Coverpoints
The two coverage primitives — covergroup as a class-like type, coverpoint as its measurable field. Three coverpoint forms, label semantics, the four legal scopes, the built-in method API, and the constructor-omission bug every team ships at least once.
Module 11 · Page 11.2
The Two Coverage Primitives
A covergroup is a user-defined type that encapsulates the coverage for one area of interest. A coverpoint is a tracked variable or expression inside that covergroup, partitioned into bins. Everything else in Module 11 — automatic bins, named bins, wildcard/illegal/ignore bins, transitions, crosses, options, sampling events — is mechanics layered on top of these two primitives.
A useful mental model: the covergroup is a class (a type that is instantiated, that owns per-instance state, that exposes built-in methods); the coverpoint is a member of that class (a tracked field with bins as its categories). Once you carry that picture, the rest of the syntax follows the rules you already know about SystemVerilog classes.
Syntax — Define, Instantiate, Sample, Query
A covergroup goes through four lifecycle phases. Every one of them must be present for coverage to land in the database.
// ── 1. Define the covergroup TYPE ─────────────────────────────────
covergroup cg_name @(sampling_event); // auto-sampled on event
coverpoint variable_name;
endgroup
// Alternate form — manual sampling, no event
covergroup cg_manual;
coverpoint variable_name;
endgroup
// ── 2. Instantiate — create the OBJECT ────────────────────────────
cg_name cg_inst = new();
cg_manual cg_manual_inst = new();
// ── 3. Sample — record current values into the bins ───────────────
cg_manual_inst.sample(); // manual sampling
// cg_inst is sampled automatically on @(sampling_event)
// ── 4. Query — read the percentage at any point in the simulation ─
real pct = cg_manual_inst.get_inst_coverage();
$display("[cg_manual] coverage = %0.2f%%", pct);What this code does. Declares two covergroups — one auto-sampled on an event, one manually sampled — constructs each into an instance, fires a manual sample, and queries the per-instance percentage at simulation time.
Synthesisability. Simulation-only, every line. Covergroups are an instrument; synthesis tools strip them. Putting a covergroup inside synthesisable RTL is harmless (the synth tool ignores it) but it is a code-organisation smell — coverage belongs in the verification environment, not in the DUT.
A Complete Working Example — APB Transaction Coverage
The pattern below is the canonical class-based coverage shape. A transaction class owns the data being measured, embeds the covergroup that measures it, constructs the covergroup in new(), and exposes a sampling hook the monitor calls.
class apb_txn;
// ── Transaction fields ────────────────────────────────────────
rand bit [31:0] paddr;
rand bit pwrite; // 1 = write, 0 = read
rand bit [31:0] pwdata;
rand bit [ 3:0] pstrb; // byte-strobe (writes only)
// ── Covergroup definition (a TYPE — no storage yet) ───────────
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 reg_corners = {32'h0000_0000, 32'h0000_00FF,
32'h1000_0000, 32'h1FFF_FFFF};
}
cp_dir : coverpoint pwrite {
bins read = {1'b0};
bins write = {1'b1};
}
cp_strb : coverpoint pstrb {
bins full_word = {4'b1111};
bins half_word[] = {4'b0011, 4'b1100};
bins byte_only[] = {4'b0001, 4'b0010, 4'b0100, 4'b1000};
bins unaligned = default; // catch-all
}
endgroup
// ── Constructor — instantiate the covergroup ──────────────────
function new();
cg_apb = new(); // ← THE LINE THAT IS ALWAYS FORGOTTEN
endfunction
// ── Sampling hook for the monitor ─────────────────────────────
function void sample_cov();
cg_apb.sample();
endfunction
endclass
// ── Driver / monitor loop (one transaction per iteration) ─────────
apb_txn t = new();
repeat (1000) begin
assert (t.randomize());
drive_dut(t); // user-supplied
@(posedge vif.pclk iff vif.pready); // wait for commit
t.sample_cov(); // sample at the right moment
endWhat this code does. Models an APB transaction with address, direction, write-strobe, and embedded coverage. The covergroup tracks three coverpoints with explicit bin partitions appropriate to the spec (memory regions, R/W split, strobe shapes). The constructor calls new() to instantiate the covergroup. After each randomised transaction is driven and the slave commits, the monitor's loop samples coverage.
How to simulate it. Compile under any IEEE 1800-compatible simulator with coverage enabled — vcs -sverilog -cm cond+fsm+tgl+branch+cov for VCS, vsim -coverage for Questa, xrun -coverage all for Xcelium. Run a test that exercises the loop, then dump coverage with urg, imc, or the tool's UCDB reporter.
Expected output (truncated urg-style coverage report).
Group cg_apb coverage 71.4%
cp_addr csr_region 100.0% ( 348 hits )
ram_region 100.0% ( 421 hits )
reg_corners[0] 32'h0000_0000 100.0% ( 14 hits )
reg_corners[1] 32'h0000_00FF 100.0% ( 11 hits )
reg_corners[2] 32'h1000_0000 0.0% ( 0 hits ) ← gap
reg_corners[3] 32'h1FFF_FFFF 0.0% ( 0 hits ) ← gap
cp_dir read 100.0% ( 489 hits )
write 100.0% ( 511 hits )
cp_strb full_word 100.0% ( 412 hits )
half_word[0] 4'b0011 100.0% ( 61 hits )
half_word[1] 4'b1100 100.0% ( 58 hits )
byte_only[0] 4'b0001 100.0% ( 31 hits )
byte_only[1] 4'b0010 0.0% ( 0 hits ) ← gap
byte_only[2] 4'b0100 100.0% ( 29 hits )
byte_only[3] 4'b1000 100.0% ( 33 hits )
unaligned 100.0% ( 19 hits )What to read out of this. Three real gaps: two address corners (32'h1000_0000, 32'h1FFF_FFFF) the random distribution missed, and one strobe shape (4'b0010) the constraint solver biased against. The coverage report is the next directed test. Without the explicit reg_corners and byte_only[] bins, the coverpoints would have looked healthy on aggregate while leaving those three scenarios unverified — a real bug class.
The Three Coverpoint Forms
A coverpoint can take three syntactic shapes. Each is appropriate for a different intent.
Form 1 — Simple coverpoint with automatic bins
The lightest form: name a variable, let the simulator pick the bin structure. For small types (≤ 64 distinct values by default, controlled by option.auto_bin_max), each distinct value becomes its own bin. For wider types the value space is bucketed automatically. Automatic binning gives you a quick first-pass coverage model with zero up-front design work — useful early in a project, often replaced later with explicit bins matched to the spec.
covergroup cg_simple;
coverpoint state; // unnamed — label defaults to "state"
cp_opcode: coverpoint opcode; // named for cross / option use
cp_addr : coverpoint txn.addr; // expression with field access
endgroupForm 2 — Coverpoint with explicit bins
The verification-plan form: divide the value space into named buckets that match the scenarios you actually care about. This is the correct form for any coverpoint whose interesting partition is not 1-bin-per-value — addresses split by memory region, opcodes split by class, payloads split by length category.
covergroup cg_addr_regions;
cp_addr: coverpoint addr {
bins csr = {[32'h0000_0000 : 32'h0000_00FF]}; // 256 values, 1 bin
bins ram = {[32'h1000_0000 : 32'h1FFF_FFFF]}; // 256 M values, 1 bin
bins mmio = {[32'h2000_0000 : 32'h2FFF_FFFF]};
bins corners = {32'h0000_0000, 32'h0000_00FF,
32'h1000_0000, 32'h1FFF_FFFF}; // explicit corners
// Anything not matching the above falls into the auto "default" bin
}
endgroup
// Coverpoint reports 100% only after EVERY listed bin is hit at least once
// (or option.at_least times, when set).Form 3 — Coverpoint on an expression
Coverpoints accept any integral expression — not just a plain variable. This lets you track derived values (sum of two operands, top bits of an address, equality between two byte lanes) without adding a real signal to the DUT or a field to the transaction.
covergroup cg_expr;
// The arithmetic result of an ALU operation
cp_sum: coverpoint (a + b) {
bins low = {[0:15]};
bins high = {[16:31]};
}
// The top nibble of a 32-bit address (region selector)
cp_region: coverpoint addr[31:28] {
bins reg0 = {4'h0};
bins reg1 = {4'h1};
bins reg4 = {4'h4};
bins others = {[4'h2 : 4'h3], [4'h5 : 4'hF]};
}
// A boolean — are the low and high bytes of a 16-bit word identical?
cp_uniform: coverpoint (data[7:0] == data[15:8]);
endgroupCoverpoint Labels — Why They Matter
A coverpoint can be named with a leading label: label: coverpoint expr. Labels are syntactically optional and substantively mandatory in any non-trivial coverage model.
| Without a label | With a label |
|---|---|
Cannot be used in cross (no name to reference) | First-class participant in any cross |
Per-coverpoint option.* settings not addressable | option.weight, option.at_least, option.goal settable per coverpoint |
Reports display tool-generated names (coverpoint_0, coverpoint_1) | Reports display the engineer-chosen name — readable, traceable to the plan |
| Refactoring breaks silently when fields are renamed | The label is the contract; the underlying expression can change without touching the model |
covergroup cg_alu;
cp_opcode: coverpoint opcode {
option.weight = 5; // critical coverage — high weight
option.at_least = 20; // 20 hits per bin before "covered"
bins add = {3'h0};
bins sub = {3'h1};
bins mul = {3'h2};
bins div = {3'h3};
}
cp_mode: coverpoint mode {
bins normal = {2'b00};
bins saturate = {2'b01};
bins overflow = {2'b10};
}
// Cross requires both operands to be LABELLED coverpoints.
cr_op_mode: cross cp_opcode, cp_mode;
endgroupThe discipline rule that follows: label every coverpoint you ship. The cost is zero (one word of typing); the benefit is every downstream activity (cross coverage, option tuning, report reading, refactoring) becomes legible.
Where Covergroups Live — The Four Legal Scopes
Unlike constraint, which is locked to classes, covergroups can be defined in four different scopes. Each scope matches a different layer of the verification environment.
| Scope | Typical use | Sampling trigger | Example |
|---|---|---|---|
| Class | Transaction-level, scoreboard, monitor coverage | Manual sample() from post_randomize() or a monitor hook | apb_txn::cg_apb above |
| Module | Protocol-level coverage inside a TB module | Clock edge, named event, or manual | cg_apb_bus @(posedge pclk iff pready) |
| Interface | Signal-level coverage close to the DUT pins | Clock edge inside the interface | cg_handshake @(posedge clk) inside an APB interface |
| Program | Test-level scenario coverage from a program block | Manual sample() driven by the test | Rare in modern UVM, common in pre-UVM SV |
The scope choice is not just a syntactic detail — it determines who owns the coverage. Class-scope is owned by the transaction (the spec witness travels with the data). Interface-scope is owned by the DUT-TB boundary (the signal-level promise the DUT must keep). Module and program scopes are owned by the test infrastructure. A mature verification environment has covergroups in all four scopes, each measuring a different layer.
Built-in Methods — The Covergroup API
Every covergroup instance ships with a small built-in API. Most of it you never call directly — the tooling reads the data. But knowing what is available makes the model controllable.
| Method | Returns | Use |
|---|---|---|
sample() | void | Force a coverage sample now — records values of every coverpoint into bins |
get_inst_coverage() | real (0.0 – 100.0) | Per-instance percentage for this specific object |
get_coverage() | real (0.0 – 100.0) | Type-level percentage merged across every instance of this covergroup type |
start() | void | (Re-)enable sampling — useful after stop() |
stop() | void | Pause sampling without losing accumulated hits — useful during reset or init |
set_inst_name(string) | void | Rename this instance in reports — useful when several instances coexist |
apb_txn t = new();
real inst_pct;
real type_pct;
repeat (500) begin
assert (t.randomize());
t.sample_cov();
end
inst_pct = t.cg_apb.get_inst_coverage();
type_pct = apb_txn::cg_apb::get_coverage(); // class-scope :: type-scope
$display("[apb_txn] this instance: %0.2f%%", inst_pct);
$display("[apb_txn] all instances: %0.2f%%", type_pct);
// Pause coverage during a known-uninteresting phase
t.cg_apb.stop();
run_reset_sequence();
t.cg_apb.start();Common Pitfalls — The Coverage-Killers
Most covergroup bugs are not in the model — they are in the plumbing around the model. The covergroup is correct; the sample never lands.
class my_txn;
covergroup cg;
coverpoint data;
endgroup
// ── BUG: no constructor → cg is null at runtime ──
endclass
// At simulation time:
// my_txn t = new();
// t.cg.sample(); // null-handle dereference (VCS/Questa)
// // or silent no-op — coverage stays at 0%covergroup cg;
coverpoint a; // ← unnamed — has no addressable label
coverpoint b;
cross a, b; // ELABORATION ERROR — no coverpoint named "a"
endgroupcovergroup cg_late @(posedge clk); // event in the type
coverpoint data;
endgroup
initial begin
@(posedge clk); // a clock edge has already happened
cg_late cg = new(); // ← constructed AFTER first edge
// Coverage will start landing on the NEXT @(posedge clk), not this one.
endclass dual_port;
covergroup cg;
coverpoint data;
endgroup
cg port_a_cov, port_b_cov;
function new();
port_a_cov = new();
port_b_cov = new();
endfunction
endclass
// Reports show ONE merged number for both ports — fine if that's the intent.
// If you wanted per-port breakdown, you need option.per_instance = 1 on the
// covergroup; otherwise the per-instance view is unavailable.The underlying shape is consistent: the covergroup compiled, but the sample never landed in the bin you expected. The fix is always at the construction or sampling site — never in the bin definitions themselves.
Debug Lab — "Coverage Compiled, the Report Says 0%"
The single most common new-engineer experience with coverage. The covergroup is syntactically correct, the simulation runs, the report comes back at 0%. Three independent failure modes converge on the same symptom.
class fifo_txn;
rand bit [7:0] data;
rand bit wr;
covergroup cg @(posedge clk); // ── BUG A: event-form covergroup
cp_data: coverpoint data {
bins all_vals[] = {[0:255]};
}
cp_wr : coverpoint wr;
endgroup
// ── BUG B: no constructor for cg ──
endclass
class env;
fifo_txn t;
task run();
t = new(); // class is constructed
repeat (1000) begin
assert (t.randomize()); // BUG C: clk is never toggled
// no manual sample, and clk never advances in this thread
end
endtask
endclassThe class compiles cleanly. The test runs. randomize() succeeds 1000 times. The regression script reports PASS. The coverage tool reports cg 0.0% (0/258 bins hit). Some simulators emit a warning at high verbosity ("covergroup not constructed") — many do not. The verification lead is told "coverage is at 0% across the board" and the team spends two days hunting a bug in the bin definitions that is not there.
Three compounding mistakes:
- Bug A ties the covergroup to
@(posedge clk), but the class is constructed in a context whereclkis never toggled. The sampling event never fires. - Bug B never constructs the covergroup (
cg = new()is missing from the constructor), so even if the event fired,cgis a null handle. - Bug C is conceptual: the test layer has no path that drives a clock or calls
sample()explicitly. Bug A only matters in the presence of a real clock; Bug B only matters in the presence of a real sample; in their absence, every coverage model in this environment will look identical.
Any one of these alone would zero the report. Together, they make the cause harder to find because each bug masks the others.
Make the sampling rule explicit, construct the covergroup, and pick the form that matches the environment. For a transaction class with no clock, manual sampling is the correct choice:
class fifo_txn;
rand bit [7:0] data;
rand bit wr;
covergroup cg; // ── Fix A: drop the event
cp_data: coverpoint data {
bins all_vals[] = {[0:255]};
}
cp_wr : coverpoint wr;
endgroup
function new();
cg = new(); // ── Fix B: construct the cg
endfunction
endclass
class env;
fifo_txn t;
task run();
t = new();
repeat (1000) begin
assert (t.randomize());
t.cg.sample(); // ── Fix C: sample explicitly
end
endtask
endclassThe discipline rule that prevents recurrence: every covergroup declaration is paired with its construction line in the same commit, and every covergroup has a documented sampling moment. Code review rejects the covergroup that has neither, the way it rejects a class … endclass with no new().
Industry Context — Where This Pattern Ships
The class-embeds-covergroup pattern shown above is the structural backbone of every modern UVM-based environment.
- AMBA AXI / AHB / APB VIP ships transaction classes (
uvm_sequence_itemderivatives) with embedded covergroups for address ranges, transfer sizes, burst types, response codes, and the cross of every interesting pair. ARM's official AMBA VIPs publish the coverage plan alongside the agent — the covergroup is the spec contract. - Ethernet MAC / PCS / PMA verification uses covergroups on frame-size buckets (64B, 65–127B, …, jumbo), VLAN tag presence, error injections, and the cross of frame-size × error-type — directly traceable to IEEE 802.3 conformance test categories.
- Memory controller verification (DDR3/4/5, LPDDR, HBM) uses covergroups on rank/bank, read/write/refresh interleaving, training step transitions, and back-to-back command pairs.
- UVC libraries (Cadence VIPC, Synopsys VC VIP, Mentor Questa VIP) all expose covergroups in their transaction classes as the primary surface for the integrator to extend with project-specific bins.
If you can read and modify a covergroup-on-a-transaction-class fluently, you can read and modify the coverage layer of any commercial VIP — the pattern is invariant across vendors.
Interview Q&A — Ten Questions You Will Be Asked
A covergroup is the container — a user-defined type that encapsulates one area of coverage. A coverpoint is an individual tracked variable or expression inside a covergroup, partitioned into bins. One covergroup, many coverpoints; one coverpoint, many bins. The covergroup carries the sampling rule and the built-in methods; the coverpoint carries the measurement and the bin partition.
Coding Guidelines — Seven Rules for Covergroup Hygiene
- Every covergroup is constructed in
new(), in the same commit as its declaration. Code review rejects either alone. The construction line is not a stylistic detail — it is the difference between a working coverage model and a 0% report. - Label every coverpoint. The cost is one word; the payoff is cross coverage, per-coverpoint options, and readable reports. Unlabelled coverpoints survive in tutorials and disappear from production code.
- Use explicit bins for any coverpoint that ships. Auto bins are a prototyping aid; the verification plan deserves explicit, named bins that map to its rows. The bin name is the plan-row scoreboard.
- Match the sampling moment to the spec moment. Sampling before the transaction commits, before the slave responds, or before the fields are valid produces "high coverage" that is measurement noise. The sample must align with the moment the spec says the scenario happened.
- Pick one form of sampling per environment and stay consistent. Event-form covergroups belong at the bus/interface layer; manual
sample()belongs at the transaction layer. Mixing them inside one model is rare and usually a sign the design has not been thought through. - Use
option.per_instance = 1whenever multiple instances need separate visibility. Without it, the tool merges instance numbers and the per-instance view disappears from the report — a debugging trap when the per-port (or per-channel) breakdown is exactly what you needed. - Treat the covergroup as documentation. Comments above each coverpoint should name the verification-plan row it implements. The covergroup is the executable spec contract; the comments are the spec citation. A reviewer should be able to read the covergroup and reconstruct the relevant plan section.
Summary
The covergroup is a class-like type that owns the coverage model; the coverpoint is its measurable field. Define the covergroup, instantiate it in the constructor, choose the sampling form (event or manual) that matches the layer, label every coverpoint, prefer explicit bins over the auto-bin default, and verify the sample is firing on the values you expect. Everything else in Module 11 — automatic bins, named bins, illegal and ignore bins, transitions, crosses, options, sampling events, instance-vs-type tracking — is a refinement of these primitives.