Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
    • Covergroups & Coverpoints
      • Covergroup Anatomy
      • Sampling: Clocked, Event & sample()
      • Coverpoint Expressions & Types
      • Building Your First Coverage Model
    • Bins: Explicit, Auto, Illegal & Ignore
    • Cross Coverage
    • Coverage Options & Methods
    • Coverage Closure & Sign-Off
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Covergroups & Coverpoints
  1. Topics
  2. SystemVerilog
  3. Functional Coverage in SystemVerilog
  4. Covergroups & Coverpoints
  5. Covergroup Anatomy

Part 5 · Functional Coverage · Intermediate

Covergroup Anatomy

Covergroup, coverpoint and bins; class and module homes; the forgotten new(); naming for reports.

On this page

    Why this matters

    A team reported a covergroup at 0% for two weeks and blamed the stimulus. The covergroup had been declared in the monitor and never built with new(), so it measured nothing and raised no error.

    After this lesson you can: read a covergroup as three counting layers, put it in a class or a module, and check that it was constructed.

    Three counting layers

    A covergroup has three counting layers. The covergroup is the container and owns the sampling trigger. Each coverpoint inside it watches one value or expression. Each coverpoint holds bins, the named buckets that samples fall into.

    Options, crosses and guards shape the count but add no new layer. Coverage rolls up bottom-up: hit bins cover a point, and covered points cover the group.

    Layer

    Role

    Coverage counts

    Covergroup

    Container with trigger and options

    Covered when its points are covered

    Coverpoint

    One watched value or expression

    Covered when its bins are hit

    Bins

    Named buckets for sampled values

    Hit counts collected at each sample

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // snippet
    covergroup cg_alu @(posedge clk);          // the container, with its sampling event
      option.per_instance = 1;                 // options configure it
      cp_op : coverpoint opcode {              // a coverpoint watches one value
        bins add = {OP_ADD};                   // a bin is a named bucket
        bins sub = {OP_SUB};
        bins shifts[] = {OP_SHL, OP_SHR};      // a bin array: one bin per value
      }
      cp_zero : coverpoint (result == 0);      // an expression coverpoint
      x_op_zero : cross cp_op, cp_zero;        // a cross counts combinations
    endgroup

    The label in cp_op : coverpoint opcode becomes the coverpoint name in reports and cross references. An unlabeled coverpoint gets an auto-generated name from its expression, which turns brittle when the expression changes. Label every coverpoint.

    The expression point cp_zero declares no bins. It still counts: a 1-bit expression gets the two automatic bins for 0 and 1.

    A class home or a module home

    A covergroup can be declared in a module, interface, program, package or class. The two working homes are a class collector and a module next to the signals.

    The next listing is not run in this course's checks: the open simulators do not count covergroups. The bench at the end of the lesson counts the same bins with the course's bins model.

    alu_cov_mod.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    // --- 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 class form declares the type and constructs it in new(). The module form declares a clocked type and instantiates it once beside the signals it watches.

    The forgotten new()

    Declaring a covergroup creates a handle, not an instance. Without new(), the covergroup does not exist and nothing samples.

    The next listing is not run in this course's checks: the open simulators do not count covergroups. The bench at the end of the lesson counts the same bins with the course's bins model.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    class broken_coverage;
      logic [3:0] opcode;
    
      covergroup cg;
        cp_op : coverpoint opcode;
      endgroup
    
      function new();
        // BUG: cg = new(); is missing.
        // Compiles clean. Nothing here calls cg.sample(), so the
        // handle stays null and the group silently measures nothing.
        // An explicit cg.sample() on the null handle would usually
        // stop the run with a null-object error instead.
      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
    • Label every coverpoint: labels are the names in reports and cross expressions.

    • Give each instance its own option.name. A constant name is shared by every instance, so pass it in as a covergroup argument with option.per_instance = 1, as the first-model lesson does.

    • Prefix consistently: cp_ for coverpoints, x_ for crosses.

    • Name bins after plan scenarios (burst_max), not after raw values (b16).

    Common pitfall

    A declared but never constructed covergroup measures nothing. Tests still pass and the report shows a hole with no error attached. Check construction first whenever coverage reads zero.

    Interview

    “Coverage is zero but the stimulus is correct. What do you check first?”

    Answer in order: the new() call, then the sampling trigger, then whether a reset guard ate every sample.

    Check yourself Why does an unlabeled coverpoint hurt?

    Reports show an auto-generated name derived from the expression, and cross references turn brittle. A label keeps both stable when the expression changes.

    Check yourself A class covergroup shows zero hits after a passing regression. What is the most likely cause?

    The constructor never ran cg = new(). The handle stayed null and no instance ever sampled. When nothing calls sample() on it, the flow stays quiet; an explicit cg.sample() on the null handle usually fails at runtime instead.

    Testbench

    The bench builds cg_alu from the diagram with the bins model, twice: once in a collector whose constructor forgot new(), once in a fixed one. Both receive the same six transactions.

    Predict first Six transactions reach both collectors. What does each report?

    The broken one reports nothing at all: its handle is null, so no sample ever lands. The fixed one shows every opcode bin hit and the holes left in the cross.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    // uses: covmodel
    // cg_alu from the anatomy diagram, counted with the bins model, and the forgotten new().
    typedef enum int {OP_ADD, OP_SUB, OP_SHL, OP_SHR} opcode_e;
    
    class alu_coverage;
      cm_group cg;                                   // the covergroup handle
      cm_point cp_op, cp_zero;
      cm_cross x_op_zero;
      function new(bit construct);
        if (!construct) return;                      // the bug: no cg = new()
        cg = new("cg_alu");
        cp_op = new("cp_op");
        cp_op.add("add", OP_ADD, OP_ADD);
        cp_op.add("sub", OP_SUB, OP_SUB);
        cp_op.add_each("shifts", OP_SHL, OP_SHR);    // bins shifts[] = {OP_SHL, OP_SHR};
        cp_zero = new("cp_zero");                    // coverpoint (result == 0): auto bins 0 and 1
        cp_zero.add("auto[0]", 0, 0); cp_zero.add("auto[1]", 1, 1);
        x_op_zero = new("x_op_zero", cp_op, cp_zero);
        cg.points.push_back(cp_op); cg.points.push_back(cp_zero); cg.crosses.push_back(x_op_zero);
      endfunction
      function void collect(opcode_e op, bit zero);
        if (cg == null) return;                      // nothing was built, so nothing counts
        cp_op.sample(op); cp_zero.sample(zero); x_op_zero.sample();
      endfunction
    endclass
    
    module tb_cov_anatomy;
      alu_coverage broken = new(0);
      alu_coverage fixed  = new(1);
      initial begin
        opcode_e ops[6] = '{OP_ADD, OP_SUB, OP_SHL, OP_SHR, OP_ADD, OP_SUB};
        bit      zs[6]  = '{0, 1, 0, 0, 1, 0};
        foreach (ops[i]) begin broken.collect(ops[i], zs[i]); fixed.collect(ops[i], zs[i]); end
        if (broken.cg == null) $display("broken collector: cg is null, so it measured nothing");
        fixed.cg.report();
        if (broken.cg != null || fixed.cp_op.get_coverage() != 100.0 || fixed.x_op_zero.n_covered() != 6) $fatal(1, "FAIL: anatomy counts");
        $display("PASS: the built covergroup counted 4 opcode bins, 2 zero bins and 6 of 8 crosses; the unbuilt one counted nothing");
        $finish;
      end
    endmodule
    Terminal
    broken collector: cg is null, so it measured nothing
    covergroup cg_alu: 91.67% (goal 100)
      coverpoint cp_op: 100.00% (4 of 4 bins)
        add            2
        sub            2
        shifts[2]      1
        shifts[3]      1
      coverpoint cp_zero: 100.00% (2 of 2 bins)
        auto[0]        4
        auto[1]        2
      cross x_op_zero: 75.00% (6 of 8 bins)
        hole: <shifts[2], auto[1]>
        hole: <shifts[3], auto[1]>
    PASS: the built covergroup counted 4 opcode bins, 2 zero bins and 6 of 8 crosses; the unbuilt one counted nothing

    Remember

    • Covergroup, coverpoint, bin: the percentage rolls up from bins.

    • A covergroup in a class must be built with new() in the constructor.

    • An unbuilt covergroup reports a hole, not an error.

    Rule of thumb: check construction first when a covergroup reads 0%.

    How this was checked

    Counted with the course's bins model (rich/coverage/cov_model.sv, listed on the Coverage hub) on Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator 5.048 parses covergroups with a COVERIGN warning and ignores their bins, and Icarus 13 has none, so each covergroup listing is marked not run and its bench counts the same bins with the model.

    The bench samples the same traffic into a built and an unbuilt group: the built one counted 4 opcode bins, 2 zero bins and 6 of 8 cross cells; the unbuilt one counted nothing.

    Related topics

    • Per-instance options

    Previous

    141 / 259

    Next: Sampling: Clocked, Event & sample()

    Up next

    Up nextSampling: Clocked, Event & sample()

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Coverpoint Expressions & Types
    • Building Your First Coverage Model