UCIe · Module 20
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.
Chapters 20.1 to 20.4 built everything that decides whether the design is correct: boundary contracts, temporal properties, and obligation models. None of them answers a different and equally necessary question — did the regression ever put the design in the situations those checks were written for?
1. The One-Sentence Model
Functional coverage measures whether meaningful architectural situations were observed. It does not measure correctness. Correctness comes from assertions, scoreboards and reference models; coverage answers did the scenario happen — under which configuration, in which state, with which fault, with which concurrency, and in which recovery context?
That division of labour is absolute, and forgetting it produces both of the classic failures. A plan with excellent coverage and weak checkers proves the design was exercised while wrong. A plan with excellent checkers and no coverage proves the design was correct in whatever situations happened to occur — and 20.3 §48 showed that a property whose antecedent never fires passes 100% of a regression.
2. What This Chapter Owns
| Question | Where it is answered |
|---|---|
| Boundary contracts, the five verification planes, independent models, safety versus liveness | 20.1 — Protocol Verification |
| The link lifecycle, reference link model, fault injection, recovery scenarios | 20.2 — Link Verification |
| Temporal properties, triggers, scoping, vacuity, antecedent covers | 20.3 — UCIe Assertions |
| Obligation models, correlation, epochs, first divergence | 20.4 — UCIe Scoreboards |
| The UVM environment that hosts these collectors | 20.6 — UVM Architecture for UCIe |
| Conformance, interoperability and official compliance | 20.7 — UCIe Compliance Testing |
Earlier chapters wrote activation covers for individual properties (20.3 §67) and scenario covers for individual models (20.4 §57). This chapter is the model those were fragments of — and the difference is architectural rather than a matter of scale:
A five-level plan hierarchy (§6) that keeps requirement coverage, scenario coverage, state coverage, cross coverage and code coverage from being confused with one another — because three of the five get quoted as if they were the first.
Sampling on the event a bin describes (§9–§10). A lifecycle bin sampled every clock measures residency, not scenario diversity, and reports 99% for the state the link idles in.
Crosses that must name a bug (§31–§33). The Cartesian product of every dimension is millions of bins, most impossible, and closure becomes bin-count management.
Illegal bins that require specification evidence (§13–§14) — because an illegal_bins derived from what the current RTL cannot do has encoded an implementation limit as a requirement, which is 20.4 §16's mirroring failure in the coverage model.
Recovery-context coverage as the centerpiece (§26–§27), because "a recovery occurred" is the bin almost every plan has and almost none of the questions it needs to answer.
And closure that classifies rather than waives (§50–§52). Six classifications, each with a different owner and a different next action, and "never hit in regression" is not a proof of unreachability.
3. Sourcing
4. Coverage Answers a Different Question
Two questions, two mechanisms, and neither substitutes for the other.
| Checkers — assertions, scoreboards, models | Coverage | |
|---|---|---|
| Asks | was the behaviour correct? | did the situation occur? |
| Fails when | the design broke a contract | nothing — coverage never fails |
| Reports | a defect | a gap in the evidence |
| Silent when | the situation never arose | the situation arose and was wrong |
| Owner of the gap | the design team | the stimulus, the environment, or the plan |
Three consequences.
Coverage cannot fail a test, and that is not a weakness. It is a measurement, and treating an uncovered bin as a defect confuses the measurement with the thing measured. §50's classification exists precisely because an uncovered bin has six possible owners and only one of them is the design.
A green regression with no coverage model is an unquantified claim. Every property passed — on the situations that happened. 20.3 §48's vacuity argument is coverage's argument in miniature, and this chapter is that argument applied to the whole plan.
And a covered bin with a weak checker is worse than an uncovered one. The situation occurred, nothing checked it, and the coverage report now records it as evidence. §34's pairing rule is the defence, and §58's review table makes the pairing visible.
5. Coverage Is Not a Score
100% of a poor model proves very little. 80% of a carefully defined model is frequently the more informative result — because the uncovered 20% is a list of specific, named questions.
What an uncovered bin can mean, and they are not the same finding:
| Meaning | Owner | Next action |
|---|---|---|
| the stimulus never produced it | DV | write a sequence or a constraint |
| the environment prevents it | DV | fix the environment restriction |
| the design prevents it because of a bug | design | a defect, found by coverage |
| it is architecturally unreachable | architecture | prove it (§51), then exclude |
| the requirement is obsolete | architecture | remove the requirement and the bin |
| the coverage model is wrong | DV | fix the bin definition |
Two observations.
Row 3 is coverage finding a design bug, and it is the row people forget coverage can do. A bin that should be reachable and is not — a degraded width the architecture supports and the design cannot enter — is a defect discovered by a mechanism that never fails a test.
And rows 4 to 6 are the rows that get skipped. A bin nobody can hit is waived, and the waiver records neither which of the three it was nor what evidence supports it. §52 is the record that makes the difference.
Read the model, not the number. A percentage is a summary of a model whose quality it cannot express.
6. The Five-Level Plan Hierarchy
Five levels, and three of them routinely get quoted as if they were the first.
| Level | Measures | Derived from | Answers |
|---|---|---|---|
| 1 — Requirement | each architectural requirement was checked and exercised | the architecture / specification | did we verify what we promised to verify? |
| 2 — Scenario | each meaningful situation occurred | the verification plan | did the interesting things happen? |
| 3 — State / configuration | each meaningful state and configuration was entered | the design's legal space | did we run in every mode? |
| 4 — Cross | meaningful combinations occurred | pairs whose interaction can break (§31) | did the interactions happen? |
| 5 — Code / structural | statements, branches, toggles, FSM arcs were executed | the RTL | is any logic unexercised? |
Four rules that follow from the ordering.
Level 1 is the only one that can be complete. The requirement list is finite and enumerable; the state space is not. A plan that reports levels 3 to 5 and not level 1 has measured what it happened to touch rather than what it set out to verify.
Level 5 is evidence, never a requirement substitute (§36). It answers a question about the implementation; levels 1 and 2 answer questions about the architecture.
Level 2 is where UCIe verification lives. "A recovery with three outstanding obligations and zero credit" is a scenario; it is not a state, not a configuration, and not expressible as a single coverpoint. §26 is the level-2 model for recovery, and it is the chapter's centerpiece.
And they must be reported separately. A merged number across all five is uninterpretable — level 5 is easy to drive high and level 2 is not, so a combined figure is dominated by the level that matters least.
7. Requirement-to-Coverage Mapping
The level-1 artefact: every architectural requirement answers both questions.
Requirement IDs below are this chapter's own labels, not UCIe identifiers (§3). They exist so the table can be referenced.
| ID | Requirement (illustrative architecture) | Checker | Coverage item | Negative test | Basis |
|---|---|---|---|---|---|
| R1 | traffic is admitted only when operational and a configuration is agreed | 20.2 §13 | lifecycle transition + config-agreed cross (§11, §16) | admit during training | architecture |
| R2 | the committed configuration matches an independent negotiation rule | 20.2 §20 | negotiation-outcome bins (§16) | incompatible capability | architecture |
| R3 | a semantic operation is delivered exactly once | 20.3 §33 | attempts-per-operation bins (§28) | duplicate arrival | architecture |
| R4 | a corrupt object is never delivered | 20.3 §32 | verdict bins × delivery (§24) | corrupt after integrity | architecture |
| R5 | semantic obligations survive a transport recovery | 20.3 §38 | outstanding-at-recovery bins (§26) | recovery with work | architecture |
| R6 | credit never exceeds active capacity | 20.3 §28 | credit boundary bins (§23) | duplicate return | architecture |
| R7 | a stale-epoch event changes nothing | 20.3 §38 | stale-epoch bins (§23) | straggler after recovery | architecture |
| R8 | the active configuration changes atomically | 20.3 §37 | commit-under-traffic bins (§16) | commit mid-operation | architecture |
| R9 | no resource is released before its obligation resolves | 20.4 §25 | stranded-check activation | free on send | architecture |
| R10 | a degraded configuration is legal for current resources | 20.2 §28 | width before × after (§18) | lane loss with work | architecture |
| R11 | no traffic class is starved | 20.3 §45 | per-class service bins (§19) | sustained one-class load | architecture |
| R12 | every accepted obligation reaches a terminal state | 20.2 §47 | terminal-classification bins (§26) | fatal with work | architecture |
Three properties of this table.
Every row has a checker and a coverage item. A row with a checker and no coverage item is a check that may never have run; a row with coverage and no checker is a situation that was exercised and unverified. Both are gaps and the table makes both visible.
Every row has a negative test. 20.1 §44's rule: a requirement whose violation has never been attempted has an unexercised detection path.
And the basis column is where §3's discipline lands. Every row here reads "architecture" — not one is claimed as a UCIe normative requirement. A real plan's basis column would cite specification clauses, and the column existing is what forces the citation.
8. Covergroup Architecture
Six collectors, not one covergroup. The split is by responsibility, and it mirrors 20.4 §5's model split for the same reason.
| Collector | Samples on | Owns |
|---|---|---|
link_cov | lifecycle events | phase transitions, training attempts, recovery outcomes |
config_cov | configuration commits | negotiated outcomes, widths, protocol enables, epochs |
resource_cov | credit and occupancy events | credit boundaries, occupancy buckets, stranding |
reliability_cov | verdicts, attempts, deliveries | fault families, attempt counts, duplicate paths |
recovery_cov | recovery entry and exit | recovery context — the centerpiece (§26) |
traffic_cov | acceptance and completion | classes, directions, concurrency, ordering |
Three reasons this beats one covergroup.
Each samples a different event (§9). One covergroup has one sample call, so it must either sample on a union of events — making most coverpoints stale at most samples — or sample every clock, which is §10.
Each can be enabled independently. A long run frequently disables the expensive collectors, and a monolith cannot be partially disabled — the same argument 20.4 §4 makes about scoreboards.
And an uncovered bin names its collector, which is the first step of §50's classification: a gap in recovery_cov and a gap in config_cov have different owners and different next actions.
9. Sample on the Event the Bin Describes
The single most consequential decision in a coverage model.
// ILLUSTRATIVE. Each collector samples on ITS OWN semantic event, delivered by
// a monitor (20.4 §9). No collector samples on a bare clock.
class ucie_config_cov;
covergroup cg_config;
// Sampled at a CONFIGURATION COMMIT — once per commit, not once per cycle.
cp_negotiation: coverpoint neg_outcome {
bins full = {NEG_FULL};
bins degraded = {NEG_DEGRADED};
bins none = {NEG_NONE}; // the refusal — §16
}
cp_width_class: coverpoint width_class {
bins w[] = {[WC_MIN:WC_MAX]}; // symbolic classes — §17
}
cp_commit_context: coverpoint outstanding_bucket {
bins idle = {OB_ZERO};
bins busy = {[OB_ONE:OB_MANY]}; // §16 — the case that matters
}
endgroup
function void on_event(ucie_event e);
if (e.kind == EVT_CFG_COMMIT) begin
neg_outcome = classify_negotiation(e);
width_class = classify_width(e);
outstanding_bucket = bucket(sem_model.outstanding_count());
cg_config.sample(); // exactly once per commit
end
endfunction
endclassThe rule, per collector:
| Question | Sample on |
|---|---|
| which configurations were used? | the configuration commit |
| which protocol classes were carried? | the acceptance of an object of that class |
| which recovery contexts occurred? | recovery entry (context) and exit (outcome) |
| which fault families were exercised? | the injection, and separately the detection |
| which lifecycle transitions happened? | the transition (§12) |
| which occupancy states were reached? | a push or a pop — the events that change it |
And the rule's justification is one sentence. A bin is a claim that a situation occurred; sampling on an unrelated event turns the count into a measure of how long that situation persisted, which is a different quantity with a misleading name (§10).
10. Wrong Coverage — Clock-Sampled State
// WRONG — the lifecycle sampled every clock.
covergroup cg_link_bad @(posedge clk);
cp_phase: coverpoint link_phase {
bins reset = {RM_RESET}; bins training = {RM_TRAINING};
bins oper = {RM_OPERATIONAL}; bins recovery = {RM_RECOVERY};
}
endgroupWorked, with realistic durations. A run trains for 1,000 cycles, operates for 100,000, and recovers twice for 500 cycles each.
| Bin | Hits | Share |
|---|---|---|
reset | 20 | 0.02% |
training | 1,000 | 0.98% |
oper | 100,000 | 98.0% |
recovery | 1,000 | 0.98% |
All four bins are hit. Coverage reports 100%. And the report says almost nothing.
Four properties.
It measures residency, not scenario diversity. The 98% figure describes how long the link idled in its operating state — which is a performance observation dressed as a coverage result.
Two recoveries and two hundred recoveries produce nearly identical reports. The hit count scales with duration, so a run with one long recovery looks better covered than a run with fifty short ones. The quantity the plan cares about — how many distinct recoveries occurred, in how many distinct contexts — is not measured at all.
It hides the missing transitions. All four states were entered; nothing says whether RECOVERY → OPERATIONAL ever happened as opposed to RECOVERY → FAILED twice. §12 is what does.
And it makes closure meaningless. The bins fill within the first thousand cycles of any run. A coverage item that is satisfied by the smoke test cannot guide stimulus development, which is coverage's main practical use (§55).
11. Link-Lifecycle Coverage
// ILLUSTRATIVE. Sampled on lifecycle EVENTS from the link model (20.2 §7).
// The phase enumeration is the ENVIRONMENT's five-phase model, deliberately
// not the published UCIe state names (§3, and 20.2 §8's reasoning).
covergroup cg_lifecycle;
// States entered — each ENTRY counted once, not each cycle of residency.
cp_phase_entered: coverpoint phase_entered {
bins reset = {RM_RESET}; bins training = {RM_TRAINING};
bins oper = {RM_OPERATIONAL}; bins recovery = {RM_RECOVERY};
bins failed = {RM_FAILED};
}
// Transitions — the level-2 question (§6). Sampled on the transition itself.
cp_arc: coverpoint phase_arc {
bins reset_to_train = {ARC_RESET_TRAIN};
bins train_to_oper = {ARC_TRAIN_OPER};
bins train_restart = {ARC_TRAIN_TRAIN}; // 20.2 §26
bins train_to_failed = {ARC_TRAIN_FAILED}; // 20.2 §24 — the refusal
bins oper_to_recovery = {ARC_OPER_RECOVERY};
bins recovery_to_oper = {ARC_RECOVERY_OPER};
bins recovery_to_failed = {ARC_RECOVERY_FAILED}; // 20.2 §42
bins failed_to_reset = {ARC_FAILED_RESET}; // the commanded exit
}
// How many training attempts were needed — a distribution, not a boolean.
cp_train_attempts: coverpoint train_attempt_count {
bins first_time = {1};
bins one_retry = {2};
bins several = {[3:MAX_TRAIN_ATTEMPTS-1]};
bins exhausted = {MAX_TRAIN_ATTEMPTS}; // the budget boundary (§22)
}
endgroupFour notes.
Eight arcs, and four of them are failure or restart paths. train_to_failed, recovery_to_failed, train_restart and failed_to_reset are the arcs a happy-path regression never takes — and they are where the refusal and classification logic lives (20.2 §24, 20.2 §41).
cp_phase_entered counts entries, not cycles. The same enumeration as §10's wrong version, sampled on a different event, producing a completely different and actually useful measurement.
cp_train_attempts covers the budget boundary. The exhausted bin is the one that exercises §22's boundary discipline and 20.2 §24's bounded-retry termination — and it requires a persistent injected fault, so it will not appear by accident.
And the phase enumeration is the environment's, not the design's. 20.2 §8's principle carried into the coverage model: a coverage model built on the design's state encoding cannot record that the design entered a phase it should not have, because the encoding is the design's opinion. §42 develops this.
12. Transition Coverage, Not Residency
The general rule this chapter keeps returning to:
If the architectural question is about a change, cover the change. If it is about a situation, cover the entry into the situation. Almost never cover the persistence of either.
| Architectural question | Wrong coverpoint | Right coverpoint |
|---|---|---|
| were all lifecycle paths taken? | phase, per clock | the arc, per transition |
| were all occupancy states reached? | occupancy, per clock | the bucket at each push/pop |
| was credit ever exhausted? | credit value, per clock | the transition to zero, and the recovery from it |
| was the configuration ever changed live? | active config, per clock | the commit event, with its context |
| were both widths used? | width, per clock | width at each commit, plus §18's cross |
Two exceptions where residency genuinely is the question.
A duration bin — how long a state persisted — is a legitimate coverage item when the architecture has a bound on it. "Was a recovery ever longer than half the permitted budget?" is a real question, and it is covered by bucketing the duration at the exit event, not by sampling the state every cycle.
And a simultaneity bin is sampled per cycle by necessity. "Did a consume and a return ever coincide?" (19.5 §13) is a per-cycle question, so it samples per cycle — but its bins are the four combinations, not the credit value, so residency does not distort it.
13. Illegal Bins Require Evidence
An illegal_bins is a specification claim. It says the condition must never occur — which means something must have said so.
Acceptable bases, and there are only four:
| Basis | Example |
|---|---|
| specification text | a state transition the specification forbids |
| an architectural contract | "an identity is not reused while live" (19.2 §21) |
| a parameter legality rule | a class index at or above NUM_CLASSES (19.6 §13) |
| an arithmetic impossibility | occupancy exceeding depth, given a correct structure |
Unacceptable bases:
- "the current RTL cannot do that" — §14;
- "we have never seen it" — that is an uncovered bin, not an illegal one;
- "the stimulus does not generate it" — an environment restriction (§50, row 2);
- "it would be a bug" — then it belongs in an assertion, which fails and localises, while an illegal bin only reports a count (§40).
And where the basis is real, prefer the assertion anyway. 20.3 §5's grid: an assertion fails at the cycle, names the boundary, and appears in the failure report. An illegal bin appears in the coverage report at end of test with a count. The illegal bin is supplemental; the assertion is the checker.
14. Wrong Illegal Bin — an Implementation Limit as a Requirement
// WRONG — the current design does not support degraded recovery from x8 to x4,
// so the coverage author marks that transition illegal.
covergroup cg_width_bad;
cp_width_change: coverpoint width_transition {
bins same = {WT_SAME};
bins to_narrower = {WT_NARROWER};
illegal_bins x8_to_x4 = {WT_X8_TO_X4}; // "we don't support that"
}
endgroupThree consequences, in the order they arrive.
The required path is never tested. The architecture permits degradation to that width; the coverage model now forbids exercising it, so no sequence is written and no checker for it is ever exercised.
When the design later supports it, the coverage model fails the run. The illegal bin fires on correct behaviour, and — following 20.3 §14's pattern — the fix applied is usually to delete the bin rather than to reclassify it, taking the coverage item with it.
And the model has encoded an implementation limitation as a requirement. This is 20.4 §16's mirroring failure in the coverage model: the model agreed with the design about what is possible, so it can never record that the design is missing something.
The correct handling depends on which of three situations it actually is:
// RIGHT — three different answers for three different situations.
covergroup cg_width;
cp_width_change: coverpoint width_transition {
bins same = {WT_SAME};
bins to_narrower = {WT_NARROWER};
// (a) The architecture permits it and the design does not yet: leave it
// UNCOVERED, and let §50 classify it as "DUT prevents — investigate".
bins x8_to_x4 = {WT_X8_TO_X4};
// (b) This product genuinely does not support the feature (not built —
// 19.6 §20's capability structure says so): IGNORE, with a reason.
ignore_bins unsupported_by_this_sku = {WT_X32_TO_X16} with
(!capability.degraded_width_supported); // §15
// (c) The architecture forbids it — WIDENING during a recovery, say:
// illegal, and there should also be an assertion (§13).
illegal_bins widening_during_recovery = {WT_WIDER_IN_RECOVERY};
}
endgroupThe three answers differ in who owns the gap. (a) is a question for the design team, (b) is a documented product scope, (c) is a rule. Collapsing them into one illegal_bins loses all three distinctions.
15. ignore_bins, and Its Documentation Requirement
ignore_bins removes a bin from the denominator. That is a claim, and it needs a record — the same record §52 requires for a waiver, because it is the same thing done earlier.
Three legitimate uses:
Architecturally irrelevant combinations. A cross of fault family × training phase has cells for faults that cannot occur before the link exists. Those cells are noise in the denominator, and removing them makes the remaining percentage mean something.
Configurations this build does not have. A replay-disabled build (19.6 §14) has no reachable retry bins. Ignoring them per build is correct; leaving them uncovered makes every replay-disabled regression report a permanent shortfall that trains the team to ignore the number.
Proven-unreachable scenarios — with the proof, per §51.
And the illegitimate use, which is the common one: ignoring a bin because it is hard to hit. That is §50 row 1 — stimulus missing — relabelled as an exclusion, and the difference is that a missing-stimulus gap has an action while an ignored bin has none.
// The record every ignore_bins needs, next to it. A bare ignore_bins with no
// comment is an undocumented reduction of the denominator.
//
// BIN: fault_during_training × integrity_fault
// REASON: integrity checking is not in the path before the Adapter is
// operational, so the combination has no architectural meaning.
// EVIDENCE: 20.1 §51's mode table — the Adapter is not in the path.
// OWNER: <name> REVIEWED: <revision>
ignore_bins no_integrity_before_operational =
binsof(cp_fault) intersect {F_INTEGRITY} &&
binsof(cp_phase) intersect {RM_TRAINING};16. Configuration Coverage
// ILLUSTRATIVE. Sampled at a configuration commit. Every value is a CATEGORY
// derived from the capability structure (19.6 §20), never a hardcoded number.
covergroup cg_config;
// Which negotiation outcomes occurred — including the refusal.
cp_outcome: coverpoint neg_outcome {
bins full = {NEG_FULL};
bins degraded = {NEG_DEGRADED};
bins none = {NEG_NONE}; // 20.2 §24 — the untested path
}
// Which protocol enables were active. Categories, not a bitmask product.
cp_proto_mix: coverpoint proto_mix {
bins single = {PM_SINGLE};
bins dual = {PM_DUAL};
bins all = {PM_ALL};
}
// Which reliability mode. Conditional on the build (§3, §40).
cp_reliability_mode: coverpoint rel_mode {
bins adapter_managed = {RM_ADAPTER};
bins raw_mode = {RM_RAW}; // 20.1 §51 — a different check set
bins no_reliability = {RM_NONE};
}
// WAS THE COMMIT ATTEMPTED UNDER TRAFFIC? The bin that validates R8.
cp_commit_context: coverpoint commit_outstanding_bucket {
bins idle = {OB_ZERO};
bins busy = {[OB_ONE:OB_MANY]}; // 19.6 §56's quiesce path
}
// Did a commit ever land during an operation's lifetime?
cp_commit_spans_operation: coverpoint commit_spanned_operation {
bins no = {0};
bins yes = {1}; // 20.3 §37's effect property
}
// Targeted crosses only — each justified in §31's terms.
x_outcome_x_reliability: cross cp_outcome, cp_reliability_mode;
x_commit_context_x_outcome: cross cp_commit_context, cp_outcome;
endgroupFour notes.
cp_outcome.none is the refusal bin. A link that has never been asked to refuse an incompatible capability set has an untested failure path — and it is the path that first executes against a peer nobody tested with (20.2 §24).
cp_commit_context.busy is what validates R8. A configuration commit on an idle link never exercises quiescence, so 19.6 §23's partial-commit failure mode is untested however many commits occurred.
cp_commit_spans_operation is a derived coverpoint, computed by the join from an obligation's accept and terminal epochs (20.4 §57). It is not observable at any single boundary, which is why the collector needs the models rather than only the monitors.
And x_outcome_x_reliability earns its place by §31's test: if negotiation and reliability mode interact incorrectly, a Raw Mode link negotiates a degraded configuration and then applies Adapter-managed reliability checks that do not exist in that path. That is a concrete bug, so the cross is justified.
17. Width Coverage
// ILLUSTRATIVE. Width CLASSES, derived from the capability structure.
function automatic width_class_e classify_width(int unsigned w);
if (w == capability.max_lanes) return WC_MAX;
if (w == capability.min_supported_width) return WC_MIN;
if (w == capability.nominal_width) return WC_NOMINAL;
if (w < capability.nominal_width) return WC_DEGRADED;
return WC_INTERMEDIATE;
endfunction
covergroup cg_width;
// Configured width class at each commit.
cp_configured: coverpoint configured_class {
bins classes[] = {WC_MIN, WC_DEGRADED, WC_INTERMEDIATE, WC_NOMINAL, WC_MAX};
}
// ACTIVE width class — which may differ after repair (19.6 §25's chain).
cp_active: coverpoint active_class {
bins classes[] = {WC_MIN, WC_DEGRADED, WC_INTERMEDIATE, WC_NOMINAL, WC_MAX};
}
// Was the active width ever below the configured one?
cp_degraded_operation: coverpoint active_below_configured {
bins no = {0};
bins yes = {1};
}
endgroupTwo notes.
Configured and active are separate coverpoints because they are separate quantities with separate owners (19.6 §25): configuration is software-owned, the active mask is hardware-owned. A single width coverpoint cannot record that they differed, which is the situation degradation creates.
And WC_INTERMEDIATE exists because degraded widths need not be powers of two. 20.2 §57's trace reached an intermediate width after a repair, and a bin structure that assumes powers of two cannot record it — which then hides a striping bug that only appears at non-power-of-two widths.
18. The Width-Before-and-After Cross
The most valuable cross in the configuration model, and it is a cross the plan must construct deliberately because neither side is observable at one event.
// ILLUSTRATIVE. Sampled at RECOVERY EXIT, with the pre-recovery class captured
// at recovery ENTRY. A cross between two moments, not two signals.
covergroup cg_width_recovery;
cp_before: coverpoint class_before_recovery {
bins classes[] = {WC_MIN, WC_DEGRADED, WC_INTERMEDIATE, WC_NOMINAL, WC_MAX};
}
cp_after: coverpoint class_after_recovery {
bins classes[] = {WC_MIN, WC_DEGRADED, WC_INTERMEDIATE, WC_NOMINAL, WC_MAX};
bins failed = {WC_NONE}; // the recovery did not restore a link
}
cp_direction: coverpoint width_direction {
bins unchanged = {WD_SAME};
bins narrower = {WD_NARROWER}; // 20.2 §30 — legal
bins wider = {WD_WIDER}; // legal only if the architecture says so
}
// THE CROSS. Justified in §31's terms below.
x_before_after: cross cp_before, cp_after;
endgroupWhy this cross is justified — the §31 test, answered concretely.
If the width before a recovery and the width after it interact incorrectly, what bug appears?
Three specific bugs, each in a different cell of the cross:
NOMINAL → DEGRADED exercises 20.2 §28's checker failure — a plan expecting the same width back reports a false failure and gets the whole degradation test deleted.
DEGRADED → NOMINAL exercises whether headroom, watermarks and credit capacity derived from the narrow width are correctly recomputed when the link widens. A design that computes them once at first bring-up is wrong here and correct everywhere else.
MAX → MAX with an intervening lane event exercises the case where degradation was attempted and recovered fully — and it is the cell that distinguishes "no lane ever failed" from "a lane failed and repair restored full width", which look identical in a single-width coverpoint.
And the diagonal is not the interesting part. A cross whose only hit cells are on the diagonal has recorded that nothing ever changed — which is a finding, and it is the finding §50 row 1 exists for.
19. Protocol-Class Coverage
// ILLUSTRATIVE. Sampled at object acceptance. NUM_CLASSES is a parameter (§40).
covergroup cg_traffic_class;
cp_class: coverpoint accepted_class {
bins c[] = {[0:NUM_CLASSES-1]};
}
// Were several classes active in the same window? The Arb/Mux question.
cp_concurrency: coverpoint active_class_count {
bins one = {1};
bins two = {2};
bins several = {[3:NUM_CLASSES]};
}
// Was a class ever admitted while another was exhausted? 19.5 §9's overflow.
cp_asymmetric_exhaustion: coverpoint asymmetric_state {
bins none = {AS_NONE};
bins one_exhausted = {AS_ONE_ZERO}; // the case a scalar pool breaks
bins all_exhausted = {AS_ALL_ZERO};
}
// Was a class ever starved of service? R11's coverage item.
cp_starvation_window: coverpoint max_service_gap_bucket {
bins short = {SG_SHORT};
bins medium = {SG_MEDIUM};
bins long = {SG_LONG}; // 19.5 §49 / 20.3 §45
}
endgroupThree notes.
cp_asymmetric_exhaustion.one_exhausted is the bin that exercises 19.5 §9's failure. A global credit pool overflows one class while stalling another, and the bug needs two classes with asymmetric depths and asymmetric load — which is the steady state in production and almost never a directed test.
cp_starvation_window buckets a gap, not a count. 20.3 §45's bounded-wait property is what checks it; this bin records whether a long gap was ever observed, which is what makes the property non-vacuous.
And no protocol mapping is claimed. UCIe maps PCIe and CXL natively (§3); which resource classes exist, how many, and how they map to those protocols is not asserted here. NUM_CLASSES is a parameter and the bins are indices.
20. Traffic-Direction Coverage
// ILLUSTRATIVE. 19.1 §41 established that TX and RX are independent. This is
// the coverage that proves the independence was exercised.
covergroup cg_direction;
cp_activity: coverpoint direction_activity {
bins tx_only = {DA_TX};
bins rx_only = {DA_RX};
bins full_duplex = {DA_BOTH}; // the operating point
}
// One direction stalled while the other progresses — 19.1 §42's shared-ready.
cp_asymmetric: coverpoint asymmetric_progress {
bins none = {AP_NONE};
bins tx_stalled = {AP_TX_STALLED};
bins rx_stalled = {AP_RX_STALLED};
}
x_activity_x_asymmetric: cross cp_activity, cp_asymmetric;
endgroupWhy cp_asymmetric is the coverpoint that matters. 19.1 §42's failure is a shared ready signal, so a stall in one direction stalls both. The bug is invisible under tx_only or rx_only load and invisible under symmetric full-duplex load — it needs one direction stalled while the other has work, which is exactly the AP_TX_STALLED cell crossed with DA_BOTH.
And the cross has one cell that must be non-empty: full_duplex × tx_stalled. If it is empty, the direction-independence property has never been exercised, whatever the total duplex coverage says.
21. Resource Coverage — Occupancy Buckets
// ILLUSTRATIVE. Buckets, not values. Sampled on a push or a pop — the events
// that change occupancy (§12). Parameter-safe (§40).
function automatic occ_bucket_e bucket(int unsigned occ, int unsigned depth);
if (occ == 0) return OCC_EMPTY;
if (occ == 1) return OCC_ONE; // §22's boundary
if (occ < depth/4) return OCC_LOW;
if (occ < HIGH_WATER) return OCC_MID;
if (occ < depth - 1) return OCC_HIGH;
if (occ == depth - 1) return OCC_NEAR_FULL; // §22's boundary
return OCC_FULL;
endfunction
covergroup cg_occupancy;
cp_bucket: coverpoint occ_bucket {
bins empty = {OCC_EMPTY};
bins one = {OCC_ONE};
bins low = {OCC_LOW};
bins mid = {OCC_MID};
bins high = {OCC_HIGH};
bins near_full = {OCC_NEAR_FULL};
bins full = {OCC_FULL};
}
// Threshold crossings — the watermark question (19.4 §29).
cp_watermark: coverpoint watermark_event {
bins asserted = {WM_ASSERT};
bins deasserted = {WM_DEASSERT};
bins chatter = {WM_CHATTER}; // 19.4 §32 — hysteresis absent
}
// Simultaneous push and pop — a per-cycle question (§12's exception).
cp_simultaneous: coverpoint push_pop {
bins neither = {2'b00}; bins push_only = {2'b10};
bins pop_only = {2'b01}; bins both = {2'b11}; // 19.4 §9's drift case
}
endgroupThree notes.
Seven buckets, of which four are boundaries. empty, one, near_full and full are single-value bins; the other three are ranges. That asymmetry is deliberate — §22.
cp_watermark.chatter requires a derived event. It is set when the watermark toggles more than once within a short window, which is 19.4 §32's missing-hysteresis signature. No single sample can see it; the collector needs a small amount of state.
And cp_simultaneous.both should be one of the most-hit bins, not one of the rarest. 19.5 §13: a simultaneous push and pop requires both producer and consumer active, which is the intended operating point. If it is rare, the stimulus is not loading the design and every simultaneity bug is unverified.
22. Boundary Bins
Engineering defects concentrate at boundaries, and a bucket structure that spans them hides exactly the values that matter.
| Boundary | Why | The bug it exposes |
|---|---|---|
| 0 | the empty/idle case | underflow; a pop from empty |
| 1 | the single-entry case | a degenerate pointer, $clog2(1) = 0 (19.6 §46) |
| N−1 | one below capacity | an off-by-one in a full check |
| N | at capacity | overflow; the truncated counter of 19.5 §11 |
| wrap | pointer wrap | a wrap that assumes a power of two (19.4 §13) |
| threshold − 1, threshold, threshold + 1 | the watermark edge | a late watermark with insufficient headroom (19.4 §29) |
Two rules.
Every boundary gets its own single-value bin, not a range that contains it. A bin [depth-4:depth] is hit by a value of depth-4 and reports the capacity boundary as covered.
And the boundaries are computed from parameters, never written as literals. A bin written as {16} is the capacity boundary of one build and an arbitrary mid-range value of the next — which is 19.6 §45's literal-width failure appearing in the coverage model.
23. Credit Coverage
// ILLUSTRATIVE. The credit model of 19.5, covered. Sampled on credit events.
covergroup cg_credit;
// Boundaries first (§22).
cp_value: coverpoint credit_bucket {
bins zero = {CB_ZERO}; // exhaustion
bins one = {CB_ONE};
bins mid = {CB_MID};
bins at_cap = {CB_AT_CAPACITY}; // 19.5 §11's truncation boundary
}
// The four update combinations — a per-cycle question (§12's exception).
cp_update: coverpoint consume_return {
bins idle = {2'b00};
bins return_only = {2'b01};
bins consume_only = {2'b10};
bins both = {2'b11}; // 19.5 §13
}
// Multi-unit deltas and their sign — 19.5 §14.
cp_delta_sign: coverpoint delta_sign {
bins negative = {DS_NEG}; bins zero = {DS_ZERO}; bins positive = {DS_POS};
}
// Epoch events — 19.5 §28/§29.
cp_epoch_event: coverpoint credit_epoch_event {
bins fresh_return = {CE_FRESH};
bins stale_return = {CE_STALE}; // the guard, exercised (§49)
bins rebaseline = {CE_REBASELINE};
}
// Batching — 19.5 §42's deadlock needs the PARTIAL flush.
cp_flush_reason: coverpoint flush_reason {
bins threshold = {FR_THRESHOLD};
bins timer = {FR_TIMER}; // the liveness mechanism
bins starved = {FR_STARVED};
}
cp_partial_batch: coverpoint pending_at_flush {
bins partial = {[1:BATCH_THRESHOLD-1]}; // 19.5 §42 — the critical bin
bins full = {[BATCH_THRESHOLD:$]};
}
// Was the progress reserve ever engaged? 19.5 §45.
cp_progress_reserve: coverpoint reserve_engaged { bins yes = {1}; bins no = {0}; }
// Targeted crosses.
x_zero_credit_x_occupancy: cross cp_value, cp_rx_occupancy_bucket;
x_stale_x_phase: cross cp_epoch_event, cp_link_phase;
endgroupThree notes on which bins matter most.
cp_partial_batch.partial is 19.5 §42's deadlock detector. If it is empty, the flush timer never fired, so the liveness mechanism that prevents a permanent stall with free capacity at both ends is untested. It requires a burst followed by silence, which random stimulus almost never produces.
x_zero_credit_x_occupancy is a justified cross by §31's test: zero credit with the receiver below capacity is the stranded-capacity signature (19.5 §63), and it is a completely different condition from zero credit with the receiver full. Neither coverpoint alone distinguishes them.
And cp_epoch_event.stale_return records the guard working. It is not an error — a stale return during a recovery is expected — but a run in which it never occurs has not exercised the guard, and the crossed version distinguishes "stale during recovery" (normal) from "stale during steady state" (alarming).
24. Fault-Family Coverage
Cover the fault family, not the fault count. "Twelve errors were injected" is not a coverage result; "each of six fault families was injected at least once, in three lifecycle moments" is.
// ILLUSTRATIVE. Fault FAMILIES are the environment's own taxonomy (§3) — no
// UCIe error class, code or name is claimed. Sampled at INJECTION and,
// separately, at DETECTION, because the pair is the interesting thing.
typedef enum {
F_INTEGRITY, // corruption after integrity was computed (20.2 §43 pt 3)
F_DROP, // an object destroyed in flight
F_DELAY, // an object or response delayed beyond expectation
F_FEEDBACK_LOST, // an acknowledgement or credit return dropped
F_FEEDBACK_DUP, // a feedback message duplicated (19.5 §24)
F_LANE_HEALTH, // a lane becomes unusable
F_TRAINING_STALL, // training progress withheld
F_PEER_SILENT // the far end stops responding
} fault_family_e;
covergroup cg_reliability;
cp_injected: coverpoint injected_family {
bins families[] = {F_INTEGRITY, F_DROP, F_DELAY, F_FEEDBACK_LOST,
F_FEEDBACK_DUP, F_LANE_HEALTH, F_TRAINING_STALL,
F_PEER_SILENT};
}
// Was the expected detector the one that fired? 20.2 §43's injection record.
cp_detection: coverpoint detection_outcome {
bins expected_detector = {DO_EXPECTED};
bins other_detector = {DO_UNEXPECTED}; // a finding, not a coverage goal
bins undetected = {DO_NONE}; // a finding
}
// Attempts per object — the retry distribution (20.4 §51).
cp_attempts: coverpoint attempts_per_object {
bins one = {1};
bins two = {2};
bins several = {[3:MAX_ATTEMPTS-1]};
bins at_budget = {MAX_ATTEMPTS}; // §22's boundary
}
// Arrivals versus attempts — this ratio says WHICH fault was injected.
cp_arrival_ratio: coverpoint arrival_vs_attempt {
bins equal = {AR_EQUAL}; // corruption: it arrived, badly
bins one_fewer = {AR_ONE_FEWER}; // a drop: it never arrived
bins surplus = {AR_SURPLUS}; // a duplicate transmission
}
x_family_x_attempts: cross cp_injected, cp_attempts;
endgroupThree notes.
cp_detection.other_detector and .undetected are findings, not goals. A fault detected by a mechanism other than the expected one means the injection record's expectation was wrong or the detection responsibility is misplaced; an undetected fault means the detection mechanism does not work. Both belong in the coverage report because 20.2 §43's injection record names the expected detector, and comparing expectation against outcome is a coverage question with an error-shaped answer.
cp_arrival_ratio is the discriminator that turns fault injection into evidence. 20.4 §51: equal counts mean the object arrived and failed integrity; one fewer means it was destroyed in flight; a surplus means it was duplicated. A plan that covers only "a fault was injected" cannot confirm the injection did what it intended.
And F_FEEDBACK_LOST / F_FEEDBACK_DUP are the two families nobody injects. 19.5 §24's duplicate return and 18.4 §20's lost return both live here, and both produce a link that slowly stops with every safety property passing.
25. Error-Timing Coverage
The same fault at a different moment is a different test. This cross is where timing-dependent bugs live, and it is the cross most plans omit.
// ILLUSTRATIVE. WHEN the fault landed, as a category rather than a cycle.
typedef enum {
T_DURING_TRAINING, // before the link exists
T_FIRST_OBJECT, // the very first object — an initialisation corner
T_STEADY_STATE, // the easy case
T_AT_MAX_OCCUPANCY, // structures full (19.4 §29's headroom)
T_BEFORE_RESOLUTION, // after transmit, before the verdict (20.2 §36)
T_DURING_RECOVERY, // a second fault while recovering
T_DURING_CFG_COMMIT // 19.6 §23's window
} fault_moment_e;
covergroup cg_fault_timing;
cp_moment: coverpoint fault_moment {
bins moments[] = {T_DURING_TRAINING, T_FIRST_OBJECT, T_STEADY_STATE,
T_AT_MAX_OCCUPANCY, T_BEFORE_RESOLUTION,
T_DURING_RECOVERY, T_DURING_CFG_COMMIT};
}
// THE CROSS. Justified below in §31's terms.
x_family_x_moment: cross cp_injected_family, cp_moment {
// Some cells have no architectural meaning (§15).
ignore_bins integrity_before_link =
binsof(cp_injected_family) intersect {F_INTEGRITY} &&
binsof(cp_moment) intersect {T_DURING_TRAINING};
}
endgroupWhy the cross is justified. The §31 question, answered concretely for three cells:
F_INTEGRITY × T_BEFORE_RESOLUTION is 20.2 §36's scenario — a fault after transmit and before the verdict, which is the window in which reliability state must survive a recovery.
F_LANE_HEALTH × T_DURING_CFG_COMMIT is a fault landing inside the quiesce-and-commit window. If the commit machine does not handle a recovery request during CFG_QUIESCE, it hangs there forever (19.6 §22's missing timeout).
F_FEEDBACK_LOST × T_DURING_RECOVERY is a credit return dropped while the agreement is being re-established — which interacts with the epoch guard and is the only way to reach 19.5 §29's straggler window deliberately.
And three of the seven moments cannot be hit by random injection. T_FIRST_OBJECT, T_AT_MAX_OCCUPANCY and T_DURING_CFG_COMMIT all require the injector to synchronise on an observed condition rather than fire on a timer — which is a fault-sequence requirement, not a coverage one, and §55 is how the coverage gap produces it.
26. Recovery-Context Coverage
The centerpiece. Almost every plan has a bin for "a recovery occurred", and almost none has the bins that make recovery verification meaningful.
The reason is 20.2 §21's sentence: a link returning to operational after a recovery is not a link that has just been brought up. It has obligations, a void resource agreement, possibly a different configuration, and a first-fault record. Each of those is a separate coverage dimension.
// ILLUSTRATIVE. The recovery collector. Context is captured at ENTRY; outcome
// at EXIT; and the cross between them is the model's most valuable item.
covergroup cg_recovery_context;
// --- CONTEXT, sampled at recovery ENTRY ---
// How much semantic work was live? THE bin that validates R5.
cp_outstanding: coverpoint outstanding_bucket_at_entry {
bins none = {OB_ZERO}; // the easy case, and the default
bins one = {OB_ONE};
bins few = {OB_FEW};
bins many = {OB_MANY}; // 20.3 §38's per-tag property
}
// Was any transport object unresolved? 20.4 §52's trace.
cp_unresolved_objects: coverpoint unresolved_bucket_at_entry {
bins none = {UB_ZERO}; bins some = {UB_SOME};
}
// Was credit exhausted? 20.2 §36 — the recovery that starts with no credit.
cp_credit_at_entry: coverpoint credit_bucket_at_entry {
bins zero = {CB_ZERO}; // the interesting one
bins partial = {CB_PARTIAL};
bins full = {CB_AT_CAPACITY};
}
// Was a response already in flight? The lost-response ambiguity (20.2 §37).
cp_response_in_flight: coverpoint response_in_flight_at_entry {
bins no = {0}; bins yes = {1};
}
// Was a configuration change pending? 19.6 §23's window.
cp_cfg_pending: coverpoint cfg_change_pending_at_entry {
bins no = {0}; bins yes = {1};
}
// Was traffic bidirectional? 19.1 §41.
cp_bidirectional: coverpoint both_directions_active_at_entry {
bins no = {0}; bins yes = {1};
}
// Was the first-fault record ALREADY valid — a second fault? 19.1 §35.
cp_first_fault_already_set: coverpoint first_fault_valid_at_entry {
bins no = {0}; bins yes = {1};
}
// --- OUTCOME, sampled at recovery EXIT ---
cp_outcome: coverpoint recovery_outcome {
bins restored_same = {RO_SAME};
bins restored_degraded = {RO_DEGRADED}; // 20.2 §30
bins failed = {RO_FAILED}; // 20.2 §42
}
cp_duration: coverpoint recovery_duration_bucket {
bins fast = {RD_FAST};
bins normal = {RD_NORMAL};
bins slow = {RD_SLOW}; // near the permitted budget
}
// --- THE CROSSES (§31) ---
x_outstanding_x_outcome: cross cp_outstanding, cp_outcome;
x_credit_x_outstanding: cross cp_credit_at_entry, cp_outstanding;
x_cfg_x_outstanding: cross cp_cfg_pending, cp_outstanding;
endgroupFour readings.
cp_outstanding.none is the default and the useless one. A regression in which every recovery happened with nothing outstanding exercised the recovery path and never tested preservation — which is 20.3 §38's entire property set and 20.4 §31's entire reset-policy argument. If few and many are empty, R5 is unverified however many recoveries occurred.
cp_first_fault_already_set.yes is the second-fault case. 19.1 §35's sticky-first record can only be shown to be sticky-first if a second fault arrives while the first is recorded — so this bin is what makes 20.3 §38's stickiness property non-vacuous.
x_credit_x_outstanding with zero × many is 20.2 §36's flagship scenario and §46's walkthrough: a recovery entered with obligations live and no credit available, so the agreement must be re-established before any of them can progress.
And cp_duration.slow requires a fault that persists. A run whose recoveries all complete quickly has never exercised the recovery-bound liveness property (20.2 §47) anywhere near its limit.
27. The Recovery-Outcome Cross
x_outstanding_x_outcome is worth its own section because its cells have completely different meanings and one of them is a trap.
| Cell | What it exercises |
|---|---|
none × restored_same | the trivial recovery — every regression has thousands |
many × restored_same | preservation with full restoration — R5's main case |
many × restored_degraded | preservation and reconfiguration together — 20.2 §30 |
many × failed | classification of live obligations on a fatal end — R12, 20.2 §42 |
none × failed | fatal handling with nothing at stake — the easy fatal case |
one × restored_degraded | the minimal reconfiguration-with-work case |
Two properties of this cross.
many × failed is the cell that exposes the worst bug class in the module. Obligations live at a fatal end must each receive a classification — failed, cancelled, unknown or reported (20.2 §41) — and a design that reports success, or silently drops them, is only caught in this cell. It requires a persistent fault injected while work is outstanding, which no random regression produces.
And the trap is that the cross closes easily on the diagonal. none × restored_same fills within minutes and dominates the hit counts. Reading the cross's percentage rather than its cells reports it as nearly closed while the four cells that matter are empty — which is why §58's review table lists what an uncovered cell means rather than only whether it is covered.
28. Semantic, Object and Attempt Coverage
20.1 §22's three-level identity hierarchy, covered. These bins test the verification architecture as much as the design.
// ILLUSTRATIVE. The multiplicity structure of each completed operation,
// computed by the join (20.4 §20).
covergroup cg_identity_hierarchy;
cp_objects_per_operation: coverpoint objects_per_op {
bins one = {1};
bins two = {2}; // fragmentation
bins many = {[3:$]};
}
cp_attempts_per_object: coverpoint attempts_per_obj {
bins one = {1};
bins two = {2}; // one retry
bins several = {[3:$]};
}
cp_deliveries: coverpoint deliveries_per_op {
bins zero_with_failure = {0}; // 20.3 §33's third value
bins exactly_one = {1};
illegal_bins duplicate = {[2:$]}; // §40 — and there is an assertion
}
// Did objects of one operation ever complete out of order?
cp_object_order: coverpoint object_completion_order {
bins in_order = {OO_ORDERED};
bins reordered = {OO_REORDERED}; // legal; must be exercised
}
// The multiplicity cross — the four combinations that matter.
x_objects_x_attempts: cross cp_objects_per_operation, cp_attempts_per_object;
endgroupFour cells of the cross, and each is a distinct verification situation:
| Cell | Situation |
|---|---|
1 object × 1 attempt | the clean path (20.4 §49) |
1 object × 2+ attempts | retry without fragmentation (20.4 §51) |
2+ objects × 1 attempt each | fragmentation without retry — the parts bitmap (20.4 §10) |
2+ objects × 2+ attempts | both at once — one part retried while others succeed |
The last cell is the one that breaks weak environments. A parts bitmap combined with per-object attempt tracking, where one part is retried and another is not — and an environment that stores attempts per operation rather than per object cannot represent it at all, which is why 20.4 §18 keys the transport record per object.
And cp_deliveries uses illegal_bins legitimately by §13's test: the basis is an architectural contract — exactly-once semantic delivery — and there is also an assertion (20.3 §33), which is the primary checker.
29. Ordering Coverage
Do not cover "ordered versus unordered". Cover the four situations that ordering verification actually needs.
// ILLUSTRATIVE. No UCIe ordering rule is claimed (§3). These bins describe
// the ORDERING-GROUP abstraction of 20.3 §22.
covergroup cg_ordering;
// A legal reorder was OBSERVED — required for 20.3 §22's second row.
cp_legal_reorder: coverpoint legal_reorder_observed {
bins no = {0};
bins yes = {1}; // if empty, one order untested
}
// A required dependency was PRESENT and preserved.
cp_dependency: coverpoint dependency_case {
bins none_required = {DC_NONE};
bins required_and_ok = {DC_HONOURED}; // the property fired non-vacuously
}
// Independent responses swapped — the aliasing precondition.
cp_swap: coverpoint independent_swap_observed { bins no = {0}; bins yes = {1}; }
// A stale identity or generation was rejected — 19.2 §21's guard, exercised.
cp_stale_identity: coverpoint stale_identity_event {
bins none = {SI_NONE};
bins rejected = {SI_REJECTED}; // §49's scenario
}
// The immediate-reuse window — 20.3 §67's cover, promoted to a bin.
cp_reuse_gap: coverpoint retire_to_reuse_gap {
bins immediate = {[0:1]}; // the aliasing window
bins short = {[2:15]};
bins long = {[16:$]};
}
x_reuse_x_reorder: cross cp_reuse_gap, cp_legal_reorder;
endgroupTwo notes.
cp_dependency.required_and_ok is what makes the precedence property non-vacuous. 20.3 §22's property only fires when a pair with a required precedence exists. If the stimulus never generates such a pair, the property passes vacuously and the ordering rule is unverified.
And x_reuse_x_reorder with immediate × yes is the aliasing cell. A delayed response for a retired operation meeting its identity's new owner requires both an immediate reuse and a reorder — 19.2 §21's exact precondition. Neither coverpoint alone reaches it, and it is the cross that justifies itself most cleanly under §31.
30. Concurrency Coverage
Combinations that catch race bugs, and they are combinations of events, not of configuration values.
| Combination | The bug it exposes |
|---|---|
| retry + full-duplex traffic | a shared ready or a shared retry resource across directions (19.1 §42) |
| recovery + outstanding obligations | R5 — the preservation failure (20.4 §31) |
| credit consume + return, same cycle | the drift bug (19.5 §13) |
| configuration commit + active traffic | the partial commit (19.6 §23) |
| fault + first-fault already valid | last-capture instead of first-capture (19.1 §35) |
| allocation + retirement, same index | 19.3 §24's simultaneous ring event |
| push + pop at capacity boundary | the occupancy drift at the worst value (19.4 §9) |
| watermark assert + producer already committed | insufficient headroom (19.4 §29) |
// ILLUSTRATIVE. Concurrency as an explicit enumeration — because a cross of
// the underlying dimensions would produce mostly meaningless cells (§32).
typedef enum {
CC_NONE,
CC_RETRY_DUPLEX,
CC_RECOVERY_WITH_WORK,
CC_CREDIT_SIMULTANEOUS,
CC_COMMIT_UNDER_TRAFFIC,
CC_SECOND_FAULT,
CC_ALLOC_RETIRE_SAME,
CC_PUSH_POP_AT_CAPACITY,
CC_WATERMARK_LATE
} concurrency_case_e;
covergroup cg_concurrency;
cp_case: coverpoint concurrency_case {
bins cases[] = {CC_RETRY_DUPLEX, CC_RECOVERY_WITH_WORK,
CC_CREDIT_SIMULTANEOUS, CC_COMMIT_UNDER_TRAFFIC,
CC_SECOND_FAULT, CC_ALLOC_RETIRE_SAME,
CC_PUSH_POP_AT_CAPACITY, CC_WATERMARK_LATE};
}
endgroupWhy this is an enumeration rather than a cross. Each case is a named, hand-chosen combination whose value the plan can defend. A cross of "is there a retry" × "is it duplex" × "is a commit pending" × "is credit zero" × ... produces sixteen or more cells of which eight are these and the rest are noise — §32's failure, and §33's control.
And the enumeration is the coverage counterpart of 20.2 §44's stage-3 fault combinations. Both are hand-chosen interaction hypotheses; the fault plan generates them and this coverage records that they occurred.
31. The Cross-Design Rule
One question decides whether two dimensions should be crossed:
"If these two dimensions interact incorrectly, what specific bug appears?"
If the answer is a concrete, nameable failure, cross them. If the answer is "I'm not sure, but it might be interesting", do not.
Worked, on four candidate crosses:
| Candidate | Answer | Verdict |
|---|---|---|
| width before × width after recovery | headroom and credit capacity derived from the old width are not recomputed (§18) | cross |
| credit-zero × receiver occupancy | zero credit with a non-full receiver is stranded capacity, a different condition entirely (§23) | cross |
| outstanding-at-recovery × recovery outcome | obligations at a fatal end must be classified, not dropped (§27) | cross |
| identity-reuse gap × reorder observed | a delayed response meets the identity's new owner (§29) | cross |
| protocol class × fault family | (none — a fault does not care which class the object carried) | do not cross |
| width class × occupancy bucket | (none — occupancy is in entries, not lanes) | do not cross |
| lifecycle phase × occupancy bucket | (mostly meaningless — structures are empty before operation) | do not cross |
Two consequences.
The justification belongs in the code, next to the cross. A cross with no comment naming its bug is a cross nobody can review or delete — and an unreviewable cross is what §32's model is made of.
And "do not cross" is not the same as "do not cover". Protocol class and fault family both deserve their own coverpoints; what they do not deserve is a cell for every pairing, because the pairing has no distinct failure mode and its cells are indistinguishable from one another in what they prove.
32. Wrong Cross — the Cartesian Product
// WRONG — every dimension crossed with every other.
covergroup cg_everything;
cp_width: coverpoint width_class { bins w[] = {[0:5]}; } // 6
cp_rate: coverpoint rate_class { bins r[] = {[0:3]}; } // 4
cp_proto: coverpoint proto_mix { bins p[] = {[0:7]}; } // 8
cp_class: coverpoint traffic_class { bins c[] = {[0:7]}; } // 8
cp_fault: coverpoint fault_family { bins f[] = {[0:7]}; } // 8
cp_phase: coverpoint link_phase { bins s[] = {[0:4]}; } // 5
cp_occ: coverpoint occ_bucket { bins o[] = {[0:6]}; } // 7
cp_credit: coverpoint credit_bucket { bins k[] = {[0:3]}; } // 4
x_all: cross cp_width, cp_rate, cp_proto, cp_class,
cp_fault, cp_phase, cp_occ, cp_credit;
endgroup6 × 4 × 8 × 8 × 8 × 5 × 7 × 4 = 1,720,320 bins.
Five properties, and the fifth is why this is worse than useless.
Most cells are impossible. A fault family crossed with a training phase where that fault cannot occur; an occupancy bucket crossed with a phase in which structures are empty; a credit bucket crossed with a phase in which no agreement exists. The impossible fraction is the large majority.
The rest are mostly indistinguishable. Width class 3 with rate class 2 and protocol mix 5 proves nothing that width class 3 with rate class 2 and protocol mix 4 does not.
Closure becomes bin-count management. The percentage is driven by how many cells the stimulus happens to sweep, so the fastest way to raise it is to randomise more dimensions — which is §56's failure, and it improves the number while improving nothing else.
Diagnostic meaning is gone. An uncovered cell in a million-cell cross cannot be classified by §50, because nobody can say what situation it represents or whether it matters. The classification step, which is the actual value of coverage, becomes impossible.
And it displaces the model that would have worked. Effort spent driving a million meaningless cells is effort not spent on §26's twelve recovery-context bins — and those twelve are where R5 and R12 are verified. The Cartesian model is not merely inefficient; it consumes the budget that the useful model needed.
The corrected architecture is the six collectors of §8 with the twelve targeted crosses this chapter has justified individually — of the order of a few thousand meaningful bins rather than 1.7 million, and every one of them answers a question somebody wrote down.
33. Controlling Cross Growth
Four techniques, in the order to reach for them.
Cross only justified pairs (§31). This is the primary control and the others are secondary.
Bucket before crossing. Crossing occupancy buckets rather than occupancy values turns a depth-256 dimension into seven bins (§21). The buckets must include the boundaries (§22) or the reduction throws away the interesting values.
Use with and binsof filters to remove architecturally meaningless cells rather than leaving them uncovered forever:
// ILLUSTRATIVE. Filter at the cross, with a documented reason (§15).
x_fault_x_phase: cross cp_fault, cp_phase {
// Structures are empty before operation, so occupancy-dependent faults have
// no meaning there.
ignore_bins pre_operational =
binsof(cp_phase) intersect {RM_RESET, RM_TRAINING} &&
binsof(cp_fault) intersect {F_INTEGRITY, F_FEEDBACK_LOST, F_FEEDBACK_DUP};
}And partition into separate covergroups rather than one wide cross. Two three-way crosses in two collectors are far more interpretable than one six-way cross, and each closes independently — so a gap names one collector rather than a coordinate in a hypercube.
34. Assertions and Coverage Are a Pair
Every meaningful requirement needs both, and they answer opposite halves of the same question.
| Assertion | Cover | |
|---|---|---|
| States | the behaviour was correct | the situation occurred |
| Fails | at the illegal cycle | never |
| Silent when | the situation never arose | the situation arose and was wrong |
| Together | correct, and exercised |
Worked, on R3 — exactly-once delivery:
// The ASSERTION: no semantic duplicate, ever. Safety (20.3 §33).
property p_delivered_at_most_once(int unsigned tag);
@(posedge clk) disable iff (!por_n)
(sem_deliver_fire && (sem_deliver_tag == tag))
|=> always !(sem_deliver_fire && (sem_deliver_tag == tag));
endproperty
// The COVER: a retry actually happened, while an obligation was live. Without
// this, the assertion above is satisfied by a design that never retries.
c_retry_with_live_obligation: cover property (
@(posedge clk) (attempt_fire && (attempt_num > 1) && chk_outstanding_any));
// And the COVERAGE ITEM that quantifies it (§28).
// cp_attempts_per_object.two and .several must both be non-empty.Three notes.
The assertion alone is satisfiable by a design that never retries. At most once is trivially true of zero. 20.3 §34's pairing argument, and this is its coverage form.
The cover alone proves nothing about correctness. It says the retry happened; the assertion says the retry did not duplicate.
And the coverage item is a third thing. The cover property is a boolean — did it ever happen. The coverage item is a distribution — how many attempts, in what contexts — which is what tells the plan whether one retry was exercised or the full retry budget.
35. Assertion Activation Coverage
A property that never activates adds no confidence, and its passing row is the most misleading line in a regression report.
Three quantities, reported together (20.3 §49):
| Quantity | Finds |
|---|---|
| antecedent cover | vacuity — the trigger never occurred |
| pass count | a property that was never bound at all |
| fail count | the defect |
Read together: non-zero pass count + covered antecedent + zero failures = genuinely checked. Non-zero pass count + uncovered antecedent = a vacuous property counted as the strongest evidence in the report.
And the quantity that is not coverage: the number of properties. A plan reporting "we have 340 assertions" has reported a code metric. Three hundred and forty properties of which forty are vacuous and twelve are unbound is a weaker plan than eighty properties all demonstrated to activate — and only these three columns distinguish them.
36. Code Coverage — What It Is Evidence Of
Statement, branch, expression, toggle and FSM coverage are evidence about the implementation. They are useful and they are not requirement coverage.
| Kind | Answers | Cannot answer |
|---|---|---|
| statement / block | was this line executed? | was it executed in the situation that matters? |
| branch | were both directions taken? | were they taken with the right state elsewhere? |
| expression | were the sub-conditions independently exercised? | is the expression the right expression? |
| toggle | did this signal change value? | did it change for an architecturally meaningful reason (§37) |
| FSM state / arc | were all states and arcs reached? | were the arcs reached in legal sequences, from legal contexts? |
Two properties.
Code coverage finds dead logic, which functional coverage cannot. A branch nothing ever takes is either unreachable — a design simplification opportunity — or a feature nobody tested. That is a real and distinct finding, and it is why code coverage belongs in the plan.
And 100% code coverage is compatible with a completely unverified architecture. Every line of a recovery handler can execute during recoveries with nothing outstanding; every line of a credit machine can execute without a single simultaneous consume-and-return. 19.5 §42's batching deadlock executes no unusual line — the deadlocked state is reached by ordinary lines in an ordinary order. Code coverage reports it as fully covered.
Code coverage is a floor: below 100% something is unexercised. It is never a ceiling: 100% says nothing about which scenarios occurred.
37. The Toggle-Coverage Trap
Worked, and it is the sharpest illustration of §36.
A configuration register bit — "degraded width supported" — is written once by reset to 0, and once at initialisation to 1.
Toggle coverage: both values seen. 100%. Covered.
What was never tested: a runtime transition of that bit, a configuration commit that changes it, the quiesce path around that commit (19.6 §22), or any traffic under the changed configuration.
Three properties.
The toggle happened for the wrong reason. Reset initialisation is not a configuration change; the architectural situation the bit exists to represent was never entered.
Functional scenario coverage distinguishes them and toggle coverage cannot, because toggle coverage has no concept of why a value changed. §16's cp_commit_context is the bin that separates a commit at initialisation from a commit under traffic.
And the same trap applies to FSM arc coverage. An arc taken once during bring-up is "covered" — so RECOVERY → OPERATIONAL reached once, in a recovery with nothing outstanding, closes the arc while §26's entire context dimension is empty. The arc is a level-5 measurement of a level-2 question.
38. The Collector Architecture
// ILLUSTRATIVE. Collectors consume MONITOR EVENTS and MODEL state, exactly as
// scoreboards do (20.4 §6). They are peers of the models, not part of them.
class ucie_cov_base;
ucie_semantic_model sem; // read-only handles for derived bins
ucie_transport_model trans;
ucie_resource_model res;
ucie_link_model link;
bit enabled; // §8 — each collector switchable
pure virtual function void on_event(ucie_event e);
endclass
class ucie_recovery_cov extends ucie_cov_base;
// Context captured at entry, held until exit — the cross of §27 spans two
// moments, so the collector needs state.
occ_bucket_e captured_outstanding;
credit_bucket_e captured_credit;
bit captured_response_in_flight;
longint entry_cycle;
function void on_event(ucie_event e);
if (!enabled) return;
case (e.kind)
EVT_RECOVERY_ENTER: begin
captured_outstanding = bucket_ob(sem.outstanding_count());
captured_credit = bucket_credit(res.expected_credit(0));
captured_response_in_flight = trans.any_awaiting_response();
entry_cycle = e.cycle;
cg_recovery_context.sample(); // the CONTEXT half
end
EVT_RECOVERY_EXIT: begin
recovery_outcome = classify_outcome(link);
recovery_duration_bucket = bucket_duration(e.cycle - entry_cycle);
cg_recovery_context.sample(); // the OUTCOME half + crosses
end
default: ;
endcase
endfunction
endclassThree notes.
The collector holds state, and that is expected. §27's cross spans recovery entry and exit, so the context must be captured and held. A collector that samples only at exit has lost the context; one that samples only at entry has lost the outcome.
It reads the models, not the design. sem.outstanding_count() is the environment's independently maintained count (20.4 §10) — not a design register, which is §42.
And enabled is per collector (§8). A collector that cannot be switched off individually will be switched off collectively the first time a long run needs the cycles.
39. A Subscriber Collector
The bridge into 20.6. A collector is naturally a subscriber: it consumes an observation stream and holds no opinions about correctness.
// ILLUSTRATIVE. 20.6 owns the environment architecture; this is the shape the
// coverage model imposes on it.
class ucie_recovery_cov_sub extends uvm_subscriber #(ucie_event);
`uvm_component_utils(ucie_recovery_cov_sub)
ucie_recovery_cov cov;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cov = new();
// Model handles arrive by configuration, not by hierarchy peeking.
if (!uvm_config_db #(ucie_model_bundle)::get(this, "", "models", models))
`uvm_fatal(get_type_name(), "model bundle not configured")
cov.sem = models.sem; cov.trans = models.trans;
cov.res = models.res; cov.link = models.link;
endfunction
function void write(ucie_event t);
cov.on_event(t); // one entry point; no correctness logic here
endfunction
endclassTwo notes.
write() does one thing. 20.4 §9's monitor discipline applied to subscribers: a subscriber that also checks, logs and formats is a component that cannot be reused or tested alone.
And the model handles arrive by configuration. Not by uvm_top lookup, not by a global, and not by reaching up the hierarchy — because a collector that finds its models by hierarchy path cannot be instantiated twice or moved. 20.6 develops the configuration architecture; the requirement originates here.
40. Parameterised Covergroups
Bins must follow the parameters, or the model is correct for one build.
// ILLUSTRATIVE. Every bound comes from a parameter or the capability structure
// (19.6 §20). No literal bin values anywhere (19.6 §45's rule, in coverage).
covergroup cg_parameterised;
cp_class: coverpoint traffic_class {
bins c[] = {[0:NUM_CLASSES-1]}; // follows the parameter
}
cp_occupancy: coverpoint occ_bucket {
// Buckets computed from DEPTH (§21), not hardcoded ranges.
bins empty = {OCC_EMPTY};
bins one = {OCC_ONE};
bins near_full = {OCC_NEAR_FULL};
bins full = {OCC_FULL};
bins mid[3] = {[OCC_LOW:OCC_HIGH]};
}
cp_credit: coverpoint credit_bucket {
bins zero = {CB_ZERO};
bins at_cap = {CB_AT_CAPACITY}; // = active_capacity
bins mid[4] = {[CB_ONE:CB_BELOW_CAP]};
}
endgroup
// And the feature-conditional bins (§3, §15): a replay-disabled build has no
// reachable retry bins, so they are IGNORED for that build rather than left
// permanently uncovered.
generate
if (!REPLAY_ENABLE) begin : g_no_replay_cov
// The retry coverpoints are not instantiated at all in this build.
end
endgenerateThree notes.
bins c[] = {[0:NUM_CLASSES-1]} creates one bin per class automatically. A hardcoded four-bin list is wrong on an eight-class build and reports full class coverage on half the classes — 19.6 §53's hardcoded-default failure in the coverage model.
Bucket boundaries are symbolic enum values, computed by a function from DEPTH (§21). The covergroup never sees a numeric depth, which is what makes it survive a parameter sweep.
And feature-conditional coverage is generated per build, not left uncovered. §15's second legitimate use: a replay-disabled regression reporting a permanent shortfall trains the team to ignore the number.
41. The Dynamic-Bin Caveat
Covergroup bins are fixed once the covergroup is constructed, and the supported value set may not be known until configuration is read. Three practical approaches, and one to avoid.
Approach 1 — normalise to categories (preferred). Define a fixed enumeration of classes — minimum, nominal, maximum, degraded — and map the runtime value to a class at sample time (§17). The bin structure is static; the mapping is dynamic. This is what every width, occupancy and credit coverpoint in this chapter does.
Approach 2 — one covergroup instance per configuration. Construct the collector after configuration is known, with bins sized from it. Practical where the number of configurations is small.
Approach 3 — a superset with per-build exclusions. Bins for every architecturally supported value, with ignore_bins applied per build from the capability structure (§40). Costs a slightly larger denominator and keeps one model across products.
And the approach to avoid: generating bin expressions from strings or building the covergroup by macro expansion per value. It works, it is unmaintainable, and the resulting model cannot be reviewed — which forfeits the entire point of §58's review table.
42. Coverage-Database Independence
A coverage model built on the design's internal encodings is a coverage model that cannot record that the design did something wrong.
Sample from monitor events and model state. Sample a design-internal signal only when the item is explicitly a white-box implementation-coverage item, and label it.
Three reasons, in increasing order of consequence.
Portability. An internal enum changes on a refactor and the coverage database becomes non-comparable across releases — so the trend line, which is the main use of a coverage database over time, is lost.
Interpretability. A bin named after an internal state does not answer an architectural question. §58's review table has a column for the architectural question, and an internal-state bin cannot fill it.
And correctness of the measurement. 20.2 §9's bug is a design that enters its ACTIVE state having committed an unconfirmed configuration. A coverage model sampling the design's state records that as an operational entry — it agrees with the design that the link is up. The environment's own five-phase model records that the operational phase was never legitimately entered (§11), which is a completely different measurement of the same run.
The legitimate white-box exception, labelled:
// WHITE-BOX IMPLEMENTATION COVERAGE. Non-portable by construction (20.3 §54).
// Lives in a separate collector so it can be dropped without touching the
// architectural model.
covergroup cg_impl_wb;
cp_replay_ptr_wrap: coverpoint replay_ptr_wrapped { bins yes = {1}; }
cp_freelist_empty: coverpoint freelist_empty { bins yes = {1}; }
cp_fsm_encoding: coverpoint dut_state_q { bins s[] = {[0:5]}; }
endgroup43. Wrong Collector — Sampling the Design's Enum
// WRONG for an architectural coverage item — samples the DUT's state register.
covergroup cg_lifecycle_bad @(cfg_commit_event);
cp_phase: coverpoint dut.link_state_q { // the DESIGN's encoding
bins reset = {RESET}; bins train = {MBTRAIN};
bins active = {ACTIVE}; bins retrain = {PHYRETRAIN};
}
endgroupFour consequences.
It agrees with the design about which phase the link is in, including phases the link should not have been in — 20.2 §9's bug recorded as normal operation.
It cannot represent the environment's distinction between training and recovery. 20.2 §7's was_operational flag: a restart and a recovery have different expectations, and if the design uses one state for both, so does the coverage model — so §26's entire context dimension becomes unattributable.
It breaks on a state-machine refactor, taking the historical coverage database with it (§42).
And it makes the bins unreviewable. A bin named MBTRAIN answers "was the design in its MBTRAIN state?" — which is not an architectural question anybody wrote down, and §58's review table has no row for it.
The corrected collector samples the link model's phase (§11), which is derived from observed evidence and belongs to the environment.
44. The Flagship Coverage Model
Six collectors, roughly a few thousand meaningful bins, twelve justified crosses. This is the model §32's Cartesian product displaces.
| Collector | Coverpoints | Justified crosses |
|---|---|---|
link_cov | phase entered, arc, training attempts | — |
config_cov | negotiation outcome, protocol mix, reliability mode, commit context, commit spans operation, configured width class, active width class | outcome × reliability mode; commit context × outcome |
resource_cov | occupancy bucket, watermark event, simultaneous push/pop, credit bucket, credit update, delta sign, epoch event, flush reason, partial batch, progress reserve | credit-zero × RX occupancy; stale × phase |
reliability_cov | injected family, detection outcome, attempts per object, arrival ratio, fault moment | family × attempts; family × moment |
recovery_cov | outstanding at entry, unresolved objects, credit at entry, response in flight, config pending, bidirectional, first-fault already set, outcome, duration | outstanding × outcome; credit × outstanding; config-pending × outstanding |
traffic_cov | class, class concurrency, asymmetric exhaustion, starvation window, direction activity, asymmetric progress, objects per operation, attempts per object, deliveries, object order, reorder observed, dependency case, swap observed, stale identity, reuse gap, concurrency case | width before × after; objects × attempts; reuse gap × reorder; direction × asymmetric |
Four properties of this model.
Every cross has a named bug (§31), written next to it in the source.
Six collectors close independently, so a gap names its owner. A recovery_cov gap is a fault-sequence problem; a traffic_cov gap is a stimulus-mix problem — different people, different next actions.
The bins with the highest value are the rarest. recovery_cov's many × failed cell, resource_cov's partial batch, traffic_cov's immediate reuse × reorder — all three require deliberate, synchronised stimulus and none will appear by accident. §55 is how the gaps produce that stimulus.
And the model is reviewable. Roughly sixty coverpoints and twelve crosses fit in §58's table, and a reviewer can ask of each row "what does an uncovered result mean?" — which is the question a million-cell hypercube makes unanswerable.
45. Wrong Model, Corrected
Side by side, on the same regression.
| Cartesian model (§32) | Targeted model (§44) | |
|---|---|---|
| Bins | 1,720,320 | ~a few thousand |
| Reachable fraction | a small minority | nearly all |
| Reported coverage after a long random regression | e.g. 8% | e.g. 74% |
| What the shortfall means | unknown — cells are uninterpretable | a list of named questions |
| Fastest way to raise the number | randomise more dimensions (§56) | write the missing scenario |
| Fits in a review | no | yes (§58) |
| Finds R5 unverified | no | yes — recovery_cov outstanding bins empty |
The row that matters is the last one. In the Cartesian model, the cells covering recovery with many outstanding obligations exist — buried among 1.7 million others, contributing 0.0002% to the percentage, invisible in any report anybody reads. In the targeted model they are four of twelve cells in one cross, and their emptiness is the headline finding.
And the second-to-last row is the honest cost. 74% is a worse-looking number than a Cartesian model tuned by sweeping dimensions. The targeted model is harder to close and its number means something, which is §5's argument stated as a trade.
46. Scenario 1 — Recovery With a Live Request and Zero Credit
20.2 §36's trace, read as coverage. One obligation accepted and attempted, credit exhausted, a fault, a recovery, and a completion afterwards.
| Collector | Coverpoint | Bin hit |
|---|---|---|
link_cov | cp_arc | oper_to_recovery, then recovery_to_oper |
link_cov | cp_phase_entered | recovery, then oper |
recovery_cov | cp_outstanding | one |
recovery_cov | cp_credit_at_entry | zero |
recovery_cov | cp_unresolved_objects | some |
recovery_cov | cp_response_in_flight | no |
recovery_cov | cp_outcome | restored_same |
recovery_cov | x_credit_x_outstanding | zero × one |
recovery_cov | x_outstanding_x_outcome | one × restored_same |
resource_cov | cp_epoch_event | rebaseline |
resource_cov | cp_value | zero, then at_cap |
reliability_cov | cp_injected | F_INTEGRITY (or the injected family) |
reliability_cov | cp_moment | T_BEFORE_RESOLUTION |
reliability_cov | cp_attempts | two |
reliability_cov | cp_arrival_ratio | one_fewer — the first attempt never arrived |
traffic_cov | cp_attempts_per_object | two |
traffic_cov | cp_deliveries | exactly_one |
config_cov | cp_commit_context | busy — the re-commit happened with work outstanding |
Four readings.
Eighteen bins from one scenario, across five collectors. That density is the argument for scenario-driven stimulus: a hand-written scenario hits bins in every collector simultaneously, which random traffic reaches only by coincidence.
x_credit_x_outstanding = zero × one is the cell this scenario exists for. A recovery entered with an obligation live and no credit available means the obligation cannot progress until the agreement is re-established — so it exercises R5, R6 and R7 in one run.
cp_arrival_ratio.one_fewer confirms the injection did what it intended. Two attempts and one arrival means the first was destroyed in flight. Had the ratio been equal, the injected fault was a corruption rather than a drop — a different test, and the bin is what distinguishes them (§24).
And config_cov.cp_commit_context.busy is hit as a side effect. The re-negotiation at recovery exit is a configuration commit, and it happens with one obligation outstanding. A plan that only commits configuration from an idle state would have this bin empty — and R8 unverified.
47. Scenario 2 — A Legal Degraded Recovery With a Retry
20.2 §30's trace plus a retry. Three obligations outstanding, a lane lost, recovery to a narrower width, and one object retried afterwards.
| Collector | Coverpoint / cross | Bin hit |
|---|---|---|
recovery_cov | cp_outstanding | few |
recovery_cov | cp_outcome | restored_degraded |
recovery_cov | x_outstanding_x_outcome | few × restored_degraded |
recovery_cov | cp_bidirectional | yes |
config_cov | cp_outcome | degraded |
config_cov | cp_configured / cp_active | nominal / degraded |
config_cov | cp_degraded_operation | yes |
cg_width_recovery | cp_before / cp_after | nominal / degraded |
cg_width_recovery | cp_direction | narrower |
cg_width_recovery | x_before_after | nominal × degraded |
reliability_cov | cp_injected | F_LANE_HEALTH |
reliability_cov | cp_moment | T_STEADY_STATE |
reliability_cov | cp_attempts | two |
traffic_cov | cp_objects_per_operation | one |
traffic_cov | x_objects_x_attempts | 1 object × 2 attempts |
link_cov | cp_arc | oper_to_recovery, recovery_to_oper |
Three readings.
x_before_after = nominal × degraded is the cell §18 was built for. It exercises whether headroom, watermarks and credit capacity derived from the wide configuration are recomputed for the narrow one — and a design that computes them once at bring-up is wrong here and correct in every other cell.
few × restored_degraded combines two dimensions that are usually tested separately. Degradation is normally tested from idle; preservation is normally tested with a same-width recovery. The cell where both happen at once is the one where 20.2 §28's false-failure checker gets written, because the width changed and the obligations must be unchanged.
And the retry after the recovery matters. The retransmission happens under the new configuration while the obligation carries the old configuration epoch (20.4 §52) — so cp_commit_spans_operation is also hit, which is R8's coverage item.
48. Scenario 3 — Full Duplex With One Direction Backpressured
| Collector | Coverpoint / cross | Bin hit |
|---|---|---|
traffic_cov | cp_activity | full_duplex |
traffic_cov | cp_asymmetric | tx_stalled |
traffic_cov | x_activity_x_asymmetric | full_duplex × tx_stalled |
traffic_cov | cp_concurrency | two or several |
traffic_cov | cp_asymmetric_exhaustion | one_exhausted |
traffic_cov | cp_starvation_window | medium or long |
resource_cov | cp_value | zero on the stalled direction's class |
resource_cov | x_zero_credit_x_occupancy | zero × below capacity — stranded |
resource_cov | cp_flush_reason | timer if returns were batched |
resource_cov | cp_partial_batch | partial |
resource_cov | cp_simultaneous | both — the operating point |
Four readings.
full_duplex × tx_stalled is the cell 19.1 §42's shared-ready bug lives in. Under tx_only load the stall is expected; under symmetric duplex load both directions stall together and it looks correct. Only one direction stalled while the other has work distinguishes a shared ready from independent ones.
x_zero_credit_x_occupancy = zero × below capacity is the stranded-capacity signature (19.5 §63) — and it is a completely different condition from zero credit with a full receiver. The cross is what separates a congested link from a stalled one.
cp_partial_batch.partial is hit here, which is 19.5 §42's deadlock precondition. The scenario reaches it because a backpressured direction produces exactly the burst-then-quiet pattern the partial flush needs.
And cp_asymmetric_exhaustion.one_exhausted exercises 19.5 §9's scalar-pool failure — one class at zero while another has room, which a global credit counter handles wrongly in both directions.
49. Scenario 4 — A Stale Epoch Event After Recovery
19.5 §29's straggler and 19.2 §21's aliasing, in one scenario. A recovery completes; a credit return tagged to the dead agreement arrives; and separately a delayed response arrives for an identity that was reused.
| Collector | Coverpoint / cross | Bin hit |
|---|---|---|
resource_cov | cp_epoch_event | stale_return |
resource_cov | x_stale_x_phase | stale × operational — after the recovery completed |
resource_cov | cp_epoch_event | rebaseline (at the re-advertisement) |
resource_cov | cp_value | at_cap — exactly the advertisement, not more |
traffic_cov | cp_stale_identity | rejected |
traffic_cov | cp_reuse_gap | immediate |
traffic_cov | cp_legal_reorder | yes |
traffic_cov | x_reuse_x_reorder | immediate × yes |
recovery_cov | cp_outstanding | few |
recovery_cov | cp_response_in_flight | yes |
reliability_cov | cp_injected | F_FEEDBACK_LOST or F_DELAY |
reliability_cov | cp_moment | T_DURING_RECOVERY |
Four readings.
x_stale_x_phase = stale × operational is the alarming cell, and it should be empty. A stale return during the recovery is expected; a stale return arriving after the link is operational again means the guard is being exercised in steady state, which points at an epoch advanced by something that should not advance it (19.6 §27's diagnostic-clear row). This is a bin whose presence is the finding, which §58's table has a column for.
immediate × yes is 19.2 §21's precondition exactly. An identity retired and immediately reused, plus a reordered response — neither coverpoint alone reaches it, and this is the cross's entire justification under §31.
cp_stale_identity.rejected records the guard working, not an error. Its emptiness would mean the generation guard has never been exercised, so 20.3 §20's uniqueness property is vacuous.
And T_DURING_RECOVERY is the moment bin that makes this scenario reachable deliberately. A feedback message dropped while the agreement is being re-established is the only way to produce the straggler window on purpose — which is why §25's fault-moment dimension exists rather than injecting on a timer.
50. Closure — Six Classifications
Every uncovered bin gets exactly one classification, and each has a different owner and a different next action. "Waived" is not one of them.
| # | Classification | Evidence required | Owner | Action |
|---|---|---|---|---|
| 1 | stimulus missing | none — the default hypothesis | DV | write a sequence or relax a constraint within the plan (§56) |
| 2 | the environment prevents it | the restricting component named | DV | fix the environment |
| 3 | the design prevents it — a bug | the architecture permits it; the design refuses | design | a defect found by coverage |
| 4 | architecturally unreachable | a proof (§51) | architecture | exclude with ignore_bins + the record (§15) |
| 5 | the requirement is obsolete | the requirement removed from the plan | architecture | remove the bin and the requirement row (§7) |
| 6 | the coverage model is wrong | the bin's definition inspected | DV | fix the bin |
Four rules.
Classification 1 is the default and must be disproved, not assumed away. Most uncovered bins are missing stimulus, and that is the cheapest thing to fix. A bin jumping straight to classification 4 without a proof is a waiver wearing a classification's name.
Classification 3 is the one people forget coverage can produce. A bin the architecture permits and the design cannot enter is a defect — and it is discovered by a mechanism that never fails a test (§4).
Classification 5 must remove the requirement row too. A bin deleted while its requirement stays in §7's table leaves a requirement with no coverage item, which is exactly the gap that table exists to make visible.
And every classification is recorded with the bin, in the plan, not in a meeting. §52 is the record.
51. Unreachability Requires a Proof
"Never hit in regression" is not a proof of unreachability. It is classification 1.
Four acceptable proofs:
| Proof | Example |
|---|---|
| a formal unreachability result | a bounded or complete proof that the state is not reachable (20.3 §62) |
| an architectural constraint | the combination contradicts a stated contract |
| a parameter legality rule | the configuration is illegal by 19.6 §13's legality function |
| a specification exclusion | the specification forbids it — with the clause cited |
Three notes.
The formal proof is the strongest and it is often cheap for exactly these bins. Unreachability of a state combination is a small, bounded question — the kind 20.3 §62 lists as an excellent formal target — and it is far cheaper than months of regression evidence that proves nothing.
A parameter-legality proof is per build, so the exclusion must be per build too (§40). An exclusion applied globally because it holds in one configuration is wrong in the others.
And the proof has an expiry. An architectural constraint can change in the next revision; a parameter can be widened. §52's record carries a review version for exactly this reason — an unreachability proof against UCIe 2.0 is not automatically valid against 3.0.
52. The Waiver Record
Where an exclusion is genuinely correct, it needs six fields. Anything less is waiver sprawl.
| Field | Why |
|---|---|
| bin | precisely which bin or cross cell — not "the recovery bins" |
| classification | which of §50's six |
| reason | one sentence, in architectural terms |
| evidence | the proof (§51), or the named environment restriction |
| owner | a person, not a team |
| review version | the revision or release at which this must be re-examined |
// The record, in the source, next to the exclusion. §15's form, completed.
//
// BIN: x_before_after[WC_MIN × WC_MAX]
// CLASSIFICATION: 4 — architecturally unreachable
// REASON: a recovery cannot widen from the minimum to the maximum
// width in one step in this architecture; renegotiation
// proceeds through the intermediate classes.
// EVIDENCE: architecture contract; formal unreachability proof
// <reference>, bounded to depth 40, converged.
// OWNER: <name>
// REVIEW AT: next specification revision adoption
ignore_bins min_to_max_in_one_step =
binsof(cp_before) intersect {WC_MIN} && binsof(cp_after) intersect {WC_MAX};Three properties.
The review version is what prevents permanent sprawl. A waiver with no expiry outlives the reason for it, and a coverage model accumulating unreviewed exclusions converges to 100% of nothing.
The owner is a person because a team owns nothing. When the review comes due, somebody must be asked.
And the record lives in the source, not in a spreadsheet. A waiver in a separate document drifts from the bin it waives — and the drift is invisible, because the coverage report shows the exclusion applied and the document shows a reason for a bin that no longer exists.
53. Coverage Merge, and What It Hides
Merging coverage across a regression is correct and it hides one specific thing.
What merging does correctly. Different tests contribute different bins; the union is the regression's coverage; and no single test needs to hit everything.
What it hides: a temporal combination that no single run achieved.
Worked. Test A covers cp_outstanding.many. Test B covers cp_outcome.restored_degraded. The merged report shows both bins covered.
And x_outstanding_x_outcome[many × restored_degraded] was never hit by either, because A recovered to the same width and B had nothing outstanding.
Three consequences.
A cross cannot be satisfied by merging its axes. The cell requires both conditions in one run, at one moment — which is precisely what a cross is for, and it is why the interesting items in §44 are crosses rather than coverpoints.
So the plan must encode combinations explicitly as crosses or as scenario enumerations (§30). A pair of coverpoints and a hope that some test hits both together is not a coverage item.
And a merged 100% across coverpoints with empty cross cells is the most flattering possible report of an under-verified design. §58's table lists crosses as separate rows for this reason.
54. Seed Diversity
Ten thousand seeds do not replace targeted scenarios, and the reason is structural rather than statistical.
What random seeds do well. Explore within the space the constraints describe. Different orderings, different interleavings, different value combinations inside the declared ranges — and they find interactions nobody hypothesised, which is real value.
What they cannot do. Reach a scenario the constraints cannot express. 19.5 §42's batching deadlock needs a burst followed by silence — and a constraint set that keeps traffic flowing, which is what a throughput-oriented random test does, can run for a billion cycles without ever producing the quiet period. No seed helps.
Three examples of scenarios no seed count reaches:
| Scenario | Why random misses it |
|---|---|
| burst then silence (19.5 §42) | the constraint set never stops traffic |
| a fault at the first object (§25) | requires synchronising on "first" |
| a fault during a configuration commit (§25) | requires synchronising on the commit window |
| an identity reused within one cycle of retirement (§29) | requires driving the allocation immediately after a retirement |
| a persistent fault exhausting the retrain budget (§11) | requires a fault that does not stop |
And the correct division of labour: constrained random explores inside an architecture plan; directed scenarios reach the corners the plan names. A regression that is all random has an unreachable set it cannot enumerate; one that is all directed misses what nobody thought of.
55. Coverage-Guided Stimulus
The useful loop, and the abuse of it.
The useful loop:
- run the regression;
- read the uncovered list, classified per §50;
- for classification 1, ask what scenario would produce this bin?;
- write that scenario — a sequence, a fault-sequence synchronisation, a constraint that widens a legitimate range;
- rerun; the bin is hit and the scenario is now a permanent, named test.
Step 4's output is the valuable artefact, not the closed bin. A directed scenario written to reach a bin remains in the regression forever and exercises the checkers around it on every run.
Worked, on the highest-value gap in §44. recovery_cov.x_outstanding_x_outcome[many × failed] is empty. Step 3's question: what produces many outstanding obligations at a fatal end? A persistent fault, injected while traffic is in flight, that exhausts the retrain budget (20.2 §42). Step 4 writes that fault sequence — and the resulting test is the only test in the suite that exercises R12's obligation classification.
And the abuse: bending constraints to hit arbitrary bins. §56.
56. Wrong Closure — Loosening Constraints
// WRONG — the constraint is widened to make bins fill, not to reach a scenario.
class ucie_traffic_seq_bad extends uvm_sequence #(ucie_semantic_item);
constraint c_class {
// Originally: cls inside {[0:NUM_CLASSES-1]} with a realistic weighting.
// "Loosened" to fill the class × occupancy cross faster:
cls dist { [0:NUM_CLASSES-1] :/ 1 }; // uniform, unrealistic
// And the burst length randomised far beyond anything a real client sends:
burst_len inside {[1:1024]};
}
endclassFour consequences.
Coverage rises and confidence does not. The bins fill; the situations they represent are not situations the design will meet, so the checkers exercised around them were exercised on unrealistic traffic.
Realistic traffic patterns get less coverage. Uniform class distribution means the class mix a real client produces — heavily skewed — is now a rare case in the regression. The bins that matter for the shipping configuration are hit less often than before the change.
It can mask a real gap. A cross cell that was empty because realistic traffic never reaches it is now filled by unrealistic traffic. The gap is closed on paper and the question it represented is unanswered.
And it inverts the relationship between plan and measurement. Coverage exists to serve the verification plan. A constraint changed to serve the coverage number has made the measurement the goal — and §5's argument is that the measurement was never the goal.
The test for any constraint change: does the new constraint describe traffic the design must actually handle? If yes, the change is a plan improvement and the coverage gain is a side effect. If no, the coverage gain is the only effect.
57. Convergence
A concept, not a dashboard. The useful quantity is new meaningful bins per regression iteration, and it has three regimes.
| Regime | Reading |
|---|---|
| new bins arriving steadily | the plan is being worked; stimulus development is productive |
| no new bins, uncovered list short | approaching maturity — check the remaining list against §50 |
| no new bins, uncovered list long | stimulus is stuck — the remaining bins need scenarios random traffic cannot reach (§54) |
Two notes.
The third regime is the common one and it is diagnosable. A long uncovered list that stops shrinking means the remaining bins are all outside the constraint space. Adding seeds will not help; §55's loop will.
And a flat curve is not the same as closure. A regression that stopped finding new bins because its stimulus cannot reach the remaining ones looks exactly like a regression that has covered everything reachable — and the difference is the classification of the remaining list, which is the only thing that distinguishes them.
58. The Coverage Review Table
The chapter's centerpiece artefact. Every coverage item answers six questions, and an item that cannot fill all six columns does not belong in the model.
| Coverage item | Architectural question | Sample event | Crossed with | Paired checker | An uncovered result means |
|---|---|---|---|---|---|
cp_arc | were all lifecycle paths taken, including failures? | phase transition | — | 20.2 §13 | a path — often a failure path — was never taken |
cp_train_attempts.exhausted | is the retrain budget enforced? | training outcome | — | 20.2 §24 | bounded-retry termination is untested |
cp_outcome.none (config) | can the link refuse an incompatible peer? | config exchange | reliability mode | 20.2 §20 | the refusal path has never executed |
cp_commit_context.busy | is the commit atomic under traffic? | config commit | outcome | 20.3 §37 | R8 is untested; partial commit undetected |
x_before_after | is capacity recomputed when width changes? | recovery exit | — | 20.2 §28 | R10 untested; stale headroom undetected |
cp_bucket.full / .near_full | are the capacity boundaries reached? | push / pop | credit | 20.3 §29 | overflow and off-by-one paths untested |
cp_simultaneous.both | is the simultaneous update correct? | push / pop | — | 19.5 §16 | the operating point was never reached |
cp_partial_batch.partial | does the flush timer ever fire? | return emission | — | 19.5 §44 | the batching deadlock is unverified |
cp_epoch_event.stale_return | is the epoch guard exercised? | credit return | link phase | 20.3 §28 | the guard has never been tested |
x_stale_x_phase[stale × oper] | (inverted) is the epoch advanced wrongly? | credit return | — | 20.3 §38 | a hit here is the finding, not a miss |
cp_outstanding.many | do obligations survive a recovery? | recovery entry | outcome, credit | 20.3 §38 | R5 is unverified |
x_outstanding_x_outcome[many × failed] | are live obligations classified on a fatal end? | recovery exit | — | 20.2 §41 | R12 is unverified |
cp_first_fault_already_set.yes | is the fault record sticky-first? | recovery entry | — | 20.3 §38 | last-capture would pass every test |
cp_attempts_per_object.two | is retry exercised? | resolution | objects per op | 20.3 §33 | R3's assertion is vacuous |
cp_arrival_ratio | which fault was actually injected? | resolution | family | 20.4 §19 | injections are unattributable |
cp_deliveries.zero_with_failure | is explicit failure reported? | terminal | — | 20.3 §33 | the failure path is untested |
x_reuse_x_reorder[immediate × yes] | can a stale response alias a reused identity? | response | — | 20.3 §20 | the aliasing guard is unverified |
x_activity_x_asymmetric[duplex × stalled] | are the directions independent? | acceptance | — | 20.3 §45 | a shared ready would pass |
cp_asymmetric_exhaustion.one_exhausted | are credit pools per class? | credit event | class | 19.5 §16 | the scalar-pool failure is untested |
x_family_x_moment | do faults at different moments behave? | injection | — | 20.2 §43 | timing-dependent bugs untested |
cp_concurrency_case | did the named races occur? | derived | — | various | the hypothesised interactions never happened |
Four properties of this table.
The last column is the reason the table exists. "An uncovered result means R5 is unverified" is actionable in a way that a percentage is not — and it is what turns §50's classification from a chore into a decision somebody can make.
One row is inverted. x_stale_x_phase[stale × operational] is a bin whose hit is the finding. A coverage model containing only bins-that-should-be-hit cannot express that, and the column makes the exception explicit.
Every row names a paired checker (§34). A row with an empty checker column is a situation being exercised and not verified.
And the table is the review gate. A proposed new coverage item that cannot fill the architectural-question column or the uncovered-meaning column is a bin somebody wanted rather than a question somebody has — which is how §32's model accumulates.
59. Closure Checklist
Coverage is being reviewed for sign-off. In order:
- Are the five levels reported separately (§6), or as one number?
- Does every requirement in §7's table have both a checker and a coverage item?
- Does every requirement have a negative test?
- Is any coverage item sampled on a clock rather than on its own event (§9)?
- Is any lifecycle question answered by a residency bin instead of a transition bin (§12)?
- Does every
illegal_binscite a specification clause, an architectural contract, a parameter rule, or an arithmetic impossibility (§13)? - Does every
ignore_binscarry the six-field record (§15, §52)? - Does every cross answer §31's question with a named bug?
- Are there crosses with more than three dimensions, and can each justify itself?
- Are boundary values in their own single-value bins (§22), or inside ranges?
- Is any bin bound a literal rather than a parameter expression (§40)?
- Does any architectural coverage item sample a design-internal signal (§42)?
- Are white-box items in a separate, labelled collector (§42)?
- Is
cp_simultaneous.bothamong the most-hit bins, or among the rarest (§21)? - Is
cp_outstanding.manynon-empty — was recovery ever tested with work outstanding (§26)? - Is
x_outstanding_x_outcome[many × failed]non-empty (§27)? - Is
cp_partial_batch.partialnon-empty (§23)? - Is
cp_outcome.nonenon-empty — has the link ever refused a peer (§16)? - Was every fault family injected, and did the expected detector fire each time (§24)?
- Was every fault moment exercised, including the three that need synchronisation (§25)?
- Does every assertion have a covered antecedent and a non-zero pass count (§35)?
- Is any property counted as coverage (§35)?
- Is code coverage reported as evidence rather than as requirement coverage (§36)?
- Does any conclusion rest on toggle or FSM-arc coverage for a scenario question (§37)?
- Is every uncovered bin classified into one of §50's six, with no "waived" category?
- Does every classification-4 bin have a proof, not a regression history (§51)?
- Does every waiver have an owner and a review version (§52)?
- Is any cross cell being satisfied by merged axes rather than a single run (§53)?
- Has any constraint been widened to fill bins rather than to describe real traffic (§56)?
- Is the uncovered list shrinking, and if not, is it short or long (§57)?
60. Common Misconceptions
"100% coverage means verified." It means the model's bins were hit. A poor model's 100% and a careful model's 80% are not comparable numbers, and the second is usually the more informative result (§5).
"More bins means a stronger plan." 1.7 million bins is a weaker plan than three thousand, because the shortfall in the large one cannot be classified and the effort it consumes is the effort the useful bins needed (§32, §45).
"Cross everything — you never know what interacts." You do know, and §31 is the question that establishes it. A cross with no named bug produces cells nobody can interpret and closure that is bin-count management.
"Code coverage is requirement coverage." It is implementation evidence. 19.5 §42's deadlock executes no unusual line and reports as fully covered (§36).
"illegal_bins can describe anything the RTL cannot do." Then the coverage model has encoded an implementation limit as a requirement, and the required path is never tested (§14).
"Unhit means unreachable." Unhit means classification 1 until proved otherwise. "Never seen in regression" is regression history, not a proof (§51).
"A waiver is a closure technique." A waiver without a classification, a proof, an owner and a review version is an undocumented reduction of the denominator (§52).
"Enough random seeds eventually hit every important race." Not one outside the constraint space. A burst followed by silence cannot be produced by a constraint set that keeps traffic flowing, at any seed count (§54).
"Coverage should sample every clock — more samples is more data." Then lifecycle bins measure residency and report 98% for the state the link idles in (§10).
"Assertions remove the need for functional coverage." An assertion whose antecedent never fires passes 100% of a regression. Coverage is what proves the assertion ran (§34, §35).
"Functional coverage checks correctness." It never fails. Correctness comes from assertions, scoreboards and models; coverage measures whether they had anything to check (§4).
"Every integer value deserves its own bin." Buckets plus boundaries beat enumeration: the boundaries are where bugs are and the middle is where redundancy is (§21, §22).
"Merged coverage proves the interactions occurred." A cross cell cannot be satisfied by merging its axes — it requires both conditions in one run (§53).
"The easiest way to close coverage is to loosen the constraints." It is, and it raises the number while reducing confidence, and it makes realistic traffic less covered than before (§56).
61. Understanding Check
62. Summary and What Comes Next
Functional coverage measures whether meaningful situations were observed. It never measures correctness — and a plan that confuses the two either exercises a design nothing checked, or checks a design in whatever situations happened to arise.
Read the model, not the number. 100% of a poor model and 80% of a careful one are not comparable, and the second is usually the more informative result.
Five levels, reported separately. Requirement coverage is the only one that can be complete; code coverage is a floor and never a ceiling; and scenario coverage is where UCIe verification actually lives.
Sample on the event the bin describes. A lifecycle bin sampled every clock measures residency and reports 98% for the state the link idles in.
Every cross names a bug. If the answer to "what breaks if these two interact wrongly?" is not a concrete failure, do not cross them — and the Cartesian product is 1.7 million uninterpretable cells that consume the budget the twelve useful ones needed.
An illegal_bins is a specification claim. Derived from what the current RTL supports, it encodes an implementation limit as a requirement and the required path is never tested.
Recovery context is the centerpiece. "A recovery occurred" captures none of the seven context dimensions or the three outcomes, and the cross of work-outstanding against outcome is where the two hardest requirements are verified.
Classify every uncovered bin into one of six. "Waived" is not one of them, unreachability needs a proof rather than a regression history, and every exclusion carries an owner and a review version.
And coverage serves the plan, never the reverse. A constraint widened to fill bins raises the number, reduces confidence, and makes realistic traffic less covered than it was before.
The plan now knows what must be checked, what must be exercised, and how to tell whether it was. What it does not yet have is the machinery that runs it — the agents that drive the boundaries, the peer that must be adversarial rather than accommodating, the sequences that compose scenarios across several interfaces at once, the coordinator that distributes the meaning of a reset rather than its wire, and the drain condition that stops a test from passing with thirty obligations still live. The next chapter assembles all of it into one reusable environment, without letting the framework's mechanics obscure the protocol reasoning these five chapters established.
- 20.6 — UVM Architecture for UCIe — a multi-agent UVM environment for UCIe.
Browse the full path on the UCIe tutorials index.