Part 5 · Functional Coverage · Intermediate

Code Coverage vs Functional Coverage

Line, toggle, branch, FSM, and expression coverage; why 100% code coverage proves nothing about scenarios; how both combine for sign-off.

Two different questions

Code coverage is extracted automatically by the simulator from the RTL structure: which lines executed, which branches took both directions, which signal bits toggled. It answers “did the implementation get exercised?” Functional coverage is written by the verification engineer from the specification: covergroups whose bins are scenarios. It answers “did the scenarios the spec requires actually happen?” One measures the code you wrote; the other measures the intent you were given.

The code coverage family

  • Line/statement — each executable RTL statement ran at least once.

  • Toggle — each signal bit transitioned 0→1 and 1→0.

  • Branch — each if/else and case arm taken; includes the implicit else.

  • Expression/condition — each boolean sub-term of a condition independently controlled the outcome.

  • FSM — each state visited and each defined state transition taken (tool-extracted from the state register).

diagram
CODE COVERAGE                      FUNCTIONAL COVERAGE
  ─────────────                      ───────────────────
  source:   RTL structure            source:   specification
  written:  automatically by tool    written:  by verification engineer
  measures: implementation exercised  measures: scenarios occurred
  100% =    all written code ran     100% =    all planned scenarios hit
  blind to: missing code,            blind to: anything not in the
            scenario meaning                   coverage model
  effort:   enable a switch          effort:   plan + covergroups

Why 100% code coverage proves nothing about scenarios

Code coverage can only measure code that exists. If the designer forgot to handle a case, there is no line to cover — the bug is invisible at 100% line coverage. And even for code that exists, line coverage only proves each piece ran in isolation, not that the interesting combination ever occurred. Concrete miss:

systemverilog
// FIFO write logic — looks innocent
always_ff @(posedge clk) begin
  if (push && !full)            // line A: covered by any normal push
    mem[wr_ptr] <= wdata;
  if (pop && !empty)            // line B: covered by any normal pop
    rd_ptr <= rd_ptr + 1;
end
// BUG: simultaneous push && pop while FULL corrupts the count
// elsewhere in the design.
//
// Test 1: pushes until full           → line A covered
// Test 2: pops until empty            → line B covered
// Line coverage: 100%. Toggle: 100%. Branch: 100%.
// The killing scenario (push && pop && full) NEVER OCCURRED.

// The functional coverage that would have exposed the gap:
covergroup cg_fifo @(posedge clk);
  cp_corner : coverpoint {push, pop, full} iff (rst_n) {
    bins push_pop_full = {3'b111};   // unhit → scenario never tried
  }
endgroup

The covergroup bin sits at zero hits, pointing a finger at exactly the stimulus gap. Code coverage had no vocabulary to even express the question.


How they combine at sign-off

The two metrics fail in opposite directions, which is why sign-off requires both. High functional coverage with low code coverage means your coverage model is too thin — RTL exists that no planned scenario touches (dead code, or a plan hole). High code coverage with low functional coverage means stimulus wandered everywhere but the planned scenarios never assembled. The quadrant chart is a standard interview whiteboard:

diagram
FUNCTIONAL COVERAGE
                     low            high
                 ┌──────────────┬──────────────────┐
  CODE     high  │ stimulus     │ sign-off          │
  COVERAGE       │ wanders;     │ candidate        │
                 │ scenarios    │ (review waivers,  │
                 │ never formed │  then close)      │
                 ├──────────────┼──────────────────┤
           low   │ early bring- │ coverage model    │
                 │ up; keep     │ too thin OR dead  │
                 │ testing      │ RTL — investigate │
                 └──────────────┴──────────────────┘

Interview framing

  1. Define both in one sentence each: code coverage = tool-measured structure execution; functional coverage = engineer-defined scenario occurrence.

  2. State the asymmetry: code coverage cannot see missing code or combinations; functional coverage cannot see anything outside its model.

  3. Give a concrete miss example (the FIFO push+pop+full case above) — interviewers want the example, not the definitions.

  4. Close with sign-off practice: both metrics at goal, holes either closed or formally waived with documented reasoning.

Key takeaways

  • Code coverage is automatic and structural; functional coverage is hand-written and scenario-based.

  • 100% code coverage cannot detect missing code or unexercised combinations of covered lines.

  • The two metrics fail in opposite directions — sign-off requires both at goal, plus reviewed waivers.

  • Low code coverage with high functional coverage signals a thin coverage model or dead RTL — always investigate.

Common pitfalls

  • Reporting 100% line coverage as “verification complete” — the classic junior-engineer trap.

  • Skipping code coverage because functional coverage exists — dead code and untouched RTL go unnoticed.

  • Waiving code-coverage holes in bulk without review — waivers are sign-off documents, not noise filters.

  • Building the functional model from the RTL instead of the spec — it inherits the designer's blind spots.