Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
    • Reusable Testbench Architecture
    • Checking Out-of-Order Traffic
    • Assertion Depth
    • Coverage Modelling for Real Features
    • Capstone: Out-of-Order Memory Controller
    • Principal Judgment
      • Escaped Bug Analysis
      • Intermittent Failures
      • Metrics That Lie
      • Scaling Verification
      • System Design Interviews
Loading lesson

‹Principal Judgment
  1. Topics
  2. SystemVerilog
  3. Principal-Level Verification
  4. Principal Judgment
  5. Metrics That Lie

Part 9 · Principal-Level Verification · Advanced

Metrics That Lie

Coverage at 100% with a live bug, flat bug curves and pass-rate traps, and how to ask the right question of each number.

On this page

    Every verification metric answers a narrower question than the one people ask of it. Coverage answers "did these bins get hit", not "is the design correct". A pass rate answers "did the checks fire", not "are the checks any good". A principal engineer knows what each number cannot tell you.

    Coverage that mirrors the wrong thing

    A coverage model is only as good as its source. If the bins are copied from the RTL structure or from the stimulus generator, they measure what the bench does, not what the spec requires. They reach 100% easily, and a bug can sit outside every bin.

    The bench below is an illustrative case. An 8-bit ALU has eight operations. The original test kept operands below 128 "to avoid overflow noise", and its coverage crosses each operation with the operand ranges the generator produces. That is a mirror of the generator.

    A second, feature-based model asks what the spec cares about: an add that carries out, a subtract that borrows, a left shift that loses a 1, a right shift of a value with bit 7 set. Verilator 5.048 ignores covergroups (it warns COVERIGN), so both models are hand-written bin arrays; the covergroup equivalent is shown after the bench.

    alu8.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package alu_pkg;
      typedef enum logic [2:0] {ADD, SUB, AND_, OR_, XOR_, SHL, SHR, PASSB} op_t;
    endpackage
    
    module alu8 import alu_pkg::*; (
      input  op_t        op,
      input  logic [7:0] a,
      input  logic [7:0] b,
      output logic [7:0] y,
      output logic       c      // carry, borrow, or the bit shifted out
    );
      always_comb begin
        c = 1'b0;
        case (op)
          ADD:   {c, y} = {1'b0, a} + {1'b0, b};
          SUB:   {c, y} = {1'b0, a} - {1'b0, b};
          AND_:  y = a & b;
          OR_:   y = a | b;
          XOR_:  y = a ^ b;
          SHL:   {c, y} = {a, 1'b0};
          SHR:   {y, c} = {1'b0, a};        // logical: zero enters at the top
          default: y = b;
        endcase
      end
    endmodule
    

    With the correct ALU, phase 1 ends with mirror coverage at 100% and feature coverage at 75%. Phase 2 closes the feature bins and the bench passes. Now plant a bug: make the right shift arithmetic, so bit 7 is copied instead of cleared.

    design.sv
    // snippet: the planted bug
    SHR: {y, c} = {a[7], a};   // should be {1'b0, a}

    The run with the bug (Verilator 5.048) prints phase 1: mirror coverage 100%, feature coverage 75%, wrong results 0, then 22 wrong results in phase 2, all on SHR with bit 7 set, and ends in FAIL. Phase 1 is the original regression: green, at 100% coverage, with a live bug. A second mutant, deleting phase 2, also fails: the "feature holes remain" check stops the bench from signing off at 75%.

    In a real environment the feature model is a covergroup. A sketch of the same bins:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // snippet: covergroup form of the feature model
    covergroup alu_features with function sample(op_t op, logic [7:0] a, logic [7:0] b);
      cp_op : coverpoint op;
      cp_corner : coverpoint {op, a[7]} {
        bins shl_msb = {{SHL, 1'b1}};
        bins shr_msb = {{SHR, 1'b1}};
      }
      cp_carry  : coverpoint (op == ADD && (9'(a) + 9'(b)) > 255) { bins hit = {1}; }
      cp_borrow : coverpoint (op == SUB && a < b)                 { bins hit = {1}; }
    endgroup

    Signs that coverage is a mirror

    • Bins sample RTL internals such as decoded state, instead of transactions seen at the interface.

    • Bin boundaries match constraint boundaries in the sequence code.

    • Coverage reached 100% in the first week and never had a hole worth discussing.

    • Nobody can point to the spec section behind a bin.

    • The same person wrote the RTL decode, the stimulus constraints and the coverage, from one reading of the spec.

    The cure is to write the coverage model from the spec and the verification plan, before looking at the RTL or the sequences. See From Spec to Coverage Plan.

    Flat bug curves

    A bug-discovery curve that goes flat is read as "the design is stable". It can also mean the bench has stopped exploring. Before reading a flat curve as maturity, check these:

    A flat curve can mean

    How to tell

    The design is clean in the explored space

    New stimulus (new sequences, wider constraints, new configurations) finds nothing either.

    The stimulus is saturated

    Coverage has not moved for weeks and new seeds hit the same bins.

    People stopped filing bugs

    Failures are triaged as "known" or fixed without a ticket. Count failing signatures, not tickets.

    The team moved on

    Fewer regression cycles or fewer engineers on the block. Normalise by effort.

    Checks were disabled

    Compare the list of enabled assertions and scoreboards with the list a month ago.

    A useful test is to plant a few bugs (mutation testing) and see whether the regression catches them. If it misses planted bugs, a flat curve says nothing.

    Pass-rate traps

    • 98% passing can hide one real bug behind 2% of failures that everybody calls "infrastructure". Group failures by first-error signature and look at each signature, not at the percentage.

    • 100% passing after a change that disabled a checker is worse than 95% with the checker on. Track the number of checks performed, not only the number that failed.

    • A test that cannot fail is common: an empty scoreboard queue at the end, a monitor that never saw a transaction, an assertion with a vacuous antecedent. Every test should report how many items it compared, and fail on zero.

    • Waived failures pile up. Each waiver needs an owner and an expiry, or the pass rate becomes a measure of how many waivers exist.

    Common pitfall

    Presenting coverage percentage as a sign-off number without saying which model it measures. "100% of a 24-bin generator mirror" and "100% of the plan's feature model" are different claims; write which one it is.

    Interview

    Question: "Coverage is 100% but a bug escaped. How is that possible?" Answer: the coverage model measured something other than the failing feature, usually because it was derived from the RTL or the stimulus instead of the spec; or the scenario was covered but no checker compared its result; or the bug needed a combination no cross included. Then say what you would change: spec-based coverage, checks counted as well as coverage, and mutation testing of the bench.

    Check yourself Why does phase 1 in the bench find no mismatch, even though the checker is correct?

    The checker only sees what the stimulus sends. With operands below 128, a right shift never has bit 7 set, so the arithmetic and logical shifts give the same result. The checker is right; the stimulus never reaches the bug.

    Check yourself The bug count has been zero for six weeks on a block. What two checks do you run before calling it stable?

    Check that coverage and stimulus are still moving (new seeds or configurations reach new bins), and plant a few bugs to confirm the regression still catches them. Also confirm that no checks were disabled and that failures are still being filed.

    Related topics

    • Previous: Intermittent Failures

    • Next: Scaling Verification

    • Sign-Off Metrics and Review

    • When Passing Is the Bug: Vacuity & Coverage

    Previous

    257 / 259

    Next: Scaling Verification

    Up next

    Up nextScaling Verification

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Escaped Bug Analysis
    • System Design Interviews