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:
- You can execute every line and miss critical corner cases
- Example:
if (addr[1:0] == 0)covers the true branch, but miss 3 possible misalignments
Functional coverage answers: "Have I tested all meaningful scenarios for the design?"
- ✅ All opcode values?
- ✅ All address ranges (low, mid, high)?
- ✅ All combinations of (opcode, addr range, burst length)?
- ✅ Back-to-back transactions vs. spaced-out?
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:
- Define coverage points (what to measure)
- Define bins (buckets for values)
- Call
axi_cg.sample()each transaction - 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:
- Use ranges
[min:max]for continuous values - Use explicit values
{ val1, val2, val3 }for discrete cases defaultcatches unmapped values (useful for error cases)$means the maximum value the data type can hold
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:
| cmd | addr (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
- ✅ Covergroups = declarative specification of what to measure
- ✅ Bins = buckets for ranges, discrete values, wildcards
- ✅ Cross-coverage = combinations of coverage points (where the real insight is)
- ✅ Call
sample()on every meaningful transaction - ✅ Aim for 100% functional coverage; unrealistic coverage goals slow you down
- ✅ Coverage drives test completeness—not lines of code
Tomorrow (Day 14): Advanced coverage techniques — merging, exclusions, and coverage-driven test generation.