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. Your First Coverage Model: Three Bins and a Report
On this page

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

    Your First Coverage Model: Three Bins and a Report

    The smallest useful covergroup: three bins, five samples and a report that names the hole, then the first PacketFIFO coverage step.

    A team watched a coverage number sit at 67% for a week and kept adding traffic. One bin, a shift, had never been hit, and the report had named it from the first run.

    After this lesson you can: write a covergroup with three bins, sample it, read the report line by line, and close the hole it names.

    The smallest covergroup

    One coverpoint with three bins is the whole idea at its smallest. The coverpoint watches the ALU opcode. Each bin is a plan row: this opcode must happen at least once.

    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
    // not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bins model counts it
    covergroup cg_alu with function sample(bit [1:0] op);
      cp_op : coverpoint op {
        bins add = {0};          // plan row 1: an ADD happened
        bins sub = {1};          // plan row 2: a SUB happened
        bins shl = {2};          // plan row 3: a shift happened
      }
    endgroup

    The bins model counts the same three bins with plain classes: cp_op.add("add", 0, 0) is bins add = {0};. Its source and its mapping to covergroup syntax are on The Bins Model page. Every bench in this part starts with // uses: covmodel, so paste it below that listing.

    Sample it and read the report

    The bench samples five transactions, none of them a shift, and prints the report. It then sends one directed shift and prints it again.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // uses: covmodel
    // The smallest covergroup, counted with the bins model: three bins on one coverpoint.
    //   covergroup cg;  cp_op : coverpoint op { bins add = {0}; bins sub = {1}; bins shl = {2}; }  endgroup
    module tb_three_bins;
      cm_point cp_op = new("cp_op");
      initial begin
        int ops[5] = '{0, 1, 0, 0, 1};     // five ALU transactions: no shift among them
        cp_op.add("add", 0, 0); cp_op.add("sub", 1, 1); cp_op.add("shl", 2, 2);
        foreach (ops[i]) cp_op.sample(ops[i]);
        $display("after five transactions:");
        cp_op.report();
        if (cp_op.get_coverage() > 67.0 || cp_op.n_covered() != 2) $fatal(1, "FAIL: expected the shl bin to be the hole");
        cp_op.sample(2);                   // one directed shift
        $display("after one shift:");
        cp_op.report();
        if (cp_op.get_coverage() != 100.0) $fatal(1, "FAIL: the shift did not close the hole");
        $display("PASS: three bins counted, the unhit shl bin was the named hole and one SHL closed it");
        $finish;
      end
    endmodule
    Terminal
    after five transactions:
      coverpoint cp_op: 66.67% (2 of 3 bins)  below goal 100
        add            3
        sub            2
        shl            0     <- hole
    after one shift:
      coverpoint cp_op: 100.00% (3 of 3 bins)
        add            3
        sub            2
        shl            1
    PASS: three bins counted, the unhit shl bin was the named hole and one SHL closed it
    Predict first Before the run: after five transactions with no shift, what does the report say?

    Two of three bins hit, 66.67%, and the shl bin marked as a hole with a count of 0.

    Terminal
    after five transactions:
      coverpoint cp_op: 66.67% (2 of 3 bins)  below goal 100
        add            3
        sub            2
        shl            0     <- hole
    after one shift:
      coverpoint cp_op: 100.00% (3 of 3 bins)
        add            3
        sub            2
        shl            1
    PASS: three bins counted, the unhit shl bin was the named hole and one SHL closed it

    Read it line by line. The first line is the coverage: bins hit divided by bins in the goal. Each line below it is a bin with its hit count, and the arrow marks a bin that has not reached at_least, which is 1 unless you change it. The report names the missing scenario, and the fix is a test, not more of the same traffic: five more adds would leave the number where it is.

    Plan first, then bins

    A real model starts from a short plan, one sentence per scenario, and gives every sentence a bin named after it. The next lesson does that for a six-row ALU plan, and the one after it explains why a model that reads 100% can still sit beside a verification gap.

    PacketFIFO step 1: the kind field. The header's top two bits are the packet kind: 0 DATA, 1 CTRL, 2 ERR. Give the project a cp_kind coverpoint with one bin per kind, feed it the header of every packet that crosses the FIFO, and read which kind has never been sent.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // uses: covmodel
    // PacketFIFO step 1: the kind field of the header, counted with the bins model.
    //   covergroup cg_kind;  cp_kind : coverpoint hdr[7:6] { bins data = {0}; bins ctrl = {1}; bins err = {2}; }  endgroup
    module tb_pkt_kind_cov;
      cm_point cp_kind = new("cp_kind");
      initial begin
        byte hdrs[4] = '{8'h22, 8'h52, 8'h13, 8'h24};   // headers of four packets: kind is bits 7:6
        cp_kind.add("data", 0, 0); cp_kind.add("ctrl", 1, 1); cp_kind.add("err", 2, 2);
        foreach (hdrs[i]) cp_kind.sample(hdrs[i][7:6]);
        cp_kind.report();
        if (cp_kind.n_covered() != 2 || cp_kind.bin_list[2].hits != 0) $fatal(1, "FAIL: expected err to be the hole");
        cp_kind.sample(8'h92 >> 6);                      // one ERR packet header
        if (cp_kind.get_coverage() != 100.0) $fatal(1, "FAIL: the ERR packet did not close the hole");
        $display("PASS: kind has three bins; no ERR packet had been sent, and one closed the hole");
        $finish;
      end
    endmodule
    Terminal
    coverpoint cp_kind: 66.67% (2 of 3 bins)  below goal 100
        data           3
        ctrl           1
        err            0     <- hole
    PASS: kind has three bins; no ERR packet had been sent, and one closed the hole

    Closure in one page: what you just did, name the hole and write the test that fills it, is the loop that Coverage Closure & Sign-Off lays out. You can read that page now.

    Check yourself Five more ADD transactions are sampled in the first bench. What happens to the 66.67%?

    Nothing. The add count grows, but the percentage divides bins hit by bins in the goal, and shl is still unhit. Only a shift moves it.

    • Each bin is a plan row; the report names the unhit one.

    • More of the same traffic does not move a hole.

    • Read the hole, then write the test that fills it.

    Rule of thumb: read the named hole before adding stimulus.

    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.

    Five transactions with no shift read 66.67% with shl as the hole, and one directed shift closed it at 100%.

    PacketFIFO step 1: a three-bin kind coverpoint read 2 of 3 until one ERR header was sampled.

    Related topics

    • Next: An ALU plan as a covergroup

    • Coverage closure in one page

    Previous

    147 / 275

    Next: An ALU Coverage Plan as a Covergroup

    Up next

    Up nextAn ALU Coverage Plan as a Covergroup

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Covergroup Anatomy
    • Sampling: Clocked, Event & sample()
    • Sign-Off Needs Both: Code and Functional Coverage