HomeSV VerificationDay 13

Functional Coverage

Measure what you've tested. Covergroups, coverage points, and bins let you see exactly which scenarios your testbench has exercised and which remain.

📍 Today's Topics

Why Measure Coverage?

On Day 12, you generated constrained-random tests. But how do you know when you're done? How many tests are enough? Did you hit all scenarios?

Code coverage (lines executed) isn't enough:

Functional coverage answers: "Have I tested all meaningful scenarios for the design?"

Result: Know when you can stop testing—and avoid spending weeks on unnecessary test cases.

Covergroups & Coverage Points

A covergroup is a declarative specification of what to measure. It contains coverage points that bucket values:

class AXI_Monitor; // Signals to observe bit [31:0] awaddr; bit [7:0] awlen; bit [2:0] awsize; // Covergroup: declare what to track covergroup axi_cg; // Coverage point 1: address ranges (low, mid, high) cp_addr: coverpoint awaddr { bins low = { [0:'h3FFF_FFFF] }; bins high = { ['h4000_0000:'hFFFF_FFFF] }; } // Coverage point 2: transaction length cp_len: coverpoint awlen { bins short = { [0:15] }; bins medium = { [16:127] }; bins long = { [128:255] }; } // Coverage point 3: transfer size cp_size: coverpoint awsize { bins b1 = { 3'b000 }; // 1 byte bins b2 = { 3'b001 }; // 2 bytes bins b4 = { 3'b010 }; // 4 bytes bins b8 = { 3'b011 }; // 8 bytes bins reserved = default; } endgroup function new(); axi_cg = new(); endfunction function void sample(); axi_cg.sample(); // Record a sample endfunction endclass

How it works:

  1. Define coverage points (what to measure)
  2. Define bins (buckets for values)
  3. Call axi_cg.sample() each transaction
  4. Simulator reports: "Covered 7/9 bins (78%)"

Binning & Bucketing Values

Bins partition values into meaningful groups:

covergroup traffic_cg; // Method 1: Explicit ranges cp_burst: coverpoint burst_len { bins b1 = { 1 }; bins b2_4 = { [2:4] }; bins b5_16 = { [5:16] }; bins b17plus = { [17:$] }; // $ = max value } // Method 2: Auto bins (one per value in range) cp_id: coverpoint pkt_id { bins ids[] = { [0:255] }; // 256 separate bins } // Method 3: Wildcard (useful for multi-bit fields) cp_mask: coverpoint byte_mask { wildcard bins all_ones = 8'b1111_1111; wildcard bins some_ones = 8'b????_???1; // ? = don't care wildcard bins all_zeros = 8'b0000_0000; } // Method 4: Default (catch-all) cp_cmd: coverpoint cmd { bins read = { 8'h01 }; bins write = { 8'h02 }; bins other = default; // Any value not explicitly binned } endgroup

Pro tips:

Cross-Coverage (Combinations)

Coverage points are single-variable. But real verification cares about combinations:

Question: "Did I test both READ at low addresses AND READ at high addresses?"

Answer: Cross-coverage:

covergroup protocol_cg; cp_cmd: coverpoint cmd { bins read = { 8'hAA }; bins write = { 8'hBB }; } cp_addr: coverpoint addr { bins low = { [0:'h0000_7FFF] }; bins high = { ['h0000_8000:'hFFFF_FFFF] }; } cp_len: coverpoint len { bins short = { [1:32] }; bins long = { [33:256] }; } // Cross: (cmd × addr) = 2×2 = 4 combinations cross_cmd_addr: cross cp_cmd, cp_addr; // Cross: (cmd × addr × len) = 2×2×2 = 8 combinations cross_all: cross cp_cmd, cp_addr, cp_len; endgroup

Cross-coverage matrix:

cmdaddr (low)addr (high)
READ✓ Covered✗ Missing
WRITE✓ Covered✓ Covered

Coverage Sampling

Call sample() at the right time to capture meaningful data:

module axi_tb(); logic [31:0] awaddr; logic [7:0] awlen; bit awvalid, awready; Monitor mon = new(); always @(posedge clk) begin if (awvalid && awready) begin // Transaction valid mon.awaddr = awaddr; mon.awlen = awlen; mon.sample(); // Record coverage for this cycle end end // Print coverage report final begin $display("=== Coverage Report ==="); mon.axi_cg.sample(); // Ensure last sample is included mon.axi_cg.stop(); end endmodule

Full Example: Protocol Coverage

Complete USB transaction monitor with functional coverage:

class USB_Monitor; bit [7:0] pid; // Packet ID (SETUP, DATA, ACK, etc.) bit [6:0] addr; // Device address bit [3:0] endp; // Endpoint covergroup usb_cg @ (posedge clk); // PID types cp_pid: coverpoint pid { bins setup = { 8'hD0 }; bins data0 = { 8'hC3 }; bins data1 = { 8'h4B }; bins ack = { 8'hD2 }; bins nak = { 8'h5A }; bins stall = { 8'h1E }; } // Device address ranges cp_addr: coverpoint addr { bins addr_0 = { 7'd0 }; bins addr_mid = { [1:63] }; } // Endpoint types cp_endp: coverpoint endp { bins control = { 4'h0 }; bins bulk = { [4'h1:4'hF] }; } // Cross: (PID × Endpoint) - all transaction types cross_pid_endp: cross cp_pid, cp_endp { // Ignore impossible combinations illegal_bins invalid = binsof(cp_pid) matches { 8'hD0 } && binsof(cp_endp) matches { 4'h1 }; } endgroup function new(); usb_cg = new(); endfunction function void sample(bit [7:0] p, bit [6:0] a, bit [3:0] e); pid = p; addr = a; endp = e; usb_cg.sample(); endfunction endclass // Use in testbench module usb_tb(); USB_Monitor mon = new(); initial begin #10 mon.sample(8'hD0, 7'd1, 4'h0); // SETUP to addr 1, EP 0 #10 mon.sample(8'hC3, 7'd1, 4'h0); // DATA0 to addr 1, EP 0 #10 mon.sample(8'hD2, 7'd1, 4'h0); // ACK from addr 1, EP 0 // ... repeat many times end final begin $display(mon.usb_cg); // Print full coverage report end endmodule

Coverage report output:

Coverage Report: usb_cg (79% / 100%) cp_pid: 5/6 bins (83%) [setup, data0, data1, ack, nak] cp_addr: 2/2 bins (100%) cp_endp: 4/4 bins (100%) cross_pid_endp: 15/16 bins (94%)

Key Takeaways

Tomorrow (Day 14): Advanced coverage techniques — merging, exclusions, and coverage-driven test generation.