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.

diagram
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)
  endgroup

Note 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.

systemverilog
// --- 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
endmodule

The 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?”

systemverilog
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
endclass

Naming 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.