Skip to content
VLSI Mentor

I²C · Module 22

A Functional Coverage Model for I²C, and the Crosses Worth Writing

What coverage answers that neither assertions nor mutation can, why six mutations survived a run with every relevant line executed, a cover form this toolchain accepts that can never be hit, and the test for whether a cross earns its place.

Coverage answers one question: did this situation ever occur? Not whether anything checked it, and not whether the design handled it. Keeping that boundary sharp is most of what makes a coverage model useful rather than decorative.

1. What Coverage Is For, Stated Against What It Is Not

Three mechanisms, three different questions:

mechanismquestionblind to
assertionwas this waveform illegal?anything legal-but-wrong
scoreboarddid the result match the contract?anything the monitor reconstructed away
coveragedid this situation arise at all?whether anything checked it

The third is the one most often over-read. A covered bin means stimulus reached a situation. It says nothing about whether a checker was watching.

2. The Cover Points

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_props.vhd — four bins, each answering a question nothing else asks
      -- psl COV_WRITE_THEN_READ : cover {start_now = '1'; active = '1'[+]; start_now = '1'};
      -- psl COV_STRETCH        : cover {scl_holders > 1};
      -- psl COV_NACK_SLOT      : cover {bitcnt = 8 and sda = '1'};
      -- psl COV_ACK_SLOT       : cover {bitcnt = 8 and sda = '0'};

Each is chosen because its absence would be invisible otherwise:

COV_WRITE_THEN_READ — two STARTs with the bus never released between them. This is the access pattern every register device actually uses, and a run that never produces it has not tested the state that must survive a repeated START. A zero here is the single most informative number in the report.

COV_STRETCH — the bus was stretched at least once. Module 20 found clock stretching present in three places, correct, and never executed; a cover point is what makes that state visible rather than assumed.

COV_ACK_SLOT / COV_NACK_SLOT — both acknowledge outcomes occurred. A run containing only acknowledges has exercised half the addressing logic, and the two bins together are what distinguish "the target always answers" from "the target was only ever asked things it answers".

3. A Cover Form That Can Never Be Hit

COV_WRITE_THEN_READ originally reported zero hits on a run that unmistakably contained the pattern.

The general form of the trap: an uncoverable bin and an unreached bin look identical. Both read zero. The only defence is the same one the properties needed — check that each cover point can be hit, by constructing a run that should hit it and confirming the count moves.

4. Which Crosses Are Worth Writing

A cross multiplies bins, and most products are noise. The test for whether one earns its place:

Does an empty cell in this cross correspond to a situation somebody would have to write new stimulus for?

If the cell is unreachable, the cross is reporting a permanent hole. If it is reachable but uninteresting, the cross is manufacturing work.

crossworth it?why
direction × how the transfer endedyesthe empty cell is "a read that began with a repeated START" — the commonest real access
direction × address matchedyesseparates "never addressed us for a read" from "read worked"
byte count × directionmarginala long read and a long write exercise different datapaths, but the bins overlap heavily
address × byte countno128 × 8 cells, nearly all meaningless
address bit k × directionnoindividual address bits have no independent behaviour

The one this module keeps is the first. Chapter 20.8's mutation P05 — a pointer cleared by framing — survived because every test set the pointer to zero first, where cleared and not-cleared are indistinguishable. A cross that flags "read after repeated START, never exercised" points at exactly that gap.

A block diagram with three question boxes across the top — was the waveform illegal, did the result match the contract, did the situation arise — feeding respectively an assertion block, a scoreboard block and a coverage block. Below them three defect-class boxes show which mechanism catches which: illegal traffic to the assertion, wrong data to the scoreboard, and an unexercised case to coverage, with an arrow showing coverage reporting a gap back to the stimulus.Was the waveformillegal?needs the trace onlyDid it match thecontract?needs a datasheetDid the situationarise?needs neitherAssertion2 of 8 defectsScoreboardthe data defectsCoveragechecks nothingIllegal trafficcaughtLegal, wrong datacaughtNever exercisedreported as a gap12
Figure 1 — three mechanisms, three questions, and the gaps between them. An assertion sees an illegal waveform; a scoreboard sees a result that contradicts the contract; coverage sees only that a situation arose. The shaded overlaps are the defects each pair still misses — a legal waveform carrying wrong data is invisible to assertions, and a situation that never occurred is invisible to both checkers.

A permanent coverage hole that was already being covered

Pitfall — an uncoverable bin, indistinguishable from an unreached one
Buggy Code
// A cover point for the write-then-read access, written the natural way:
//
//    -- psl COV_WRITE_THEN_READ :
//    --      cover {start_now = '1'; active = '1'[*]; start_now = '1'};
//
// The bench drives exactly that pattern in test A2: a write that keeps the bus,
// a repeated START, then a read. The report says:
//
//    COV_WRITE_THEN_READ    hits=0
//
// The natural reading is that the stimulus does not produce the case, so somebody
// is assigned to write a test for it -- a test that already exists.
//
// In NVC 1.23 a cover sequence containing an UNBOUNDED star never matches. The
// bin is not unreached; it is UNREACHABLE. And a zero looks the same either way.
Root Cause

An uncoverable bin reports the same zero as an unreached one, so it silently redirects effort toward writing stimulus for a case that is already exercised — and the bin stays red no matter what anyone does. The specific cause here is a tool limitation on unbounded repetition, but the defence is procedural rather than tool-specific: confirm each cover point can be hit, exactly as each property must be shown able to fire.

Fix
// Use a form the tool actually matches. Measured, not guessed:
//
//    {a; b}            plain concatenation   -> matches
//    {a; b[*]; a}      unbounded star        -> NEVER matches
//    {a; b[*1 to 8]; a} bounded range        -> matches
//    {a; b[+]; a}      one or more           -> matches
//
//    -- psl COV_WRITE_THEN_READ :
//    --      cover {start_now = '1'; active = '1'[+]; start_now = '1'};
//
//    => COV_WRITE_THEN_READ    hits=1
//
// AND THE PROCESS FIX, which outlives the tool version: every cover point needs a
// run that SHOULD hit it, with the count confirmed to move. That is the same
// discipline the properties need in the other direction -- a property must be
// shown able to FIRE, a cover point must be shown able to be HIT.
//
// A bin at zero has two explanations and they need different work:
//    unreached   -> write stimulus
//    unreachable -> fix the bin
// Nothing in the report distinguishes them, so the check has to be deliberate.
Pitfall — reading a covered bin as a checked case
Buggy Code
// A sign-off review. The coverage report is strong:
//
//    functional coverage: 100%   (every bin hit)
//    statement coverage:   97%
//    branch coverage:      95%
//
// The conclusion recorded in the review: "the register interface is covered".
//
// Then a mutation campaign runs against the same environment and six mutants
// survive -- including a pointer that CLEARS on framing instead of persisting,
// and a read-only mask that is ignored entirely.
//
// Every line involved was executed by the tests that failed to kill them. The
// bins were hit. Coverage was measuring that the STIMULUS reached the situation,
// and nothing in it measured whether a CHECKER was watching when it did.
Root Cause

Coverage instruments the stimulus reaching a situation, which is necessary and not sufficient — a bin is hit whether or not any comparison was performed at that moment. The failure is systemic rather than accidental: every metric in a coverage report behaves this way, so a high number invites exactly the wrong inference. Pairing each feature's coverage with a mutation result separates 'we went there' from 'we would have noticed', and the gap between them is where surviving defects live.

Fix
// Read coverage as a statement about stimulus, and pair it with a measurement of
// detection. They answer different questions and neither substitutes:
//
//    coverage says   : "a write to a read-only register OCCURRED"
//    mutation says   : "if that write had been WRONGLY ACCEPTED, we would notice"
//
// The pairing is what makes a sign-off argument:
//
//    for each feature in the matrix:
//       covered?  -- did stimulus reach it            (coverage report)
//       killed?   -- would a defect there be caught   (mutation campaign)
//
//    both yes  -> verified
//    covered, not killed -> A CHECKER IS MISSING   <-- the dangerous quadrant
//    killed, not covered -> impossible
//    neither   -> not verified, and honestly reported
//
// "Covered but not killed" is the state that a coverage-only sign-off reports as
// success, and it is exactly where Module 20's six survivors lived.
//
// THE ONE-LINE VERSION: coverage measures the stimulus, mutation measures the
// checkers. A report of one described as evidence of the other is the most common
// way a verification sign-off overstates itself.

5. What 22.6 Settled

Coverage answers whether a situation arose, and nothing else. It is blind to whether a checker was watching, which is why Module 20's six survivors sat inside fully-covered code.

Cover points earn their place by making an absence visible — a repeated START never produced, a stretch never exercised, an acknowledge outcome never seen.

An uncoverable bin and an unreached bin both read zero, so every cover point needs a run that should hit it, exactly as every property needs a run that should fire it.

A cross is worth writing when its empty cells are actionable, and harmful when they are unreachable, because the exclusion habit that follows outlives the cross.

Pair coverage with mutation for a sign-off argument. Covered-but-not-killed is the dangerous quadrant, and it is the one a coverage-only review reports as success.

Next, the tests that deliberately do the wrong thing — and why a NACK is not an error. Chapter 22.7 — Negative Testing.

Continue learning