Part 5 · Functional Coverage · Intermediate

Building Your First Coverage Model

Complete worked example: ALU spec to coverage plan to covergroup implementation to simulation to reading the report.

From spec to plan

A coverage model starts in the spec, not in code. Read the spec, extract the scenarios that must be proven to have occurred, write them down as a numbered plan, and only then implement covergroups whose bins map one-to-one onto plan items. The worked example: a small ALU.

diagram
ALU SPEC (excerpt)
  ──────────────────
  ops:     ADD, SUB, AND, OR, XOR, SHL
  inputs:  a[7:0], b[7:0]
  outputs: y[7:0], zero (y == 0), carry (ADD/SUB only)

  COVERAGE PLAN (derived from spec)
  ─────────────────────────────────
  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  operand extremes: a and b each seen at 0 and 255
  CP-5  every opcode executed with zero result   (cross)
  CP-6  shift by 0 and shift by 7 for SHL

Each plan item is a sentence a non-programmer could check off. The covergroup's job is to make every item mechanically measurable — by name.


Plan to covergroup

systemverilog
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 → 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 only meaningful for ADD/SUB → iff guard
    cp_carry : coverpoint carry iff (op inside {ADD, SUB}) {
      bins carry_set = {1};
    }

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

Driving and sampling it

systemverilog
module tb;
  alu_coverage cov = new();
  op_e        op;
  bit [7:0]   a, b, y;
  bit         zero, carry;

  initial begin
    repeat (2000) begin
      assert(std::randomize(op, a, b));
      alu_model(op, a, b, y, carry);   // reference computation
      zero = (y == 0);
      cov.cg.sample(op, a, b, y, zero, carry);
    end
    $display("ALU coverage: %0.1f%%", cov.cg.get_inst_coverage());
  end
endmodule

Reading the report and closing the loop

After simulation, the coverage report lists every coverpoint and bin with hit counts. Because bins are named after plan items, reading the report IS reviewing the plan. A typical first-run result for this model:

diagram
COVERGROUP alu_cov                       87.2%
  ├── cp_op                  100.0%   (CP-1 )
  │     add 412  sub 388  and_ 401  or_ 395  xor_ 379  shl 425
  ├── cp_zero                100.0%   (CP-2 )
  ├── cp_carry               100.0%   (CP-3 )
  ├── cp_a_ext                50.0%   (CP-4 )
  │     min_v 7   max_v 0   ◄── a == 255 never randomized
  ├── cp_b_ext                50.0%
  │     min_v 9   max_v 0
  ├── x_op_zero               66.7%   (CP-5 )
  │     <shl, asserted> 0   ◄── SHL never produced y == 0
  └── cp_shamt               100.0%   (CP-6 )

  HOLES  ACTIONS
  cp_a_ext.max_v    add constraint corner: a dist {255 := 5, ...}
  x_op_zero <shl>   directed case: SHL with a == 0
  1. Extract scenarios from the spec into a numbered, reviewable plan.

  2. Implement one coverpoint/cross per plan item; name bins so the report reads like the plan.

  3. Run with random stimulus; query get_inst_coverage() and dump the database.

  4. Read holes as missing scenarios, not missing percentage — each hole names a stimulus gap.

  5. Fix holes with constraint tweaks or small directed cases; rerun; repeat until every plan item is hit.

Key takeaways

  • Coverage models are derived from the spec via a written plan — never invented at the keyboard.

  • Name bins and coverpoints after plan items so reports are self-auditing.

  • Holes are actionable stimulus gaps: each unhit bin dictates a constraint change or directed test.

  • iff guards and binsof-filtered crosses keep the model measuring only meaningful combinations.

Common pitfalls

  • Writing covergroups without a plan — you measure what was easy to type, not what the spec requires.

  • Covering raw operand values exhaustively instead of extremes and properties — uncloseable bins.

  • Crossing everything with everything — CP-5 needed one filtered cross, not a full 6×2×2 product.

  • Declaring victory on the covergroup percentage without checking which bins are the unhit ones.