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).
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 + covergroupsWhy 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:
// 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
}
endgroupThe 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:
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
Define both in one sentence each: code coverage = tool-measured structure execution; functional coverage = engineer-defined scenario occurrence.
State the asymmetry: code coverage cannot see missing code or combinations; functional coverage cannot see anything outside its model.
Give a concrete miss example (the FIFO push+pop+full case above) — interviewers want the example, not the definitions.
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.