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:
| mechanism | question | blind to |
|---|---|---|
| assertion | was this waveform illegal? | anything legal-but-wrong |
| scoreboard | did the result match the contract? | anything the monitor reconstructed away |
| coverage | did 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
-- 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.
| cross | worth it? | why |
|---|---|---|
| direction × how the transfer ended | yes | the empty cell is "a read that began with a repeated START" — the commonest real access |
| direction × address matched | yes | separates "never addressed us for a read" from "read worked" |
| byte count × direction | marginal | a long read and a long write exercise different datapaths, but the bins overlap heavily |
| address × byte count | no | 128 × 8 cells, nearly all meaningless |
| address bit k × direction | no | individual 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 permanent coverage hole that was already being covered
Pitfall — an uncoverable bin, indistinguishable from an unreached one
// 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.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.
// 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
// 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.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.
// 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
Related tutorials
- Related topic
UCIe Functional Coverage
Proving a UCIe regression exercised the meaningful architectural space rather than merely running many tests — a five-level plan hierarchy, coverage sampled on the event it describes rather than every clock, crosses justified by a named bug, illegal bins that require specification evidence rather than an implementation limit, recovery-context coverage as the centerpiece, and closure that classifies every uncovered bin instead of waiving it.
- Related topic
RTL and Verification Review
546 declared coverage bins, 243 unreachable by construction, and one regression that is 51.832% or 93.399% depending on the denominator — plus the twenty bins random stimulus will never reach.
- Related topic
DDR Functional Coverage
A naive six-axis cross is 11,520 bins. 288 are legal and 96 test a margin that can fail — so 99.2% of the denominator is a number nobody chose.
- Related topic
UART vs Other Interfaces: Choosing the Right Link
Serial interfaces differ first in where the receiver's timing comes from, then in what organises a shared medium — and capability is paid for in what the system must already provide. A question order for choosing between UART, SPI, I2C, CAN, USB and Ethernet.
