Functional Coverage

Functional coverage measures how thoroughly your tests exercise the design's functionality. It answers the critical question: "Have I tested all the scenarios I intended to test?" This is your complete guide to mastering coverage in SystemVerilog.

What is Functional Coverage?

Functional coverage is a user-defined metric that measures how much of the design's functionality has been exercised during verification. Unlike code coverage (which is automatic), functional coverage requires you to specify what scenarios you want to track.

The Verification Problem

Consider this scenario:

  • Your design has 100 possible transaction types
  • You run random tests and achieve 95% code coverage
  • But have you actually tested all 100 transaction types?
  • Code coverage won't tell you - functional coverage will!

Types of Coverage

Type What it Measures Automatic?
Code Coverage Lines, branches, conditions exercised in RTL Yes (tool-generated)
Functional Coverage Design features, scenarios, corner cases No (user-defined)
Assertion Coverage Which assertions were triggered Yes (from SVA)

Covergroups - The Container

A covergroup is a container that holds coverage definitions. It groups related coverage items and controls when sampling occurs.

Basic Covergroup Structure
class packet_coverage;
    
    // Variables to sample
    bit [7:0]  packet_size;
    bit [1:0]  packet_type;
    bit        priority;
    bit [31:0] address;
    
    // Covergroup definition
    covergroup packet_cg @(posedge clk);  // Sample on clock
        
        // Coverpoint for packet size
        size_cp: coverpoint packet_size {
            bins small  = {[0:63]};      // Sizes 0-63
            bins medium = {[64:127]};    // Sizes 64-127
            bins large  = {[128:191]};   // Sizes 128-191
            bins huge   = {[192:255]};   // Sizes 192-255
        }
        
        // Coverpoint for packet type
        type_cp: coverpoint packet_type {
            bins read   = {2'b00};
            bins write  = {2'b01};
            bins config = {2'b10};
            bins status = {2'b11};
        }
        
        // Coverpoint for priority
        prio_cp: coverpoint priority;  // Auto-bins: 0 and 1
        
        // Cross coverage - all combinations
        size_type_cross: cross size_cp, type_cp;
        
    endgroup
    
    // Constructor
    function new();
        packet_cg = new();
    endfunction
    
endclass

Coverpoints - What to Track

A coverpoint specifies a variable or expression to monitor. The simulator tracks all values this variable takes during simulation.

Automatic Bins

Without explicit bin definitions, SystemVerilog creates bins automatically:

Automatic Bins
covergroup auto_cg;
    // 4-bit signal: creates 16 bins (0, 1, 2, ... 15)
    cp1: coverpoint state;
    
    // 1-bit signal: creates 2 bins (0, 1)  
    cp2: coverpoint valid;
    
    // Limit auto-bins with option
    cp3: coverpoint addr {
        option.auto_bin_max = 8;  // Max 8 bins instead of 2^32
    }
endgroup

User-Defined Bins

You define exactly what ranges/values to track:

User-Defined Bins
covergroup user_bins_cg;
    
    // Specific values
    cmd_cp: coverpoint cmd {
        bins read  = {4'h0};
        bins write = {4'h1};
        bins erase = {4'h2};
        bins idle  = {4'hF};
    }
    
    // Value ranges
    addr_cp: coverpoint address {
        bins low_range    = {[32'h0000_0000:32'h0000_FFFF]};
        bins mid_range    = {[32'h0001_0000:32'h7FFF_FFFF]};
        bins high_range   = {[32'h8000_0000:32'hFFFF_FFFF]};
    }
    
    // Multiple values in one bin
    special_cp: coverpoint opcode {
        bins arithmetic = {ADD, SUB, MUL, DIV};
        bins logical    = {AND, OR, XOR, NOT};
        bins shift      = {SHL, SHR, ROL, ROR};
    }
    
    // Array of bins (one bin per element)
    size_cp: coverpoint size {
        bins sizes[] = {1, 2, 4, 8, 16, 32};  // 6 separate bins
    }
    
    // Ignore specific values
    state_cp: coverpoint state {
        bins active_states[] = {[0:10]};
        ignore_bins invalid = {11, 12, 13, 14, 15};
    }
    
    // Illegal values (cause error if hit)
    prot_cp: coverpoint protection {
        bins valid_prot[] = {0, 1, 2, 3};
        illegal_bins reserved = {[4:7]};
    }
    
endgroup

Cross Coverage - Combinations

Cross coverage tracks all combinations of two or more coverpoints. This is crucial for finding bugs that only appear in specific scenarios.

Cross Coverage Examples
covergroup transaction_cg;
    
    // Coverpoints
    cmd_cp: coverpoint cmd {
        bins read  = {CMD_READ};
        bins write = {CMD_WRITE};
    }
    
    size_cp: coverpoint size {
        bins byte_size  = {1};
        bins word_size  = {4};
        bins dword_size = {8};
    }
    
    addr_cp: coverpoint addr[1:0] {
        bins aligned   = {0};
        bins unaligned = {1, 2, 3};
    }
    
    // Cross of cmd and size
    // Creates: read+byte, read+word, read+dword,
    //          write+byte, write+word, write+dword
    cmd_size_cross: cross cmd_cp, size_cp;
    
    // Cross all three
    // Creates 2 x 3 x 2 = 12 combinations
    full_cross: cross cmd_cp, size_cp, addr_cp;
    
    // Cross with specific bins
    important_scenarios: cross cmd_cp, size_cp {
        // Only care about these specific combinations
        bins write_large = binsof(cmd_cp.write) && binsof(size_cp.dword_size);
        bins read_small  = binsof(cmd_cp.read) && binsof(size_cp.byte_size);
        
        // Ignore some combinations
        ignore_bins unimportant = binsof(cmd_cp.read) && binsof(size_cp.dword_size);
    }
    
endgroup

Why Cross Coverage Matters

Without cross coverage, you might have 100% coverage on individual coverpoints but miss critical combinations:

  • Read command: ✓ covered
  • Write command: ✓ covered
  • Unaligned address: ✓ covered
  • Write to unaligned address: Never tested!

Transition Coverage

Transition coverage tracks value changes over time. It's essential for state machines and sequential logic verification.

Transition Bins
covergroup state_machine_cg;
    
    state_cp: coverpoint state {
        // Track individual states
        bins idle   = {IDLE};
        bins active = {ACTIVE};
        bins done   = {DONE};
        bins error  = {ERROR};
        
        // Track specific transitions
        bins idle_to_active = (IDLE => ACTIVE);
        bins active_to_done = (ACTIVE => DONE);
        bins done_to_idle   = (DONE => IDLE);
        bins any_to_error   = (IDLE, ACTIVE, DONE => ERROR);
        bins error_recovery = (ERROR => IDLE);
        
        // Track multi-step sequences
        bins full_cycle = (IDLE => ACTIVE => DONE => IDLE);
        
        // Range transitions
        bins increasing = (0 => 1 => 2 => 3);
        bins jumping    = (0 => 5), (5 => 10);
    }
    
    // Counter transitions
    count_cp: coverpoint count {
        bins up   = ([0:14] => [1:15]);     // Incrementing
        bins down = ([1:15] => [0:14]);     // Decrementing
        bins wrap = (15 => 0);              // Wrap around
    }
    
endgroup

Complete Coverage Example

Here's a production-quality coverage model for an AHB master:

AHB Master Coverage
class ahb_coverage extends uvm_subscriber #(ahb_transaction);
    `uvm_component_utils(ahb_coverage)
    
    // Local variables for sampling
    bit [31:0] addr;
    bit [1:0]  trans;
    bit        write;
    bit [2:0]  size;
    bit [2:0]  burst;
    bit [1:0]  resp;
    bit        ready;
    
    // ═══════════════════════════════════════════════════════════
    // COVERGROUP DEFINITION
    // ═══════════════════════════════════════════════════════════
    covergroup ahb_cg;
        
        option.per_instance = 1;
        option.name = "AHB_Coverage";
        
        // ─────────────────────────────────────────────────────
        // Transfer Type
        // ─────────────────────────────────────────────────────
        trans_cp: coverpoint trans {
            bins idle   = {2'b00};
            bins busy   = {2'b01};
            bins nonseq = {2'b10};
            bins seq    = {2'b11};
        }
        
        // ─────────────────────────────────────────────────────
        // Write/Read
        // ─────────────────────────────────────────────────────
        write_cp: coverpoint write {
            bins read  = {0};
            bins write = {1};
        }
        
        // ─────────────────────────────────────────────────────
        // Transfer Size
        // ─────────────────────────────────────────────────────
        size_cp: coverpoint size {
            bins byte_size       = {3'b000};  // 8 bits
            bins halfword_size   = {3'b001};  // 16 bits
            bins word_size       = {3'b010};  // 32 bits
            bins doubleword_size = {3'b011};  // 64 bits
            illegal_bins reserved = {[3'b100:3'b111]};
        }
        
        // ─────────────────────────────────────────────────────
        // Burst Type
        // ─────────────────────────────────────────────────────
        burst_cp: coverpoint burst {
            bins single  = {3'b000};
            bins incr    = {3'b001};
            bins wrap4   = {3'b010};
            bins incr4   = {3'b011};
            bins wrap8   = {3'b100};
            bins incr8   = {3'b101};
            bins wrap16  = {3'b110};
            bins incr16  = {3'b111};
        }
        
        // ─────────────────────────────────────────────────────
        // Response Type
        // ─────────────────────────────────────────────────────
        resp_cp: coverpoint resp {
            bins okay  = {2'b00};
            bins error = {2'b01};
            bins retry = {2'b10};
            bins split = {2'b11};
        }
        
        // ─────────────────────────────────────────────────────
        // Address Alignment
        // ─────────────────────────────────────────────────────
        addr_align_cp: coverpoint addr[2:0] {
            bins align_byte  = {3'b000, 3'b001, 3'b010, 3'b011, 
                                3'b100, 3'b101, 3'b110, 3'b111};
            bins align_half  = {3'b000, 3'b010, 3'b100, 3'b110};
            bins align_word  = {3'b000, 3'b100};
            bins align_dword = {3'b000};
        }
        
        // ─────────────────────────────────────────────────────
        // Address Regions
        // ─────────────────────────────────────────────────────
        addr_region_cp: coverpoint addr[31:28] {
            bins boot_rom    = {4'h0};
            bins peripheral  = {4'h4};
            bins sram        = {4'h2};
            bins ddr         = {[4'h8:4'hF]};
        }
        
        // ─────────────────────────────────────────────────────
        // Wait State Handling
        // ─────────────────────────────────────────────────────
        wait_cp: coverpoint ready {
            bins no_wait = {1};
            bins wait_state = {0};
        }
        
        // ═══════════════════════════════════════════════════
        // CROSS COVERAGE
        // ═══════════════════════════════════════════════════
        
        // Write/Read vs Size
        write_size_cross: cross write_cp, size_cp;
        
        // Write/Read vs Burst
        write_burst_cross: cross write_cp, burst_cp;
        
        // Burst vs Size (important for alignment)
        burst_size_cross: cross burst_cp, size_cp {
            // Illegal: Wrapping burst crossing 1KB boundary
            ignore_bins large_wrap = binsof(burst_cp) intersect {3'b110, 3'b111} &&
                                      binsof(size_cp.doubleword_size);
        }
        
        // Response vs Transfer Type
        resp_trans_cross: cross resp_cp, trans_cp {
            // IDLE transfers always get OKAY
            illegal_bins idle_error = binsof(trans_cp.idle) && 
                                       binsof(resp_cp) intersect {2'b01, 2'b10, 2'b11};
        }
        
        // ═══════════════════════════════════════════════════
        // TRANSITION COVERAGE
        // ═══════════════════════════════════════════════════
        
        trans_transitions: coverpoint trans {
            // Valid transitions
            bins start_burst = (2'b00 => 2'b10);  // IDLE to NONSEQ
            bins continue_burst = (2'b10 => 2'b11) , (2'b11 => 2'b11);
            bins end_burst = (2'b11 => 2'b10), (2'b11 => 2'b00);
            bins busy_insert = (2'b10 => 2'b01), (2'b11 => 2'b01);
            
            // Important: BUSY to SEQ or NONSEQ
            bins busy_resume = (2'b01 => 2'b11), (2'b01 => 2'b10);
        }
        
    endgroup
    
    // ═══════════════════════════════════════════════════════════
    // COMPONENT METHODS
    // ═══════════════════════════════════════════════════════════
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
        ahb_cg = new();
    endfunction
    
    // Called for each transaction
    function void write(ahb_transaction t);
        // Copy to local variables
        addr  = t.haddr;
        trans = t.htrans;
        write = t.hwrite;
        size  = t.hsize;
        burst = t.hburst;
        resp  = t.hresp;
        ready = t.hready;
        
        // Sample the covergroup
        ahb_cg.sample();
    endfunction
    
    // Report coverage at end of simulation
    function void report_phase(uvm_phase phase);
        real coverage_pct;
        coverage_pct = ahb_cg.get_inst_coverage();
        
        `uvm_info("COV", $sformatf(
            "\n═══════════════════════════════════════════\n" +
            " AHB Coverage Report\n" +
            "═══════════════════════════════════════════\n" +
            " Overall Coverage: %.2f%%\n" +
            "═══════════════════════════════════════════",
            coverage_pct), UVM_LOW)
    endfunction
    
endclass

Coverage Options

SystemVerilog provides several options to control coverage behavior:

Important Coverage Options
covergroup options_demo;
    
    // ─────────────────────────────────────────────────────
    // COVERGROUP OPTIONS (apply to entire covergroup)
    // ─────────────────────────────────────────────────────
    
    option.name = "MyGroup";           // Name in reports
    option.comment = "Transaction coverage";
    option.per_instance = 1;           // Per-instance tracking
    option.goal = 100;                 // Coverage goal (%)
    option.weight = 2;                 // Weight in total coverage
    option.at_least = 5;               // Minimum hits per bin
    option.auto_bin_max = 64;          // Max auto-generated bins
    
    // ─────────────────────────────────────────────────────
    // COVERPOINT OPTIONS (per coverpoint)
    // ─────────────────────────────────────────────────────
    
    cp1: coverpoint signal1 {
        option.at_least = 10;          // Need 10 hits per bin
        option.weight = 0;             // Exclude from total
        option.goal = 90;              // Only need 90%
        option.auto_bin_max = 8;       // Max 8 auto bins
    }
    
    // ─────────────────────────────────────────────────────
    // CROSS OPTIONS
    // ─────────────────────────────────────────────────────
    
    cross1: cross cp1, cp2 {
        option.weight = 3;             // Important cross
        option.at_least = 2;           // Need 2 hits per bin
    }
    
endgroup

Topics Covered

Browse the sidebar to dive deeper:

  • Covergroups - Creating and instantiating covergroups
  • Coverpoints - Defining what to track
  • Bins - Grouping values and ranges
  • Cross Coverage - Tracking combinations
  • Transitions - State change coverage
  • Options - Controlling coverage behavior
  • Sampling - When and how to sample
  • UVM Integration - Coverage in UVM testbenches

Common Interview Questions

  1. What is the difference between functional and code coverage?

    Code coverage is automatic - it measures which lines/branches of RTL were executed. Functional coverage is user-defined - it measures which design scenarios were exercised based on your verification plan.

  2. When do you use cross coverage?

    When you need to verify that specific combinations of values occurred together. Individual coverpoints might show 100% coverage, but you still need cross coverage to ensure all combinations were tested.

  3. What is "at_least" option?

    It specifies the minimum number of times a bin must be hit to be considered covered. Default is 1. Higher values ensure scenarios were tested multiple times.

  4. What are illegal_bins vs ignore_bins?

    ignore_bins exclude values from coverage calculation - they won't affect coverage percentage. illegal_bins cause an error if hit - they represent scenarios that should never occur.