Part 5 · Functional Coverage · Intermediate
Covergroup Anatomy
The covergroup/coverpoint/bins structure, declaring in classes vs modules, the mandatory new() call, and coverpoint naming.
Three layers: covergroup, coverpoint, bins
A covergroup has exactly three structural layers. The covergroup is the container — it owns the sampling trigger and options. Each coverpoint inside it watches one variable or expression. Each coverpoint contains bins — named buckets that sampled values fall into. Coverage percentage is computed bottom-up: a coverpoint is covered when its bins are hit, a covergroup is covered when its coverpoints are.
COVERGROUP ANATOMY (labeled)
covergroup cg_alu @(posedge clk); ◄── container + sampling event
│
├── option.per_instance = 1; ◄── options (configuration)
│
├── cp_op : coverpoint opcode { ◄── coverpoint (one watched value)
│ │
│ ├── bins add = {OP_ADD}; ◄── bin: named bucket
│ ├── bins sub = {OP_SUB};
│ └── bins shifts[] = {OP_SHL, OP_SHR}; ◄── bin array
│ }
│
├── cp_zero : coverpoint (result == 0); ◄── expression coverpoint
│
└── x_op_zero : cross cp_op, cp_zero; ◄── cross (combinations)
endgroupNote the label syntax cp_op : coverpoint opcode. The label becomes the coverpoint's name in every coverage report and in cross references. An unlabeled coverpoint gets an auto-generated name derived from the expression — ugly in reports and brittle if the expression changes. Always label.
Declaring in classes vs modules
A covergroup can be declared in a module, interface, program, or class. The two common homes are a class (testbench coverage collector) and a module or interface (white-box coverage near the signals). The declaration syntax differs in one important way: a class-embedded covergroup is implicitly an instance member, while a module-level covergroup is a type you instantiate explicitly.
// --- In a class: declare AND construct in new() ---
class alu_coverage;
logic [3:0] opcode;
logic zero;
covergroup cg_alu; // embedded covergroup
cp_op : coverpoint opcode;
cp_zero : coverpoint zero;
endgroup
function new();
cg_alu = new(); // MANDATORY — see below
endfunction
function void collect(logic [3:0] op, logic z);
opcode = op;
zero = z;
cg_alu.sample();
endfunction
endclass
// --- In a module: type + explicit instance ---
module alu_cov_mod (input logic clk, input logic [3:0] opcode);
covergroup cg @(posedge clk); // type with clocked sampling
cp_op : coverpoint opcode;
endgroup
cg cg_inst = new(); // explicit instantiation
endmoduleThe forgotten-new() classic: silent zero coverage
Declaring a covergroup creates a handle, not an instance — exactly like a class. If you never call new(), the covergroup does not exist , nothing samples, and most simulators report it as 0% coverage or omit it entirely — with no error and no warning . Tests pass, regressions are green, and weeks later someone notices the coverage report has a hole. This is the single most common covergroup bug and a favorite interview question: “your covergroup shows zero hits but the stimulus is correct — what do you check first?”
class broken_coverage;
logic [3:0] opcode;
covergroup cg;
cp_op : coverpoint opcode;
endgroup
function new();
// BUG: cg = new(); is missing.
// Compiles clean. Runs clean. cg.sample() on a null
// covergroup is silently ignored by many tools (or
// fatals only with extra checking enabled).
endfunction
endclass
class fixed_coverage;
logic [3:0] opcode;
covergroup cg;
option.name = "alu_op_cov"; // name it for the report
cp_op : coverpoint opcode;
endgroup
function new();
cg = new(); // the one-line fix
endfunction
endclassNaming for readable reports
Label every coverpoint (cp_op : coverpoint opcode) — labels are the names in reports and cross expressions.
Set option.name on the covergroup when multiple instances exist, so report rows are distinguishable.
Use a consistent prefix scheme: cp_ for coverpoints, x_ for crosses — reviewers scan reports faster.
Name bins after plan scenarios (bins burst_max = {16};), not after values (bins b16).
Key takeaways
Covergroup → coverpoint → bins: container, watched value, counted bucket — coverage rolls up bottom-up.
Class-embedded covergroups must be constructed with cg = new() in the class constructor.
A forgotten new() produces silent zero coverage — no compile error, no runtime error.
Labels and option.name are what you see in reports; unlabeled coverpoints make reports unreadable.
Common pitfalls
Declaring a covergroup in a class and never calling new() — the classic silent zero-coverage bug.
Leaving coverpoints unlabeled — auto-generated names break crosses and confuse reports.
Constructing a module-level covergroup inside an initial block conditionally — instance may never exist in some runs.
Assuming the simulator errors on sampling a never-constructed covergroup — most tools stay silent by default.