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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
    • Covergroups & Coverpoints
      • Covergroup Anatomy
      • Sampling: Clocked, Event & sample()
      • Coverpoint Expressions & Types
      • Your First Coverage Model: Three Bins and a Report
      • An ALU Coverage Plan as a Covergroup
      • Sign-Off Needs Both: Code and Functional Coverage
      • The Bins Model: Counting Covergroups on Open Tools
    • Bins: Explicit, Auto, Illegal & Ignore
    • Cross Coverage
    • Coverage Options & Methods
    • Coverage Closure & Sign-Off
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←Covergroups & Coverpoints
  1. Topics
  2. SystemVerilog
  3. Functional Coverage in SystemVerilog
  4. Covergroups & Coverpoints
  5. An ALU Coverage Plan as a Covergroup
On this page

    Part 5 of 9, lesson 5 of 25Part 5 of 9/Functional Coverage/Lesson 5 of 25Intermediate

    An ALU Coverage Plan as a Covergroup

    Turn a six-row ALU plan into a covergroup, carry it in a wrapper class, and read the holes random stimulus leaves.

    A plan with six rows became a covergroup with six coverpoints and a cross, and 2,000 random transactions still missed one cell: OR gives zero only when both operands are zero.

    After this lesson you can: turn a numbered plan into a covergroup that mirrors it, carry it in a wrapper class, and close its holes with directed cases.

    From spec to plan

    A coverage model starts in the spec, not in code. Scenarios move from the spec into a numbered plan, and only then into covergroups whose bins map one-to-one onto plan items. The worked example is a small ALU.

    Item

    Scenario

    CP-1

    Every opcode executed at least once

    CP-2

    Zero flag seen asserted and deasserted

    CP-3

    Carry seen asserted for ADD and for SUB

    CP-4

    Operands a and b each seen at 0 and at 255

    CP-5

    Every opcode executed with a zero result

    CP-6

    Shift by 0 and shift by 7 for SHL

    Each plan item is a sentence a non-programmer can check off. The covergroup makes every item mechanically measurable by name.

    A covergroup that mirrors the plan

    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, listed on the Bins Model page of this topic.

    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
    41
    42
    43
    44
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    typedef enum logic [2:0] { ADD, SUB, AND_, OR_, XOR_, SHL } op_e;
    
    class alu_coverage;
      covergroup cg with function sample(op_e op, bit [7:0] a, b, y,
                                         bit zero, bit carry);
        option.per_instance = 1;
        option.name = "alu_cov";
    
        // CP-1: enum gives one named bin per opcode, automatically
        cp_op : coverpoint op;
    
        // CP-2
        cp_zero : coverpoint zero {
          bins asserted   = {1};
          bins deasserted = {0};
        }
    
        // CP-3: carry is meaningful only for ADD and SUB
        cp_carry : coverpoint carry iff (op inside {ADD, SUB}) {
          bins carry_set   = {1};
          bins carry_clear = {0};
        }
    
        // CP-4: operand extremes
        cp_a_ext : coverpoint a { bins min_v = {0}; bins max_v = {255}; }
        cp_b_ext : coverpoint b { bins min_v = {0}; bins max_v = {255}; }
    
        // CP-5: every opcode with a zero result
        x_op_zero : cross cp_op, cp_zero {
          ignore_bins nz = binsof(cp_zero.deasserted);
        }
    
        // CP-6: shift amount extremes (b[2:0] is the shift count)
        cp_shamt : coverpoint b[2:0] iff (op == SHL) {
          bins sh0 = {0};
          bins sh7 = {7};
        }
      endgroup
    
      function new();
        cg = new();
      endfunction
    endclass

    One coverpoint or cross per plan item, with bins named after the item. The iff guards keep the model measuring only meaningful combinations.

    The filtered cross holds 6 bins: 6 opcodes times the asserted half of cp_zero. Only lower-case and, or and xor are keywords, so AND would also be legal; the underscore in AND_ is just a readability choice.

    The wrapper that carries it

    Before IEEE 1800-2023 a covergroup could not be extended; 2023 adds covergroup extends, but tool support varies. A covergroup also travels badly on its own. The standard reuse unit is a wrapper class: it owns construction, names each instance, and exposes a stable sampling API.

    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, listed on the Bins Model page of this topic.

    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
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    class alu_cov_collector;
      covergroup cg (string inst_name) with function sample(op_e op, bit zero);
        option.per_instance = 1;
        option.name = inst_name;      // one report row per instance
        cp_op : coverpoint op;        // 6 named bins, one per opcode
        cp_zero : coverpoint zero {
          bins asserted   = {1};
          bins deasserted = {0};
        }
      endgroup
    
      function new(string inst_name);
        cg = new(inst_name);          // covergroup new() takes arguments
      endfunction
    
      function void sample_txn(op_e op, bit zero);
        cg.sample(op, zero);
      endfunction
    
      function real get_pct();
        return cg.get_inst_coverage();
      endfunction
    endclass
    
    // Merged reporting hides weak instances; per-instance reporting does not:
    // cpu_cov = new("cpu_port");   -> report row "cpu_port"
    // dma_cov = new("dma_port");   -> report row "dma_port"

    Each collector object owns one covergroup instance with its own report row. One strong port can no longer mask a weak one.

    • The wrapper gives construction control, reuse across testbenches, and a stable sample_txn API.

    • Per-instance reporting plus distinct names keeps multi-port coverage honest.

    • A covergroup type may also be declared at package scope. The sampling context is what limits reuse, which is why the wrapper class still wins.

    • Argument plumbing, value versus ref formals, and instance merging are detailed in the Options topic.

    Related topics

    • Per-instance options

    • Covergroup arguments

    Count the plan and read the holes

    The same plan counted with the bins model, from 2000 random transactions and then a directed top-up:

    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
    // uses: covmodel
    // The ALU plan counted with the bins model: random stimulus first, then the directed top-up.
    typedef enum int {ADD, SUB, AND_, OR_, XOR_, SHL} op_e;
    
    class alu_coverage;
      cm_group cg = new("alu_cov");
      cm_point cp_op = new("cp_op"), cp_zero = new("cp_zero"), cp_carry = new("cp_carry");
      cm_point cp_a_ext = new("cp_a_ext"), cp_b_ext = new("cp_b_ext"), cp_shamt = new("cp_shamt");
      cm_cross x_op_zero;
      function new();
        cp_op.add("add", ADD, ADD); cp_op.add("sub", SUB, SUB); cp_op.add("and_", AND_, AND_);
        cp_op.add("or_", OR_, OR_); cp_op.add("xor_", XOR_, XOR_); cp_op.add("shl", SHL, SHL);   // CP-1
        cp_zero.add("deasserted", 0, 0); cp_zero.add("asserted", 1, 1);                          // CP-2
        cp_carry.add("carry_clear", 0, 0); cp_carry.add("carry_set", 1, 1);                      // CP-3
        cp_a_ext.add("min_v", 0, 0); cp_a_ext.add("max_v", 255, 255);                            // CP-4
        cp_b_ext.add("min_v", 0, 0); cp_b_ext.add("max_v", 255, 255);
        cp_shamt.add("sh0", 0, 0); cp_shamt.add("sh7", 7, 7);                                    // CP-6
        x_op_zero = new("x_op_zero", cp_op, cp_zero);                                            // CP-5
        x_op_zero.exclude("", "deasserted");                                                     // ignore_bins nz
        cg.points = '{cp_op, cp_zero, cp_carry, cp_a_ext, cp_b_ext, cp_shamt};
        cg.crosses.push_back(x_op_zero);
      endfunction
      function void sample(op_e op, bit [7:0] a, b, bit zero, carry);
        cp_op.sample(op); cp_zero.sample(zero); x_op_zero.sample();
        if (op inside {ADD, SUB}) cp_carry.sample(carry);                                        // iff (op inside {ADD, SUB})
        cp_a_ext.sample(a); cp_b_ext.sample(b);
        if (op == SHL) cp_shamt.sample(b[2:0]);                                                  // iff (op == SHL)
      endfunction
    endclass
    
    Terminal
    after 2000 random transactions:
    covergroup alu_cov: 97.62% (goal 100)
      coverpoint cp_op: 100.00% (6 of 6 bins)
        add            339
        sub            316
        and_           371
        or_            343
        xor_           315
        shl            316
      coverpoint cp_zero: 100.00% (2 of 2 bins)
    ... 6 more lines ...
        min_v          5
        max_v          7
      coverpoint cp_b_ext: 100.00% (2 of 2 bins)
        min_v          8
        max_v          6
      coverpoint cp_shamt: 100.00% (2 of 2 bins)
        sh0            39
        sh7            47
      cross x_op_zero: 83.33% (5 of 6 bins)
        hole: <or_, asserted>
    after the directed top-up: 100.00%
    PASS: random stimulus left named holes, and six directed cases closed the model at 100%

    The report reads like the plan: every coverpoint is full, and the one hole is a named cross bin, <or_, asserted>. OR gives zero only when both operands are zero, 1 in 65,536 for uniform operands, so 2000 random transactions almost never produce it. The group sits at 97.62%, the mean of its seven members. Six directed cases close it.

    1. Extract scenarios from the spec into a numbered, reviewable plan.

    2. Implement one coverpoint or cross per plan item, with bins named after the item.

    3. Run random stimulus, then query get_inst_coverage() and dump the database.

    4. Read holes as missing scenarios, not missing percentage.

    5. Close holes with constraint tweaks or small directed cases, then rerun.

    Check yourself Why does the model keep its covergroup inside a wrapper class instead of declaring it next to the signals?

    A wrapper owns construction, names each instance and exposes one sample_txn call, so any testbench can reuse it and each port gets its own report row.

    • Plan rows first, then one bin per row.

    • Random stimulus leaves named holes; directed cases close them.

    • A wrapper class gives each instance its own report row.

    Rule of thumb: name every bin after the plan row it proves.

    How this was checked

    Counted with the course's bins model (rich/coverage/cov_model.sv, listed on The Bins Model page) 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.

    Random stimulus left named holes, and six directed cases closed the model at 100%.

    Related topics

    • Next: why sign-off needs code and functional coverage

    Previous

    148 / 275

    Next: Sign-Off Needs Both: Code and Functional Coverage

    Up next

    Up nextSign-Off Needs Both: Code and Functional Coverage

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Covergroup Anatomy
    • Sampling: Clocked, Event & sample()
    • Coverpoint Expressions & Types