Day 05 / 25
← Day 04 Day 06 →
HomeVerificationDay 5 — Functional Coverage
Track 1 — Foundations

Functional Coverage — Covergroup & Coverpoint

Functional coverage answers the question "what have we actually tested?" It lets you define a coverage model based on your verification plan — specific values, ranges, transitions, and cross-combinations — and tracks which ones your testbench has exercised.

⏱ 26 min read📖 Day 5 of 25🎯 Coverage · Bins · Cross
Contents
  1. Functional vs Code Coverage
  2. covergroup Declaration
  3. bins — Explicit and Auto Bins
  4. cross Coverage
  5. Transition Bins
  6. iff Guard
  7. sample() and Manual Sampling
  8. $get_coverage and Goals
  9. UART Frame Coverage Model

1. Functional vs Code Coverage

Code coverage is automatically extracted by the simulator — it tells you which lines and branches of RTL were reached. But reaching a line of code is not the same as testing the right scenario. Functional coverage is a user-defined metric driven by the verification plan:

AspectCode CoverageFunctional Coverage
SourceExtracted from RTL automaticallyWritten by verification engineer
MeasuresWhich RTL statements were executedWhich design scenarios were exercised
100% meansAll code was touchedAll planned scenarios were hit
MissesLegal corner cases not in the code pathBugs in the coverage model itself
Closure goal: A project is not "done" when code coverage hits 100%. You need both — code coverage to find dead code, and functional coverage to confirm all specification scenarios were verified.

2. covergroup Declaration

A covergroup is like a class — you define it once and can instantiate it multiple times. It can live in a class, module, or interface. The sampling trigger can be a clock edge, an event, or the manual sample() call:

// Covergroup triggered by a clock edge
covergroup cg_transfer @(posedge clk);
  cp_addr: coverpoint addr;        // auto bins for all values of addr
  cp_data: coverpoint data[7:0];   // only lower 8 bits
endgroup

// Covergroup inside a class — uses sample() for manual control
class uart_cov_model;
  covergroup cg_uart;
    cp_baud: coverpoint baud_rate;
    cp_par:  coverpoint parity;
  endgroup

  function new();
    cg_uart = new();   // instantiate the covergroup
  endfunction

  function void sample_transaction(uart_pkt pkt);
    baud_rate = pkt.baud; parity = pkt.par;
    cg_uart.sample();    // explicit sample call
  endfunction
endclass

3. bins — Explicit and Auto Bins

Without explicit bins, the simulator creates automatic bins. For a 4-bit signal that gives 16 auto-bins. You can override with meaningful named bins:

covergroup cg_opcode @(posedge clk);
  cp_op: coverpoint opcode {
    // Named single-value bins
    bins add  = {4'b0000};
    bins sub  = {4'b0001};
    bins mul  = {4'b0010};

    // Range bin — set of values, one bin
    bins alu  = {[4'b0011:4'b0111]};

    // Array bin — one bin PER value in range ([] suffix)
    bins load[] = {[4'b1000:4'b1011]};

    // Wildcard bin — matches any value with X in that position
    bins store_any = {4'b11??};

    // Default bin — catches everything not matched above
    bins others = default;
  }
endgroup

// ignore_bins — values that should NOT count as covered
// illegal_bins — values that should NEVER occur (causes error if seen)
covergroup cg_state @(posedge clk);
  cp_st: coverpoint state {
    bins         valid_states[] = {IDLE, RUN, PAUSE, DONE};
    ignore_bins  rsvd         = {3'b101, 3'b110};
    illegal_bins bad_state    = {3'b111};
  }
endgroup

4. cross Coverage

cross tracks combinations of values across two or more coverpoints. It creates N×M bins automatically and reveals which pairs were never exercised:

covergroup cg_rw_size @(posedge clk);
  // Individual coverpoints
  cp_rw: coverpoint rw {
    bins read  = {1'b0};
    bins write = {1'b1};
  }
  cp_size: coverpoint xfer_size {
    bins byte_sz  = {2'b00};
    bins half_sz  = {2'b01};
    bins word_sz  = {2'b10};
    bins dword_sz = {2'b11};
  }

  // Cross: 2 x 4 = 8 bins automatically
  // Checks: was a WRITE with DWORD size ever done? etc.
  cx_rw_size: cross cp_rw, cp_size;
endgroup

// Exclude specific cross bins (e.g., invalid combinations)
covergroup cg_cross_filtered @(posedge clk);
  cp_rw:   coverpoint rw;
  cp_size: coverpoint xfer_size;
  cx: cross cp_rw, cp_size {
    ignore_bins no_dword_read =
      binsof(cp_rw.read) && binsof(cp_size.dword_sz);
  }
endgroup

5. Transition Bins

Transition bins capture sequences of values — not just individual values but the order in which they occur. Ideal for state machine coverage:

covergroup cg_fsm_trans @(posedge clk);
  cp_state: coverpoint state {
    // Simple state values
    bins idle  = {IDLE};
    bins run   = {RUN};
    bins pause = {PAUSE};
    bins done  = {DONE};

    // Transition bins — 2-step
    bins idle2run   = (IDLE  => RUN);
    bins run2pause  = (RUN   => PAUSE);
    bins pause2run  = (PAUSE => RUN);
    bins run2done   = (RUN   => DONE);

    // Multi-step transition — full normal flow
    bins full_flow = (IDLE => RUN => DONE);

    // Error recovery path
    bins err_recovery = (RUN => PAUSE => RUN => DONE);
  }
endgroup

6. iff Guard

The iff keyword attaches a guard condition to a coverpoint or the entire covergroup. Sampling only occurs when the guard is true:

covergroup cg_bus @(posedge clk);
  // Only sample this coverpoint when the bus is enabled
  cp_addr: coverpoint addr iff (bus_enable) {
    bins low    = {[0:15]};
    bins mid    = {[16:239]};
    bins hi     = {[240:255]};
  }

  // Only sample write data during actual write transactions
  cp_wdata: coverpoint wdata[7:0] iff (bus_enable && !rnw);
endgroup

// iff on the entire covergroup (at instantiation)
covergroup cg_rd @(posedge clk) iff (rd_valid);
  cp_rdata: coverpoint rdata;
endgroup

7. sample() and Manual Sampling

By default a covergroup is sampled at the triggering event. The sample() method lets you control when to sample from procedural code — useful when you're inside a UVM component and want to sample after transaction processing:

class my_coverage;
  logic [7:0] addr_v;
  logic [1:0] size_v;
  logic        rnw_v;

  // No automatic trigger — sample() controls when
  covergroup cg_xfer;
    cp_addr: coverpoint addr_v {
      bins low[]  = {[0:63]};
      bins high[] = {[192:255]};
    }
    cp_size: coverpoint size_v;
    cp_rnw:  coverpoint rnw_v;
    cx_size_rnw: cross cp_size, cp_rnw;
  endgroup

  function new(); cg_xfer = new(); endfunction

  // Called from monitor write() or scoreboard
  function void sample(bus_item txn);
    addr_v = txn.addr;
    size_v = txn.size;
    rnw_v  = txn.rnw;
    cg_xfer.sample();  // manual trigger
  endfunction
endclass

8. $get_coverage and Coverage Goals

// $get_coverage() returns overall coverage percentage (0.0–100.0)
// cg_xfer.get_coverage() returns coverage for one instance
// cp_addr.get_coverage() returns coverage for one coverpoint

initial begin
  wait(sim_done);
  $display("Overall coverage: %.1f%%", $get_coverage());
  $display("Transfer cg:      %.1f%%", cov_inst.cg_xfer.get_coverage());
end

// Coverage option — set goals and names per-instance
covergroup cg_with_goal @(posedge clk);
  option.name        = "cg_ctrl";   // name in report
  option.goal        = 95;          // target 95% (default 100)
  option.at_least    = 2;           // bin needs 2 hits to count
  option.per_instance = 1;          // report each instance separately

  cp_ctrl: coverpoint ctrl_reg;
endgroup

9. UART Frame Coverage Model

A complete coverage model for a UART transmitter, capturing baud rates, parity modes, data values, and the full state transition sequence:

typedef enum logic [1:0] {PAR_NONE=0, PAR_ODD=1, PAR_EVEN=2} parity_e;
typedef enum logic [2:0] {
  ST_IDLE=0, ST_START=1, ST_DATA=2,
  ST_PARITY=3, ST_STOP=4
} uart_state_e;

class uart_coverage;
  int          baud_v;
  parity_e     par_v;
  logic [7:0] data_v;
  uart_state_e state_v;

  covergroup cg_uart_frame;
    // Baud rate coverage — named bins for each standard rate
    cp_baud: coverpoint baud_v {
      bins b9600   = {9600};
      bins b115200 = {115200};
      bins b921600 = {921600};
    }

    // Parity mode
    cp_parity: coverpoint par_v {
      bins none = {PAR_NONE};
      bins odd  = {PAR_ODD};
      bins even = {PAR_EVEN};
    }

    // Data value coverage — corner cases + all-ones/all-zeros
    cp_data: coverpoint data_v {
      bins all_zero = {8'h00};
      bins all_one  = {8'hFF};
      bins low[]    = {[8'h01:8'h0F]};
      bins mid[]    = {[8'h10:8'hEF]};
      bins high[]   = {[8'hF0:8'hFE]};
    }

    // State machine transition coverage
    cp_state_trans: coverpoint state_v {
      bins normal_frame =
        (ST_IDLE => ST_START => ST_DATA => ST_STOP => ST_IDLE);
      bins parity_frame =
        (ST_IDLE => ST_START => ST_DATA => ST_PARITY => ST_STOP => ST_IDLE);
    }

    // Cross: was each baud rate tested with each parity mode?
    cx_baud_par: cross cp_baud, cp_parity;
  endgroup

  function new(); cg_uart_frame = new(); endfunction

  function void sample_frame(uart_transaction txn);
    baud_v  = txn.baud_rate;
    par_v   = txn.parity;
    data_v  = txn.data;
    state_v = txn.state;
    cg_uart_frame.sample();
  endfunction
endclass

Key Takeaways — Day 5

Next → Day 06
Introduction to UVM
IEEE 1800.2 UVM library — uvm_component vs uvm_object, phasing, factory, reporting macros, Hello UVM testbench.