Skip to content

SystemVerilog · Module 11

Introduction to Functional Coverage

Why functional coverage is the only honest verification-closure metric, the four goals every plan must satisfy, the CRV feedback loop, and the trap of 100% code coverage with 0% functional coverage.

Module 11 · Page 11.1

What Functional Coverage Measures

Functional coverage is a user-defined, specification-driven metric that counts how many of the scenarios you declared interesting have actually been exercised by your tests. You write the coverage model. The simulator counts the hits. The percentage answers the only question that matters at the end of a verification project: have we verified what the spec says we must?

It is the opposite of code coverage. Code coverage is structural and automatic — the EDA tool inspects RTL and reports which lines, branches, and expressions a simulation executed. Functional coverage is intentional and manual — you tell the simulator which values, transitions, and combinations of signals encode "real verification work."

Why Code Coverage Is Not Enough

Two teams report "1000 tests passing, 100% line coverage." One team hit every corner case in a 200-row verification plan. The other team hit ten scenarios a hundred times each. Code coverage cannot distinguish these outcomes. Functional coverage can.

AspectFunctional CoverageCode Coverage
AuthorVerification engineerEDA tool
SourceThe verification plan and the specThe RTL source as written
MeasuresWhether features were verifiedWhether code was executed
Setup effortHigh — requires plan-to-coverpoint translationNone — -cov flag on the simulator
100% achievable?Yes, with ignore_bins for unreachable scenariosOften not — dead code, synthesis-only branches
Silent failureEngineer forgets to write the coverpointCode runs with safe values, real bug never triggered
Use at sign-offThe closure number that gates tape-outDiagnostic — finds unexercised RTL paths

The Four Coverage Goals

Every verification plan organised around functional coverage must satisfy four distinct goals. They are not synonyms for each other; a model that covers one but omits another leaves real scenarios unverified.

1 — Completeness

Every feature in the verification plan has at least one coverpoint. If a feature has no coverage, no test result tells you whether it was exercised. The goal is: zero uncovered features in the plan, full stop.

2 — Corner Cases

Boundary values, min/max payloads, empty/full FIFOs, simultaneous events, illegal opcodes. Dedicated bins for the corners that random traffic almost never hits — without them, an "85% coverage" report can be the easy 85% while the hard 15% sits at zero.

3 — Scenario Coverage

Cross coverage. A READ is well-tested in isolation; a maximum payload is well-tested in isolation; "READ with maximum payload" might never have been combined. The intersection is where most silicon-stage bugs live.

4 — Regression Quality

Cumulative coverage across regression runs, merged into a single database. If a design change drops merged coverage, the regression suite has quietly lost reach — and you know immediately, not at silicon.

A Concrete Example — Covering APB Transactions

Concepts hold up best against working code. Here is a minimal covergroup for an AMBA APB transaction. It tracks the address space, the read/write split, and — crucially — the cross of the two, because a fault in PWRITE decoding inside a specific address region is not visible from PWRITE alone or PADDR alone.

SystemVerilog — covergroup on an APB transaction
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Transaction class with embedded coverage ───────────────────────
class apb_txn;
    rand bit [31:0] paddr;
    rand bit         pwrite;        // 1 = write, 0 = read
    rand bit [31:0] pwdata;
 
    // Sampling event — fired explicitly when the monitor publishes
    // a completed transaction. See Module 11.9 for event details.
    covergroup cg_apb;
        cp_addr: coverpoint paddr {
            bins low_regs   = {[32'h0000_0000:32'h0000_00FF]};   // CSR region
            bins ram        = {[32'h1000_0000:32'h1FFF_FFFF]};   // mapped RAM
            bins boundary[] = {32'h0000_0000, 32'h0000_00FF,     // explicit
                                32'h1000_0000, 32'h1FFF_FFFF};    // corners
            illegal_bins reserved = {[32'hF000_0000:32'hFFFF_FFFF]};
        }
        cp_dir: coverpoint pwrite {
            bins read_xn  = {1'b0};
            bins write_xn = {1'b1};
        }
        // The intersection — scenario coverage.
        cr_addr_dir: cross cp_addr, cp_dir;
    endgroup
 
    function new();
        cg_apb = new();   // covergroup must be constructed
    endfunction
 
    function void sample_cov();
        cg_apb.sample();  // monitor calls this after each transaction
    endfunction
endclass

What this code does. Declares a transaction class, embeds a covergroup that tracks two coverpoints and their cross, constructs the covergroup in new() (forgetting this is the most common bug — see the Debug Lab below), and exposes a sampling hook.

How to simulate it. Compile under any IEEE 1800-compatible simulator (vcs -sverilog -cm line+cond+fsm+tgl+branch+cov for VCS, vsim -coverage for Questa, xrun -coverage all for Xcelium). Run a test that creates, randomises, and samples ≥ 1000 transactions, then dump coverage with the simulator's UCDB or urg/imc reporter.

Expected output (truncated urg-style report).

Functional coverage report — apb_txn::cg_apb
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Group       cg_apb                          coverage   78.6%
  cp_addr   low_regs                         100.0%   ( 412 hits )
            ram                              100.0%   ( 388 hits )
            boundary[0] (32'h0000_0000)      100.0%   (  12 hits )
            boundary[1] (32'h0000_00FF)      100.0%   (   9 hits )
            boundary[2] (32'h1000_0000)        0.0%   (   0 hits )   ← gap
            boundary[3] (32'h1FFF_FFFF)        0.0%   (   0 hits )   ← gap
            reserved                       ILLEGAL    (   0 hits )   ← good
  cp_dir    read_xn                          100.0%   ( 408 hits )
            write_xn                         100.0%   ( 412 hits )
  cr_addr_dir                                  62.5%   ( 10/16 bins )

What to read out of this. Two address-corner bins (32'h1000_0000 and 32'h1FFF_FFFF) are at zero — the random distribution favoured mid-range addresses. The cross is at 62.5% because the corner-address × write-direction combinations never occurred. This is the coverage report telling you the next directed test to write. No code-coverage tool reports this gap.

The Coverage-Driven Verification Loop

Functional coverage is the measurement instrument inside a four-stage feedback loop called Coverage-Driven Verification (CDV). The loop runs until the mandatory coverage targets are met.

1 — Author the coverage model

Translate the verification plan into covergroups and coverpoints. Decide bin boundaries, illegal bins, ignore bins, and per-bin at_least thresholds. Pair each plan item with a measurable bin.

2 — Run randomised regressions

Use constrained-random stimulus (Module 10) to drive the DUT. The solver produces diverse legal transactions; coverage bins accumulate hits across every simulation in the regression.

3 — Read the coverage report

Identify bins at zero or below threshold. Decide for each: is the scenario reachable, and if so, why has random stimulus not hit it? Is it actually unreachable and a candidate for ignore_bins?

4 — Close the gap

Write directed tests or tighten constraints to steer CRV traffic into the uncovered region. Add justified ignore_bins for unreachable scenarios. Re-run regression. Repeat until every mandatory bin has hit its threshold.

Inside Module 11 — What You'll Build

The rest of this module builds the coverage vocabulary in detail. Each subtopic targets one construct or one closure problem.

ConstructWhat it doesLesson
covergroup / coverpointThe container and the per-variable tracker11.2
Automatic binsThe default behaviour when no bins are declared11.3
Named binsExplicit bins/bins[] for declared value sets11.4
wildcard_bins / illegal_bins / ignore_binsPattern matching, error flagging, and unreachable scoping11.5
Transition coverage=> operator across sampled values11.6
Cross coverageScenario coverage between coverpoints11.7
option.at_least / option.weight / option.goalThreshold, importance, and closure target11.8
Sampling eventsWhen the simulator commits a sample to the database11.9
Instance vs type coveragePer-object tracking vs class-wide aggregation11.10

Common Pitfalls — The Three Coverage Traps

Most coverage failures are not modelling problems — they are sampling problems. The covergroup exists, the bins are correct, but the report says 0%.

Pitfall 1 — Forgetting to construct the covergroup
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class my_txn;
    covergroup cg_data;
        cp_d: coverpoint data { bins all[] = {[0:255]}; }
    endgroup
    // ── Missing: cg_data = new(); inside the constructor ──
endclass
 
// At simulation time:
//   my_txn t = new();
//   t.cg_data.sample();   // SEGFAULT — null handle dereference (VCS/Questa)
//                          // Or silently no-op (some flows). Report: 0%.
Pitfall 2 — Sampling the wrong handle
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class env;
    apb_txn t1, t2;
 
    task run();
        forever begin
            mbx.get(t1);     // monitor publishes a fresh transaction
            t2.sample_cov(); // SAMPLES t2 — the wrong object — always
                              // stale or null fields. Coverage looks
                              // healthy *for the wrong scenarios*.
        end
    endtask
endclass
Pitfall 3 — Sampling before the fields are valid
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_mon;
    task collect();
        apb_txn t = new();
        @(vif.pclk);            // wait one cycle
        t.sample_cov();         // PADDR/PWRITE still default 0
        // ...then drive fields ...
        t.paddr  = vif.paddr;
        t.pwrite = vif.pwrite;
    endtask
endclass
 
// The sample happened on the *initial* values, not the captured ones.
// Coverage shows everything hitting the "address 0, read" bin only.

The shape of the underlying mistake is consistent: the sample fires on a value the engineer did not intend. The fix is always upstream — construct the covergroup, sample the right handle, sample after the fields are valid.

Debug Lab — Coverage at 0% While All Tests Pass

A real failure mode. The DV team reports green: every test in the regression returns PASS. The verification lead requests the coverage report. It shows 0% functional coverage. Both statements are true, and both are tracking the same fact from different angles.

Buggy code
SystemVerilog — the report says PASS, coverage says 0%
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn;
    rand bit [31:0] paddr;
    rand bit         pwrite;
 
    covergroup cg_apb;
        cp_addr: coverpoint paddr { bins all = {[0:$]}; }
        cp_dir:  coverpoint pwrite;
    endgroup
    // ── BUG: no constructor for cg_apb ─────────────────────────
endclass
 
class apb_driver;
    task run();
        apb_txn t = new();
        repeat (10_000) begin
            assert(t.randomize());
            drive(t);
            t.cg_apb.sample();   // null-handle dereference, or silently
                                  // no-op, depending on tool flow
        end
    endtask
endclass
Symptom

Every transaction passes. The driver runs ten thousand transactions per test, the regression returns green, the scoreboard reports zero mismatches. urg / imc report: cg_apb 0% (0/2 coverpoints sampled). No assertion failure. No simulator warning unless +UVM_VERBOSITY=UVM_HIGH is set — and in some flows, no warning at all.

Root cause

The covergroup was declared but never constructed. SystemVerilog covergroups behave like class objects: declaration reserves the type, but new() must be called to instantiate the storage. On the offending line t.cg_apb.sample(), cg_apb is a null handle. VCS and Questa typically null-dereference (the simulator may swallow the error or report it only at high verbosity); some simulators silently no-op. Either way, the bin counters never increment. Tests pass — they were never sampling.

Fix

Construct the covergroup explicitly in the constructor:

SystemVerilog — the constructor fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class apb_txn;
    rand bit [31:0] paddr;
    rand bit         pwrite;
 
    covergroup cg_apb;
        cp_addr: coverpoint paddr { bins all = {[0:$]}; }
        cp_dir:  coverpoint pwrite;
    endgroup
 
    function new();
        cg_apb = new();       // ── the missing line ──
    endfunction
endclass

The discipline rule that prevents recurrence: every class that embeds a covergroup must construct that covergroup in new(), and the construction line must appear in the same commit as the covergroup declaration. Code review should flag the omission. Lint tools (Spyglass, JasperGold lint) detect it as COVERGROUP_NOT_CONSTRUCTED in default rule sets.

Industry Context — Where Coverage Sign-Off Actually Happens

Functional coverage is the document of record for tape-out readiness at every modern ASIC and large-FPGA project.

  • ARM Cortex-A / Cortex-M IP teams maintain coverage plans with thousands of bins per CPU subsystem; release gates require 100% closure on the mandatory plan items before a tag ships to integration partners.
  • PCI-SIG compliance flows publish coverage plans for LTSSM states, TLP types, DLLP cases, and credit return scenarios — the test labs check both protocol-compliance tests and coverage of the underlying scenarios.
  • DDR4/DDR5 controller verification uses coverage to ensure every refresh interaction, every training sequence, and every rank/bank crossing has been hit before the controller IP ships to SoC teams.
  • UVM-based VIP (AMBA, Ethernet, USB, JTAG vendors) ships with coverage models alongside the agents; the coverage model is the spec promise the VIP holds to the integrator.

Coverage closure rates are a leading indicator of project schedule risk: a project at 60% functional coverage two weeks before tape-out is in a different reality from one at 95%, no matter how green the regression is.

Interview Q&A — Ten Questions You Will Be Asked

A user-defined metric, derived from the verification plan, that measures which spec-driven scenarios the simulator has actually exercised — independent of whether tests passed and independent of which RTL lines ran.

Coding Guidelines — Six Rules for Coverage Discipline

  1. Every covergroup is constructed in new(). No exceptions. Declaration without construction is the single most common reason a coverage report reads 0%; code review should reject any class that omits the construction line.
  2. Coverage models trace one-to-one to the verification plan. Each plan row maps to a named bin or a named cross. A covergroup with bins that do not appear in the plan is dead weight; a plan row with no bin is an audit failure.
  3. illegal_bins for "must never happen"; ignore_bins for "legal but uninteresting / unreachable." Confusing the two either masks real bugs (illegal turned into ignore) or floods the regression with false errors (ignore turned into illegal). The distinction is intent, not value.
  4. Sample on a precise, well-named event. A covergroup tied to @(posedge clk) without qualification samples noise; tie it to a monitor's transaction_committed event or call sample() explicitly when the published transaction is final. The sampling event is part of the coverage model.
  5. Coverage closure is plan-driven, not percentage-driven. "100% coverage" with a missing mandatory bin is not sign-off; "92% coverage" with the rest in justified ignore bins is. Track closure per plan row, not per total-bin denominator.
  6. A green regression without coverage is not verification done. Sign-off gates require both: scoreboard cleanliness and coverage closure on the mandatory plan. Teams that ship on regressions alone find the gaps in the lab, where they cost an order of magnitude more.

Summary

Functional coverage is the only honest answer to "have we verified what the spec requires?" — code coverage proves the simulator executed the RTL, scoreboard cleanliness proves the transactions that were tested matched the reference, and functional coverage proves the testbench hit the scenarios the verification plan promised. Module 11 is the construct-by-construct build of that model: covergroups, coverpoints, bins, crosses, options, and the sampling discipline that makes the report trustworthy. The rest of the module assumes the framing from this page — coverage as the document of record for verification completeness, and CRV as the engine that fills the coverage database.