Skip to content

UVM

Coverage Issues

Applying the debugging methodology to functional coverage — why coverage under-reports (a hole that won't close because the covergroup isn't sampled, is sampled at the wrong time, has an unhittable bin, or covers the wrong variable) or over-reports (suspiciously complete coverage that measures the testbench's own stimulus rather than the design's behavior, or uses bins so coarse anything hits them), the over-report being the dangerous false-confidence case; how to observe the coverage report and the sampling points and fix each so coverage measures what was actually verified.

UVM Debugging · Module 27 · Page 27.8

The Engineering Problem

A coverage hole won't close — you throw more stimulus at it, more random seeds, directed tests, and it stays at zero. Or the opposite: coverage closes suspiciously fast100% on a key covergroup after barely any testing — and you sign off, confident. Both are coverage issues (from 27.1's family: a coverage hole that won't close, or suspiciously complete coverage), and they're two directions of the same deeper problem: coverage is a measurement, and a broken coverage model liesin either direction. It can under-report: a hole that won't close not because stimulus is missing but because the covergroup isn't sampled, is sampled at the wrong time, has a bin that can never be hit, or covers the wrong variable — so you chase stimulus that can never close it. Or it can over-report: suspiciously complete coverage that measures the testbench's own stimulus rather than the design's behavior, or uses bins so coarse anything hits them — so 100% means nothing. The over-report is the more dangerous — like the silent scoreboard (27.4) and the zero-time green (27.6), it produces false confidence: you believe you verified the design when you covered your own stimulus. The trap with the under-report is chasing stimulus for a hole that's a sampling or model bug, not a stimulus gap; the trap with the over-report is trusting a number that measures the wrong thing. The problem this chapter solves is coverage issues: applying the methodology to functional coveragereading the symptom (hole won't close vs suspiciously complete), observing the coverage report and the sampling points, and confirming and fixing each so coverage measures what was actually verified.

Coverage issues are failures of the coverage model as a measurementunder-reporting (a hole that won't close) or over-reporting (suspiciously complete) — that make coverage not measure what was actually verified. The under-report causes: the covergroup is never sampled (not constructed, or .sample() never called — 0% across the board, a sampling bug, not a stimulus gap); sampling at the wrong time (.sample() at the wrong instant — before the value is valid, after it changed, wrong edge — so it records stale or wrong values); a bin that can never be hit (defined for a value the design can't produce — an unhittable hole no stimulus can close); or coverage on the wrong variable (a coverpoint sampling a variable that isn't the design state you meant — so the hole won't close because you're covering the wrong thing). The over-report causes: measuring the testbench, not the design (the covergroup samples the value you drove rather than the DUT's behavior — so coverage measures what you stimulated, not what the design did, and closes trivially); or bins too coarse (bins so broad that any value hits themtrivially 100%, hiding the real holes). The crucial asymmetry: the under-report wastes effort (chasing stimulus for an unhittable/unsampled hole), but the over-report is dangerous — it produces false confidence (100% coverage that verified nothing). The methodology applied: the symptom is a hole that won't close or suspiciously complete coverage; localize to the coverage model; observe the coverage report (which bins are 0 or 100) and the sampling points (is it sampled? when? on what variable?); confirm whether it's a sampling/model bug (under) or measuring the wrong thing (over); and fix (construct and sample at the right time, remove unhittable bins, sample the design variable, refine coarse bins). This chapter is coverage issues: the two directions, the sampling points, the observation, and the fixes.

Why does a coverage hole refuse to close despite stimulus, or coverage close suspiciously fast — and how are these two directions of a broken coverage model (under-reporting from a sampling or model bug, over-reporting from measuring the testbench or coarse bins) found by observing the coverage report and the sampling points, with the over-report the dangerous false-confidence case?

Motivation — why coverage lies in two directions

Coverage is a measurement, and a measurement can be wrong by reading too low or too high — each with a different trap. The reasons:

  • The under-report sends you chasing stimulus that can't help. A hole that won't close looks like a stimulus gap ("I haven't hit this case"), so you write more tests and try more seeds — but if the covergroup isn't sampled, the bin is unhittable, or you're covering the wrong variable, no stimulus can close it. You burn effort on a measurement bug.
  • The over-report is false confidence — the worst failure. Suspiciously complete coverage looks like thorough verification, so you sign off — but if the coverage measures the testbench or uses coarse bins, 100% means nothing. You've verified less than you think and shipped on a false number.
  • Sampling is invisible in the coverage number. The coverage report shows percentages, but not whether the covergroup was sampled, when, or on what variable — so a sampling bug (the deepest cause of both directions) is invisible unless you look at the sampling points, not just the number.
  • "Measuring the testbench" closes trivially and means nothing. A covergroup that samples the value you drove hits all its bins as soon as constrained-random generates all values — which is fast and easy — so it closes quickly and feels like progress, while measuring the design's actual behavior (which it doesn't) would show the real holes.
  • Both directions corrupt the sign-off decision. Coverage drives sign-off ("are we done?"). An under-report says "not done" when you are (wasting effort); an over-report says "done" when you're not (shipping bugs). A wrong measurement makes the whole closure decision wrong.

The motivation, in one line: coverage is a measurement that drives sign-off, and it lies in two directionsunder-reporting (a hole that won't close from a sampling/model bug, sending you chasing stimulus that can't help) or over-reporting (suspiciously complete coverage that measures the testbench or coarse bins, giving false confidence) — and both are invisible in the percentage unless you observe the sampling points, so you must check what is sampled, when, and on what variable, not just the number.

Mental Model

Hold coverage as a fitness tracker that must measure your real activity — wrong if it's off, mis-timed, set an impossible goal, or counting the wrong motion:

Imagine you rely on a fitness tracker to tell you how much you've actually exercised. The number it shows drives your decisions — whether you've done enough, whether to keep going. For that number to mean anything, the tracker has to actually measure your real activity, and there are several ways it fails to. First, it might not be measuring at all — you forgot to wear it, or never started the session — so no matter how hard you exercise, it reads zero. You could run a marathon and the tracker says you did nothing. Second, it might measure at the wrong time — only sampling while you're sitting still — so it badly under-counts real effort. Third, it might have a goal you can never reach — a target for steps in a twenty-fifth hour of the day — and that goal will simply never complete, no matter what you do, because it's impossible by construction. Those three make the tracker read too low: you've done the work, but the number doesn't reflect it, and adding more exercise won't fix a tracker that isn't measuring you. But there's a fourth failure that's worse, because it reads too high. Suppose the tracker is counting the wrong motion entirely — it's picking up the car's movement while you sit in traffic, or the washing machine's vibration, instead of your steps. Now it happily reports you hit your goal, while you did no exercise at all. That's the dangerous one: a low reading makes you do unnecessary extra work, but a high reading from measuring the wrong thing makes you believe you're fit when you're not. The number looks great and means nothing. The tracker only tells you something true if it's actually on, sampling at the right time, with reachable goals, and measuring you and not the car. Imagine you rely on a fitness tracker to tell you how much you've actually exercised. The number drives your decisionswhether you've done enough. For that number to mean anything, the tracker must actually measure your real activity, and there are several ways it fails. First, it might not be measuring at allnot worn, never started — so however hard you exercise, it reads zero. Second, it might measure at the wrong timeonly while you sit still — so it under-counts. Third, it might have a goal you can never reachsteps in a twenty-fifth hour of the day — that never completes, impossible by construction. Those three make it read too low: you did the work, the number doesn't reflect it, and more exercise won't fix a tracker that isn't measuring you. But a fourth failure is worse, because it reads too high: the tracker is counting the wrong motionthe car's movement while you sit in traffic — so it reports you hit your goal while you did no exercise. That's the dangerous one: a low reading makes you do extra work, but a high reading from measuring the wrong thing makes you believe you're fit when you're not. The number looks great and means nothing. The tracker only tells you something true if it's on, sampling at the right time, with reachable goals, and measuring you and not the car.

So coverage is a fitness tracker of what was verified: the covergroup must measure the design's real behavior, and it fails in two directions. Reads too low (the hole that won't close): not sampled (tracker off — covergroup not constructed / .sample() never called); sampled at the wrong time (measuring while sitting — wrong instant); a bin that can never be hit (goal in the 25th hourimpossible by construction) — and more stimulus won't fix a measurement that isn't measuring. Reads too high (the dangerous over-report): counting the car, not your steps (measuring the testbench's stimulus, not the design's behavior100% achieved sitting in traffic); or a goal of "take 1 step" (bins too coarsetrivially met, meaningless). The discipline is the same: the number only means something if the tracker is on, well-timed, with reachable goals, measuring the design and not the testbench. Diagnose coverage by reading the report against the sampling points: a hole that won't close may be a tracker that's off, mis-timed, or chasing an impossible goal (not a stimulus gap), and suspiciously complete coverage may be counting the car instead of your steps (measuring the testbench, not the design) — check what is sampled, when, and on what. A coverage number means nothing until you confirm it's measuring the design.

Visual Explanation — the two directions of broken coverage

The defining picture is the two directions a coverage model can lie: under-reporting (a hole that won't close) and over-reporting (suspiciously complete).

Two directions of broken coverage: under-report and over-reportUnder: never sampled / wrong timecovergroup not constructed or .sample() never called (0% everywhere), or sampled at the wrong instant — a sampling bug, not a stimulus gapcovergroup not constructed or .sample() never called (0% everywhere), or sampled at the wrong instant — a sampling bug, not a stimulus gapUnder: unhittable bin / wrong variablea bin defined for a value the design can't produce, or a coverpoint on the wrong variable — no stimulus can close ita bin defined for a value the design can't produce, or a coverpoint on the wrong variable — no stimulus can close itOver: measures the testbench, not the designsamples the value you drove rather than the DUT's behavior — closes trivially, 100% means nothing (false confidence)samples the value you drove rather than the DUT's behavior — closes trivially, 100% means nothing (false confidence)Over: bins too coarsebins so broad that any value hits them — trivially 100%, hiding the real holesbins so broad that any value hits them — trivially 100%, hiding the real holes
Figure 1 — the two directions a broken coverage model lies. Under-reporting (reads too low, a hole that won't close): the covergroup is never sampled (0% across the board), or sampled at the wrong time (records stale or wrong values), or has a bin that can never be hit (unhittable by construction), or covers the wrong variable — and no stimulus can close a hole that is a sampling or model bug. Over-reporting (reads too high, suspiciously complete): the covergroup measures the testbench's own stimulus rather than the design's behavior (closes trivially, means nothing), or uses bins so coarse anything hits them (hides the real holes). The under-report wastes effort chasing stimulus, but the over-report is dangerous: it is false confidence, 100% coverage that verified nothing.

The figure shows the two directions a broken coverage model lies. Under: never sampled / wrong time (brand-colored): covergroup not constructed or .sample() never called (0% everywhere), or sampled at the wrong instant — a sampling bug, not a stimulus gap. Under: unhittable bin / wrong variable (default-colored): a bin for a value the design can't produce, or a coverpoint on the wrong variableno stimulus can close it. Over: measures the testbench, not the design (warning-colored — the dangerous one): samples the value you drove rather than the DUT's behaviorcloses trivially, 100% means nothing. Over: bins too coarse (warning-colored): bins so broad any value hits themtrivially 100%, hiding the real holes. The crucial reading is the symmetry and the asymmetry: the two directions are symmetric as measurement errors (too low, too high), but asymmetric in danger. The under-report (top two) is visible (a hole stares at you) and wasteful (you chase stimulus that can't help) — but it errs safe: it says "not done" when you might be, so you do more work, not ship a bug. The warning-colored over-report (bottom two) is invisible (a green 100% looks like success) and dangerous: it says "done" when you're not, so you sign off and ship. This is why the over-report is highlighted: a measurement that reads too high is worse than one that reads too low, because too-low costs effort while too-high costs escaped bugs. The common thread across all four is that coverage measures something other than the design's actual behaviornothing (not sampled), the wrong instant (wrong time), the impossible (unhittable), the wrong signal (wrong variable / the testbench), or too coarsely (coarse bins) — so the number doesn't reflect what the design did. The diagram is the coverage-lie map: under-report (won't close, wasteful) vs over-report (suspiciously complete, dangerous). A broken coverage model lies too low (a hole that won't close — a sampling or model bug, wasteful) or too high (suspiciously complete — measuring the testbench or coarse bins, dangerous false confidence).

RTL / Simulation Perspective — the causes and the sampling points

In code, each cause is a specific sampling or model defect, visible at the sampling point. The example shows the causes and the fixes.

coverage failures: the sampling points, the causes, and the fixes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === The covergroup — and WHAT it samples is everything ===
class my_cov extends uvm_subscriber #(my_txn);
  my_txn tr;
  covergroup cg;
    cp_state: coverpoint tr.state { bins s[] = {[0:3]}; }   // ← WHAT variable? design or testbench?
  endgroup
  function new(string name, uvm_component parent);
    super.new(name, parent); cg = new();                     // ✓ construct the covergroup
  endfunction
  function void write(my_txn t);                             // called per observed transaction
    tr = t; cg.sample();                                     // ✓ sample at the right time, on observed data
  endfunction
endclass
 
// ✗ UNDER 1 — never sampled: cg = new() missing, or write()/sample() never called → 0% everywhere
// ✗ UNDER 2 — wrong time: cg.sample() before tr is valid, or on the wrong edge → records stale/X values
// ✗ UNDER 3 — unhittable bin: bins illegal = {4,5};  // design can never produce 4 or 5 → bin stays 0 forever
// ✗ UNDER 4 — wrong variable: coverpoint tr.local_copy;  // not the design state you meant → hole won't close
 
// ✗ OVER 1 — measures the TESTBENCH: sample the value the SEQUENCE drove, not the DUT's behavior
class bad_cov;
  covergroup cg_stim;
    cp: coverpoint seq.driven_value;   // ✗ the stimulus you generated — constrained-random hits all bins
  endgroup                              //   → 100% trivially, measures "did I drive it", NOT "did the DUT do it" (DebugLab)
endclass
// ✓ FIX: sample the DUT's OBSERVED behavior from the monitor, not the driven stimulus
 
// ✗ OVER 2 — bins too coarse: coverpoint tr.addr { bins all = {[0:$]}; }  // one bin → any addr hits → trivial 100%
// ✓ FIX: meaningful bins that distinguish the cases you care about
 
// === OBSERVE: read the coverage REPORT and the SAMPLING POINTS together ===
//   coverage report: which coverpoints/bins are 0 (holes) or 100 (suspiciously complete)
//   sampling points: is cg constructed? is sample() called, when, and on WHAT variable (design vs testbench)?

The code shows the causes and the sampling points. The covergroup cg samples tr.state per observed transaction (writecg.sample()), constructed in new — and what it samples is everything. Under 1 (never sampled): cg = new() missing, or write/sample never called0% everywhere. Under 2 (wrong time): sample() before tr is valid or on the wrong edgestale/X values. Under 3 (unhittable bin): bins illegal = {4,5} for values the design can't producestays 0 forever. Under 4 (wrong variable): coverpoint tr.local_copy instead of the design statehole won't close. Over 1 (measures the testbench): coverpoint seq.driven_value — the stimulus you generatedconstrained-random hits all binstrivial 100%, measuring "did I drive it" not "did the DUT do it" (the DebugLab); fix: sample the DUT's observed behavior from the monitor. Over 2 (coarse bins): bins all = {[0:$]}one binany addr hitstrivial 100%; fix: meaningful bins. Observe: read the coverage report (which bins are 0 or 100) and the sampling points (is cg constructed? is sample() called, when, and on what variable — design vs testbench?). The shape to carry: the covergroup's value and timing are the whole storywhat it samples (design vs testbench, right vs wrong variable), when (right vs wrong instant), whether at all (constructed/sampled), and how finely (bins) — and both the report and the sampling points must be read together, because the report's number is meaningless without knowing what produced it. Read the coverage report against the sampling points — what is sampled, when, and on what variable — because the percentage is meaningless without knowing what produced it, and the most important check is that you sample the design's behavior, not the driven stimulus.

Verification Perspective — measuring the testbench is 100% that means nothing

The defining danger is the over-report from measuring the testbench100% coverage that verified nothing. Seeing how it produces false confidence clarifies why coverage must measure the design.

Measuring the testbench: 100% that means nothingcovergroup samples the driven valuecovergroupsamples the…constrained-randomhits all binsdesign behavior never sampleddesignbehavior neve…100% — but on the wrong thing100% — but onthe wrong…real holes invisibleStimulus (driven value)what you generatedCovergroup on thestimulusmeasures the testbench100% triviallyall input values hitDUT behaviorstates/responses producedNever sampledreal coverage unmeasuredFalse confidence100% verified nothing12
Figure 2 — measuring the testbench produces 100% that means nothing. A covergroup that samples the value the sequence drove measures the stimulus you generated, not the design's behavior. Constrained-random generates all the input values quickly, so this covergroup hits all its bins and reads 100% — fast and easy. But the design's actual behaviors — the states and responses the DUT produced — were never sampled, so whether the DUT exercised all of them is unmeasured. A bug in an unexercised DUT behavior ships, despite 100% coverage, because the coverage measured the inputs you drove, not the design you were supposed to verify. The tell is coverage that closes suspiciously easily on a covergroup pointed at a testbench variable rather than a monitored design signal.

The figure shows measuring the testbench producing 100% that means nothing. A covergroup that samples the value the sequence drove measures the stimulus you generated, not the design's behavior. Constrained-random generates all the input values quickly, so this covergroup hits all its bins and reads 100%fast and easy. But the design's actual behaviors — the states and responses the DUT produced — were never sampled, so whether the DUT exercised all of them is unmeasured. The verification insight is the disconnect between what closed and what was verified: the covergroup closed (100% on the driven values), but closing it required only that the testbench generate all inputs — which the constrained-random solver does triviallynot that the DUT exhibit all behaviors. So the 100% reflects the testbench's generator, not the design's coverage. A bug in an unexercised DUT behavior ships, despite 100% coverage, because the coverage measured the inputs you drove, not the design you were supposed to verify. This is the same false-confidence pattern as the silent scoreboard (27.4, checks nothing, passes green) and the zero-time green (27.6, runs nothing, passes green): a measurement that looks complete while measuring the wrong thing — here, 100% coverage that measured the testbench. The warning covergroup-on-stimulus → 100% path and the brand DUT-behavior → never-sampled path converge on false confidence. The crucial tell is what the covergroup points at: a coverpoint on a sequence's driven_value (a testbench variable) versus a coverpoint on a monitored DUT signal (the design's behavior) — and coverage that closes suspiciously easily is a red flag that it's measuring the generator, not the design. The crucial discipline is that coverage must sample the design's observed behavior, from the monitor, not the stimulus you drove — and closing fast should raise suspicion, not confidence. The diagram is the false-100% mechanism: covergroup on the driven stimulus → trivially 100% → design behavior never sampled → false confidence. A covergroup that samples the driven stimulus hits 100% trivially while the design's behavior goes unmeasured — coverage must measure the design's observed behavior, not the testbench's generated stimulus, and coverage that closes suspiciously easily is measuring the wrong thing.

Runtime / Execution Flow — diagnosing a coverage issue

At run time, diagnosing a coverage issue follows the methodology, branching on won't-close versus suspiciously-complete and reading the report against the sampling points. The flow shows it.

Diagnosing a coverage issue by directionread the symptom (won't close / suspiciously complete) → won't close: check sampled-at-all + timing + bin-hittable + right-variable → suspiciously complete: check design-vs-testbench variable + bin granularity → confirm report vs sampling points → fixread the symptom (won't close / suspiciously complete) → won't close: check sampled-at-all + timing + bin-hittable + right-variable → suspiciously complete: check design-vs-testbench variable + bin granularity → confirm report vs sampling points → fix1Read the symptoma hole that won't close despite stimulus, or coverage that closedsuspiciously fast.2Won't close → check the samplingsampled at all (0% = not sampled), right time, bin hittable, rightvariable.3Suspiciously complete → check what is sampleddesign behavior vs driven stimulus, and meaningful bins vs toocoarse.4Confirm report vs sampling points → fixsample the design variable at the right time, remove unhittablebins, refine coarse bins.
Figure 3 — diagnosing a coverage issue step by step. Read the symptom: a hole that won't close despite stimulus, or coverage that closed suspiciously fast. For a hole that won't close, observe the sampling points: is the covergroup constructed and sampled at all (0% everywhere means not sampled), is it sampled at the right time, is the bin hittable by the design, and is it sampling the right variable. For suspiciously complete coverage, observe what is sampled: is the covergroup measuring the design's observed behavior or the testbench's driven stimulus, and are the bins meaningful or so coarse anything hits them. Confirm against the coverage report and the sampling points together, then fix: sample at the right time on the design variable, remove unhittable bins, and refine coarse bins.

The flow shows the direction-driven diagnostic. Read the symptom (step 1): a hole that won't close despite stimulus, or coverage that closed suspiciously fast. Won't close → check the sampling (step 2): sampled at all (0% = not sampled), right time, bin hittable, right variable. Suspiciously complete → check what is sampled (step 3): design behavior vs driven stimulus, and meaningful bins vs too coarse. Confirm → fix (step 4): sample the design variable at the right time, remove unhittable bins, refine coarse bins. The runtime insight is that both directions require reading the coverage report together with the sampling points — the report alone is insufficient (it shows the percentage but not what produced it). For the won't-close direction, the report shows which bin is 0, but the sampling points reveal whether the covergroup is even sampled (a 0% across the entire covergroup is a strong sign of "not sampled", not a stimulus gap), when it samples, and on what variable. For the suspiciously-complete direction, the report shows 100%, but only the sampling points reveal the crucial factis it sampling the design or the testbench? — which determines whether the 100% means anything. So the diagnostic is always "read the number, then read what produced it." The crucial heuristic in step 1 is to be suspicious of fast closure: coverage that closes easily is as much a red flag as a hole that won't closeboth are symptoms of a broken model (the over and the under), and the fast-closing one is the more dangerous precisely because it doesn't look like a problem. The brand (symptom) → success (under-check) / warning (over-check) → warning (fix) flow shows the two branches and the shared confirmation. The flow is the coverage diagnostic: symptom direction → check the sampling points → confirm against the report → fix. Diagnose a coverage issue by reading the report with the sampling points — a hole that won't close needs the covergroup checked for being sampled, well-timed, hittable, and on the right variable; suspiciously complete coverage needs checking whether it samples the design or the testbench — and fast closure is as suspect as a stuck hole.

Waveform Perspective — coverage tracking the wrong signal

The over-report is visible: the covergroup samples the driven stimulus (which reaches all values), while the DUT's actual state (a different signal) never reaches some states. The waveform shows coverage tracking the wrong signal.

Coverage tracks the driven stimulus (all values hit) while the DUT's actual state never reaches some states

10 cycles
Coverage tracks the driven stimulus (all values hit) while the DUT's actual state never reaches some statesthe covergroup samples drive_val (the driven stimulus) every cycle (cg_sample)the covergroup samples…drive_val reaches all values 0,1,2,3 → the covergroup hits every bin → 100%drive_val reaches all …but dut_state only ever reaches 0,1 — states 2,3 are never producedbut dut_state only eve…states 2,3 are real holes the covergroup can't see — it samples the wrong signalstates 2,3 are real ho…clkdrive_val0123012301dut_state0101010101cg_samplet0t1t2t3t4t5t6t7t8t9
Figure 4 — coverage tracking the wrong signal. The covergroup samples the driven stimulus value, which the sequence cycles through all four values (0,1,2,3), so the covergroup hits every bin and reads 100 percent. But the DUT's actual state only ever reaches 0 and 1 — states 2 and 3 are never produced by the design under this stimulus. Those are real coverage holes, but the covergroup does not see them, because it is sampling the driven stimulus, not the DUT state. The covergroup reports complete while the design has uncovered states. The signature is coverage that closes on a stimulus signal whose values are fully exercised, while a monitored design signal still sits in a subset of its states.

The waveform shows coverage tracking the wrong signal. The covergroup samples the driven stimulus value (drive_val), which the sequence cycles through all four values (0,1,2,3), so the covergroup hits every bin and reads 100%. But the DUT's actual state (dut_state) only ever reaches 0 and 1states 2 and 3 are never produced by the design under this stimulus. Those are real coverage holes, but the covergroup does not see them, because it's sampling the driven stimulus, not the DUT state. The crucial reading is the divergence between the two signals: drive_val spans all four values (so the covergroup, watching it, reads 100%), while dut_state spans only two (so the design has uncovered states). The covergroup's 100% reflects drive_val, which is not the thing you wanted to cover — you wanted to cover dut_state (the design's behavior), and that has holes (2,3) the covergroup is blind to. So the 100% is true about the wrong signal and false about the design. The picture to carry is that coverage is only as meaningful as the signal it samples: watching drive_val tells you "the testbench drove all values" (which the generator does easily); watching dut_state would tell you "the design reached all states" (which it didn't) — and only the latter is verification coverage. Reading the waveform this way — which signal is the covergroup sampling, and does that signal's full range mean the design was covered?exposes the over-report: the covergroup's full range on drive_val hides the design's holes on dut_state. The covergroup reaching 100% on a stimulus signal while a monitored design signal sits in a subset of its states is the waveform signature of *coverage measuring the testbench, not the design. Coverage that reads 100% on the driven stimulus while the DUT's actual state never reaches some states is measuring the wrong signal — the covergroup must sample the design's behavior, because a stimulus signal's full range hides the design's real holes.

DebugLab — the 100% coverage that measured the testbench, not the design

A covergroup that reached 100% by measuring the driven stimulus, hiding uncovered design behavior

Symptom

A team was verifying a packet-processing block with several modes of operation. They had a covergroup on the mode, and it closed beautifully100%, quickly, across all modes. The coverage dashboard was green, the mode covergroup at 100%, and on the strength of that the team signed off the mode handling as thoroughly covered. Later, a bug escaped in one of the modes — a corner of the block's behavior in that mode that had never actually been exercised in a way that mattered, despite the covergroup showing that mode at 100%. Investigating how a 100%-covered mode could harbor an unexercised behavior, the team looked at what the covergroup actually sampled. The coverpoint was on seq.mode — the mode field the sequence randomized and drovenot on the mode the DUT was actually operating in, as observed by the monitor. The covergroup had been measuring "did the sequence generate all mode values?" — which the constrained-random solver achieved in the first handful of transactionsnot "did the DUT exercise all modes' behavior?". The 100% meant the testbench drove every mode, not that the design did every mode's work — and so whole swaths of in-mode behavior sat uncovered and unverified, under a green 100%.

Root cause

The covergroup sampled the testbench's driven stimulus (seq.mode, the value the sequence generated) rather than the design's observed behavior (the DUT's actual mode from the monitor) — so the coverage measured what was generated, not what the design did, closed trivially as constrained-random hit all input values, and reported 100% while the design's behavior went unmeasured:

why a 100% covergroup measured the testbench and hid uncovered design behavior
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ COVERGROUP ON THE DRIVEN STIMULUS — measures the testbench, not the design:
  covergroup cg_mode;
    cp_mode: coverpoint seq.mode;   // ✗ the mode the SEQUENCE randomized and drove
  endgroup
  // constrained-random generates all mode values in the first few transactions → cg_mode = 100% FAST
  // → measures "did I drive every mode?" (trivially yes), NOT "did the DUT exercise every mode?"
  // → in-mode design behavior never sampled → a bug in one mode ships under a green 100% (DebugLab)
 
✓ COVERGROUP ON THE DESIGN'S OBSERVED BEHAVIOR — measures the design:
  class mode_cov extends uvm_subscriber #(mode_txn);   // fed from the MONITOR
    covergroup cg_mode;
      cp_mode: coverpoint observed.dut_mode;           // ✓ the DUT's actual operating mode, observed
      // (+ cross with the in-mode behaviors that must be exercised per mode)
    endgroup
    function void write(mode_txn t); observed = t; cg_mode.sample(); endfunction
  endclass
  // → now 100% means the DUT actually exercised every mode (and behavior), not just that you drove them

This is the measuring-the-testbench bug — the most dangerous coverage failure, and a false-confidence trap like the silent scoreboard (27.4) and the zero-time green (27.6). The covergroup's coverpoint was on seq.mode — the mode the sequence randomized and drovenot on the DUT's actual mode, as observed by the monitor. So the coverage measured "did the testbench generate all mode values?" — which the constrained-random solver achieves trivially, in the first handful of transactionsnot "did the DUT exercise all modes' behavior?". The covergroup closed to 100% fast and easily, and that ease was itself the tell — but fast closure read as success, not suspicion, so the team signed off. The 100% reflected the generator, not the design: whole swaths of in-mode behavior were never sampled (because the covergroup wasn't looking at the DUT) and never verified, and a bug in one mode escaped under the green 100%. The deep error is that coverage must measure the design's behavior, not the testbench's stimulus — a covergroup on a testbench variable (the driven value) measures what you generated, which says nothing about what the DUT did. The fix is to sample the design's observed behavior, from the monitor: a coverpoint on the DUT's actual mode (observed.dut_mode), crossed with the in-mode behaviors that must be exercised per mode — so 100% means the DUT actually exercised every mode and behavior, not just that you drove them. The general lesson, and the chapter's thesis: coverage must measure the design's observed behavior, not the testbench's driven stimulus — a covergroup on the value you drove closes trivially (constrained-random hits all inputs fast) and reports 100% that means nothing, hiding the design's real holes; sample from the monitor, on the DUT's behavior, and treat suspiciously fast closure as a red flag, because 100% coverage on the stimulus you generated verifies your generator, not your design — and a green 100% that measured the testbench ships bugs in the behavior it never sampled. Coverage that closes easily on a testbench variable is measuring the wrong thing — sample the design's behavior from the monitor, and be suspicious of fast 100%.

Diagnosis

The tell is coverage that closes suspiciously fast, or a bug escaping in a behavior the coverage reported as complete. Diagnose measuring the testbench:

  1. Check what variable the coverpoint samples. A coverpoint on a sequence or driver variable (the driven value) measures the testbench; a coverpoint on a monitored signal measures the design.
  2. Be suspicious of fast 100%. Coverage that closes easily, especially in the first few transactions, often measures the generated stimulus, which constrained-random hits trivially.
  3. Trace the covergroup's feed. A covergroup fed from the sequence or config measures stimulus; one fed from the monitor's observed transactions measures behavior.
  4. Map coverage to escaped bugs. A bug in a behavior the coverage reported complete means the coverage wasn't measuring that behavior.
Prevention

Measure the design, not the testbench:

  1. Sample from the monitor, on observed DUT behavior. Feed covergroups from monitored transactions and sample the DUT's actual states and responses, not the driven stimulus.
  2. Treat fast closure as a red flag. Suspiciously easy 100% warrants checking what is being sampled, not celebrating.
  3. Cover behavior, not just inputs. Cross input values with the DUT's resulting behaviors so coverage reflects what the design did, not just what you drove.
  4. Read the report with the sampling points. Always confirm what each coverpoint samples and when, because the percentage is meaningless without knowing what produced it.

The one-sentence lesson: coverage must measure the design's observed behavior, not the testbench's driven stimulus, because a covergroup on the value you generated closes trivially as constrained-random hits all inputs and reports 100% that means nothing while the design's real behavior goes unsampled — so sample from the monitor on the DUT's behavior, treat suspiciously fast closure as a red flag, and read the coverage report against the sampling points.

Common Mistakes

  • Sampling the driven stimulus instead of the design's behavior. A covergroup on the value you drove closes trivially and measures the testbench; sample the DUT's observed behavior from the monitor.
  • Trusting fast 100% coverage. Suspiciously easy closure often means measuring the wrong thing; treat it as a red flag, not success.
  • Chasing stimulus for a hole that's a sampling bug. A covergroup never sampled, sampled at the wrong time, or with an unhittable bin won't close no matter the stimulus; check the sampling points first.
  • Bins so coarse anything hits them. A single broad bin reads trivially 100% and hides the real holes; define meaningful bins that distinguish the cases you care about.
  • Reading the percentage without the sampling points. The number is meaningless without knowing what is sampled, when, and on what variable; read the report and the sampling points together.
  • Unhittable bins counted as real holes. A bin for a value the design can't produce will never close; remove it or mark it illegal rather than chasing it.

Senior Design Review Notes

Interview Insights

A broken coverage model can under-report or over-report, and the over-report is far more dangerous. Coverage is a measurement of what was verified, and like any measurement it can read too low or too high. Under-reporting is a hole that won't close: you throw stimulus at it and it stays at zero, but not because stimulus is missing — because of a sampling or model bug. The covergroup might never be sampled, so it reads zero everywhere no matter what you drive; it might be sampled at the wrong time, recording stale or wrong values; it might have a bin defined for a value the design can never produce, an unhittable bin; or it might be covering the wrong variable. In all of these, no amount of stimulus closes the hole, because the measurement isn't measuring the thing you think. Over-reporting is suspiciously complete coverage: it closes fast and easy, but it measures the wrong thing. The classic case is a covergroup that samples the value you drove rather than the design's behavior, so it measures whether the testbench generated all inputs — which constrained-random does trivially — not whether the DUT exercised all behaviors. Or bins so coarse that any value hits them, so it reads 100% while distinguishing nothing. The reason the over-report is more dangerous is the same reason a fuel gauge reading full when empty is worse than one reading empty when full. The under-report errs safe: it says not done when you might be done, so it costs you wasted effort chasing a hole, but it doesn't ship a bug. The over-report says done when you're not, so you sign off on false confidence and ship the bugs in the behavior you never actually covered. It's the same false-confidence pattern as a scoreboard that checks nothing passing green, or a test that runs no stimulus passing green — a measurement that looks complete while measuring the wrong thing. And the over-report is insidious because it doesn't look like a problem: a green 100% reads as success, so nobody investigates. That's why fast, easy coverage closure should raise suspicion rather than confidence. The common thread across both directions is that coverage is measuring something other than the design's actual behavior — nothing, the wrong instant, the impossible, the wrong signal, or too coarsely — so you diagnose both by reading the report together with the sampling points, asking what is sampled, when, and on what.

Because coverage is supposed to tell you what the design did, and a covergroup that samples the stimulus you drove tells you only what you generated, which constrained-random achieves trivially and which says nothing about whether the DUT exercised all its behaviors. Think about what you want coverage to answer: did the design reach all its states, exercise all its modes, hit all the corner behaviors you care about. That's a question about the design's behavior, observed at the DUT. Now suppose you put a coverpoint on the value the sequence randomized and drove — say the mode the sequence selected. The constrained-random solver generates all the mode values in the first handful of transactions, so that covergroup hits all its bins and reads 100% almost immediately. But that 100% means did the testbench drive every mode, which is trivially yes, not did the DUT exercise every mode's behavior, which is the actual question. The design might process most transactions in one mode, might have whole in-mode behaviors never triggered, might have states never reached — and the covergroup, looking at the driven stimulus, is blind to all of it. So you get a green 100% on the mode covergroup while modes' actual behavior is unverified, and a bug in one mode ships under that green number. The fix is to sample the design's observed behavior, fed from the monitor: a coverpoint on the DUT's actual operating mode as observed, crossed with the in-mode behaviors that must be exercised per mode, so 100% means the DUT genuinely exercised every mode and behavior. The deeper principle is that coverage and stimulus are different things: stimulus is what you generate and drive, coverage is what you measure the design doing in response. If your coverage samples your stimulus, you've collapsed them — you're measuring your own generator, which is both easy to max out and irrelevant to verification. A useful heuristic falls out of this: be suspicious of coverage that closes fast and easily, because real design-behavior coverage usually takes effort to close, while stimulus coverage closes trivially. So when coverage closes suspiciously easily, the first thing to check is whether the covergroup is fed from the monitor and samples the DUT, or fed from the sequence and samples what you drove. Coverage only measures verification if it measures the design.

You diagnose a stubborn coverage hole by checking the sampling points before writing any more stimulus, because a hole that won't close is often a sampling or model bug, not a stimulus gap, and no stimulus can close a measurement that isn't measuring. The instinct when a bin stays at zero is to assume you haven't hit that case yet and to write more tests and try more seeds. But first confirm the covergroup is actually capable of closing, by checking four things. One: is the covergroup sampled at all? If the whole covergroup reads zero across every bin, not just one, that's a strong sign it was never constructed or sample is never called — a sampling bug, and no stimulus will help until you fix it. Check that the covergroup is newed and that sample is invoked, typically from the monitor on each observed transaction. Two: is it sampled at the right time? If sample is called before the value is valid or stable, or on the wrong edge, it records stale, wrong, or X values, so the real values you're driving never get recorded. Check when sample fires relative to when the value is valid. Three: is the bin hittable? A bin defined for a value the design can never produce — out of range, illegal by construction, or an impossible cross combination — will never close no matter what, because the design can't generate it. If the bin is genuinely unreachable, remove it or mark it illegal rather than chasing it; if it should be reachable, that's a different question about the design or constraints. Four: is it sampling the right variable? A coverpoint on a local copy, a stale signal, or the wrong field measures something other than the design state you meant, so the hole reflects the wrong thing. Confirm the coverpoint samples the actual design signal you intend to cover. You do all of this by reading the coverage report together with the sampling points — the report tells you which bin is zero, the sampling points tell you whether the covergroup can close it at all. Only after confirming the covergroup is sampled, well-timed, hittable, and on the right variable should you conclude it's a real stimulus gap and write directed tests. Otherwise you can burn enormous effort writing stimulus for a hole that a sampling fix would close instantly, or that nothing can close because it's unhittable.

Fast, easy coverage closure is a red flag because real coverage of a design's behavior usually takes deliberate effort to close, so coverage that maxes out quickly often means it's measuring something trivial — like the stimulus you generate or bins so coarse anything hits them — rather than the design's actual behavior. Consider what it takes to genuinely cover a design. You need the DUT to reach all its states, exercise all its modes, hit corner combinations and cross terms — and many of those require specific, sometimes hard-to-generate scenarios, so closing real functional coverage is typically a sustained effort involving directed tests, constraint tuning, and many cycles. So when a covergroup hits 100% almost immediately, in the first few transactions, that's anomalous for design-behavior coverage and characteristic of something else. The most common something else is measuring the testbench: a covergroup on the value the sequence drove closes as soon as constrained-random generates all the input values, which it does trivially, because generating all values is exactly what the solver is good at. Another is coarse bins: if a coverpoint has one broad bin covering the whole range, any value hits it, so it's 100% after one transaction and distinguishes nothing. In both cases the coverage closed fast because it wasn't measuring anything hard — it was measuring the generator or measuring almost nothing. So fast closure should trigger investigation, not celebration: you look at what the covergroup samples — design behavior from the monitor, or driven stimulus from the sequence — and at whether the bins meaningfully partition the values that matter. The deeper reason this heuristic is valuable is that the over-report is the dangerous failure, the one that ships bugs under a green number, and it's invisible by nature because 100% looks like success. Fast closure is one of the few external tells that something might be wrong with a high coverage number, so it's worth being trained to react to. The mirror-image heuristic is that a hole that won't close is also suspect — possibly a sampling bug rather than a stimulus gap. Between them, the discipline is to be suspicious of coverage that behaves at either extreme, closing too easily or refusing to close, and to confirm by reading the report against the sampling points rather than trusting the percentage.

An unhittable bin is a bin defined for a value or combination the design can never produce, so it will never close no matter what stimulus you apply, whereas a real coverage hole is a reachable case the design can exhibit but that your stimulus hasn't yet exercised — and the distinction matters because you chase a real hole with stimulus but you remove or reclassify an unhittable bin. The reason this matters is that both show up identically in the coverage report: a bin at zero. The report doesn't tell you whether that zero is because you haven't hit the case yet or because the case is impossible. If you treat an unhittable bin as a real hole, you write directed tests and try seeds endlessly trying to close something that cannot be closed, because the design physically cannot generate that value — wasted effort with no possible payoff. Unhittable bins arise a few ways. A coverpoint with bins spanning a full range when the design only uses part of it — the unused values are unreachable. A bin for an illegal-by-design value, like a reserved encoding the design never emits. A cross bin for a combination that can't co-occur — two states that are mutually exclusive, so their cross is impossible. In all of these the bin is zero forever, not because verification is incomplete but because the bin describes something outside the design's behavior. The fix is not stimulus — it's to fix the model: remove the bin, or define the illegal values as illegal_bins or ignore_bins so they're excluded from the coverage goal rather than counted as holes. A real hole, by contrast, is a case the design can reach but your tests haven't driven it to — and that you close by writing the stimulus that reaches it. So when a bin won't close, the diagnostic question is whether the design can produce that value at all: check the spec and the design to see if the value or combination is reachable. If it's reachable, it's a real hole — write stimulus. If it's unreachable, it's an unhittable bin — fix the model by removing or reclassifying it. Getting this wrong in either direction is costly: chasing unhittable bins wastes effort, and wrongly excluding a reachable case as unhittable hides a real hole. So you reconcile the bin against what the design can actually do.

Exercises

  1. Classify the failure. For each — coverage at 0% across a whole covergroup; a single bin stuck at 0; a covergroup at 100% after three transactions — name the likely cause and what to check.
  2. Point it at the design. Given a covergroup sampling seq.mode, rewrite it to sample the DUT's observed mode from the monitor and explain what changes about the meaning of 100%.
  3. Hittable or not. Given a bin stuck at zero, describe how you'd determine whether it's a real hole or an unhittable bin, and the different fix for each.
  4. Refine the bins. Given a coverpoint with one broad bin reading 100%, propose meaningful bins and explain what holes they would reveal.

Summary

  • Coverage issues are failures of the coverage model as a measurement — it lies in two directions: under-reporting (a hole that won't close) or over-reporting (suspiciously complete) — so coverage doesn't measure what was actually verified.
  • The under-report causes (a hole that won't close from a measurement bug, not a stimulus gap): covergroup never sampled (0% everywhere), sampled at the wrong time, an unhittable bin, or coverage on the wrong variableno stimulus can close them.
  • The over-report causes (suspiciously complete): measuring the testbench's driven stimulus rather than the design's behavior (closes trivially, 100% means nothing), or bins too coarse (any value hits them).
  • The crucial asymmetry: the under-report wastes effort (chasing stimulus), but the over-report is dangerousfalse confidence (100% that verified nothing), like the silent scoreboard and zero-time green; coverage must measure the design's behavior, not the testbench's stimulus, and fast closure is a red flag.
  • The durable rule of thumb: coverage is a measurement that lies in two directions — too low (a hole that won't close because the covergroup isn't sampled, is mis-timed, has an unhittable bin, or covers the wrong variable, so stimulus can't help) or too high (suspiciously complete because it samples the testbench's driven stimulus rather than the design's behavior, or uses coarse bins) — so read the coverage report together with the sampling points (is it sampled, when, and on what variable?), sample the design's observed behavior from the monitor not the stimulus you drove, treat fast 100% as a red flag, and remember the over-report is the dangerous one because 100% that measured the wrong thing ships bugs.

Next — Environment Bring-Up Debug: the module closes on the hardest debugging context — a brand-new environment that doesn't work yet, where many of these failures coexist. How to bring up a UVM environment from nothing systematically: establishing signs of life layer by layer (clock and reset, interface connection, a single transaction end to end, then checking and coverage), isolating the first failure before the next, and turning a silent, non-working testbench into one that drives, checks, and measures — so the methodology and the specific failure modes come together into a bring-up discipline.