Skip to content

UCIe · Module 14

Error Detection

How each layer decides that what arrived is not what should have arrived — three detector classes for three failure modes, CRC state lifetime and why it must not advance during a stall, checker-result alignment to object identity, the delivery gate, buffer-until-validated versus speculative forwarding, parity as UCIe actually defines it, sequence-history checking, first-error capture, false positives, and the error-injection matrix.

Every trace in Modules 9 through 13 assumed something without ever stating it: that the bits which arrived are the bits that were sent. Flow control assumed it. Transaction tracking assumed it. Congestion policy assumed it. Throughput optimisation assumed it and then measured what it costs when it is false.

This module removes the assumption.

1. The One-Sentence Model

Error detection is comparison against redundancy or expectation. Every detector is one of those two, they catch different failures, and no single detector catches all of them.

Comparison against redundancy means the sender transmitted extra bits mathematically derived from the data, and the receiver recomputes and compares. CRC and parity are this.

Comparison against expectation means the receiver holds a model of what should arrive next, and checks what did. Sequence and history checking are this.

They are genuinely different mechanisms with genuinely different blind spots. A CRC cannot detect a unit that never arrived — there is nothing to check. A sequence check cannot detect a corrupted payload in a unit that arrived in the right order. A design with only one of the two has an entire class of failure it is structurally incapable of noticing.

2. What This Chapter Owns

The registry describes this chapter as "CRC, parity, sequence-number checks per layer." That is three detectors and a scope, and the scope is the important word.

QuestionOwner
How does a layer decide the object it received is wrong?14.1 — this chapter
How does the link restore trust once it has been lost?14.2 — Error Recovery
How is the object actually re-sent?14.3 — Retry Mechanisms

Detection and recovery are separate mechanisms, and this chapter deliberately stops at the boundary. When a corrupted object is detected here, what happens next — discard, replay request, link recovery, error report to software — belongs to 14.2 and 14.3. What belongs here is: detecting it, not delivering it, and recording enough to diagnose it.

New in this chapter: the detector-to-failure mapping that prevents "CRC catches everything"; CRC state lifetime and the stall-qualification bug that is the single most common error in a streaming checker; checker-result alignment to object identity, which is the pipeline bug that produces both false failures and, rarely, false passes; the delivery gate and why acting before the integrity result is catastrophic rather than merely wrong; parity as UCIe actually defines it, which is not where most engineers assume; sequence-history checking and the cross-mechanism bug where expectation advances on a unit that was discarded; false positives as a first-class failure; and the error-injection matrix, which UCIe's own compliance requirements happen to mandate.

3. Sourcing — and UCIe Defines More Here Than Elsewhere

4. The Core Question

How does each layer decide that the object it received is not the object that should have arrived?

Three things in that sentence do real work.

"Each layer." The detectors are not one mechanism at one place. A physical-layer bit flip, a corrupted RAM entry in a replay buffer, and a transport unit that never arrived are three different failures at three different layers, and each needs a detector positioned where the failure occurs. §5's mapping is the substance.

"Not the object that should have arrived." This is broader than "corrupted". An object can arrive perfectly intact and still be wrong: a duplicate, one arriving out of order, or a stale response from a transaction that was already completed. Bit-level integrity and semantic correctness are different claims, and a clean CRC establishes only the first.

"Decide." Detection produces a decision that gates delivery (§19). A detector that reports an error after the object reached a semantic consumer has not prevented anything — it has documented a corruption that already took effect (§20).

5. Three Detector Classes, Three Failures

Detector classCompares againstCatchesStructurally cannot catch
CRCmathematical redundancy over a protected regionbit corruption in transit, within its detection guaranteea unit that never arrived; a unit outside the protected region; a correct unit delivered to the wrong place
Parity / structural checka redundant bit over a local storage or transport fieldsingle-bit corruption in local storage or a narrow fieldmulti-bit corruption in the same parity group; anything outside the protected field
Sequence / history checkthe receiver's model of transport historyloss, duplication, reordering, stale replaycorruption within a correctly ordered unit

Read the last column, not the third. The value of the table is what each detector cannot see, because that is what determines whether a design needs all three.

A CRC's blindness to absence is the important one. If a transport unit is lost entirely — dropped by a physical event, or never framed — there is no received object over which to compute a CRC. The CRC does not fail; it never runs. The only mechanism that notices is a receiver holding an expectation about what should have arrived next, which is §26.

And a sequence check's blindness to content is the mirror. A unit arriving with the exactly expected sequence, in order, with no duplication, whose payload has been corrupted, passes every history check. Only redundancy over the payload catches it.

The two mechanisms are complements rather than alternatives. A design with CRC and no history check is blind to loss. A design with history checking and no CRC is blind to corruption. Neither blindness is graceful — both deliver wrong data silently.

Parity's role is different from both, and this is where designs get confused. Parity in a data path duplicates what a CRC already does, less well. Its actual value is where a CRC is not present: inside local storage, on a narrow control field, on a path between two points that the end-to-end check does not span. §23 develops that, and §22 first establishes what UCIe itself defines parity for — which is neither of the two.

6. Error Taxonomy — Failure to Detector

FailureWhere it happensDetector that catches itDetector that does not
Physical bit corruption in transitthe channelCRC over the protected regionparity on internal storage — the corruption is downstream of it
Internal RAM / datapath corruptiona replay buffer, a queue, a register fileparity or ECC where implemented (§23)link CRC — the data was correct when the CRC was computed
Lost transport unitanywheresequence / history check (§26)CRC — it never runs
Duplicated transport unita retry path, a confirmation losshistory check for an already-committed sequence (§31)CRC — the duplicate is bit-perfect
Reordered unitsa multi-path or multi-queue transporthistory / expectation checkCRC — each unit is individually valid
Wrong metadata-to-data associationa pipeline misalignment, a tag mix-upend-to-end semantic scoreboardevery check in this chapter — both halves are individually valid
Stale response aliasa reused identity after a timeouttransaction-generation model (12.4)CRC and sequence — the response is well-formed and in order

Three rows deserve emphasis.

Row two is the argument for local protection. If a replay-buffer entry is corrupted after its CRC was computed and before retransmission, the sender will compute a fresh CRC over the corrupted data and transmit a perfectly consistent wrong object. The link CRC will pass. The receiver has no way to know. Only a check inside the storage — recompute-and-compare on read — catches it, and this is the failure class §23 exists for.

Row six is the one no detector in this chapter catches, and it must be said plainly. If a pipeline associates packet N's payload with packet N+1's metadata, both halves are individually valid, the CRC over each is correct, the sequence is in order, and the delivered object is wrong. The only thing that catches it is an end-to-end scoreboard comparing the delivered pair against the transmitted pair (§41). §15 is the RTL bug that produces it.

Row seven belongs to Module 12 and is listed to close the loop. A response arriving for an identity that has been reused after a timeout is well-formed, in-order, and refers to a transaction that no longer exists. 12.4's generation counter is the detector, and no integrity check helps.

7. The Detector Pipeline

A received transport unit enters a framing checker, then a CRC checker, then a sequence and history checker, and only then a delivery gate that admits it to the semantic consumer. All three checkers report into a single error collector, which holds the first error cause and per-detector counters. A separate local storage path — a replay RAM with parity — recomputes parity on read and reports into the same error collector, because a corruption inside local storage is invisible to the link CRC. The delivery gate requires framing, CRC and sequence all to pass; a failure blocks delivery rather than reporting after the fact.Received unitbeats arriving withvalidFraming checkerstructure and lengthCRC checkerredundancy over theregionSequence checkeragainst expectedhistoryDelivery gateall mandatory checkspassSemantic consumeracts on the objectReplay RAM +parityrecheck on readError collectorfirst cause,per-detectoronly if cleanparity fail12
Figure 1 — the layered detector pipeline. A received unit passes framing, CRC and sequence checks, and reaches the semantic consumer only through a delivery gate that requires every mandatory check to pass. Local storage parity feeds the same error collector, so a corruption that the link CRC cannot see is still reported through one path.

Three structural properties to read off the figure.

The gate is between the last checker and the consumer, not beside it. Delivery is gated, not annotated. An architecture where the consumer receives the object and a separate error signal arrives later is §20's bug, and it is a different diagram.

Every detector reports into one collector. Separate counters per detector (§36), one first-cause register (§34). The collector is what makes an error report answer "which layer, which detector, which object" rather than just "an error occurred."

And the storage path is drawn deliberately outside the main chain, because that is the point of §23: a corruption there is invisible to the link CRC, and if it did not have its own detector reporting into the same collector, it would be invisible entirely.

8. CRC — the Hardware View

No polynomial algebra. The implementation view is four things:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
accepted data bytes  →  CRC state update  →  compare at unit end  →  error event

                    initialised at unit start

Four properties matter for RTL, and only the fourth is about the mathematics.

It is a state machine over accepted bytes. The state advances once per accepted byte or beat, and "accepted" is the operative word — §12 is the bug that gets this wrong.

It has a defined start and end. One CRC accumulation corresponds to one transport unit: initialised at the unit's start, compared at its end, reset for the next. §11 is that lifetime.

It compares rather than corrects. CRC is detection. Nothing in this chapter corrects anything — correction, where it exists, is a different mechanism with different mathematics, and UCIe's approach at high rates is CRC plus replay rather than forward correction (§3).

And its detection guarantee is finite and specified. Consortium material describes a 3-bit detection guarantee for random bit errors for the Adapter's CRC (§3). That is a guarantee, not a limit — it detects all patterns of up to three flipped bits, and detects the overwhelming majority of larger patterns without a guarantee. The engineering consequence is that "the CRC passed" is a strong statement, not a proof. §47's first misconception is this.

9. What the CRC Covers — the Protected Region

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[  protected region  ][ CRC ]

   computed over exactly this, and nothing else

What is verified: the Adapter's CRC is computed over 128 bytes, and where the message is shorter it is zero-extended in the MSB for the computation (§3). What is not verified is precisely which fields fall inside that region for each flit format.

So this chapter uses the term protected region and declines to enumerate it. That is not evasion — it is the difference between a chapter you can trust and one you cannot, and the reason is concrete: a design that assumes the CRC protects a field it does not is unprotected in exactly the place it believes it is safe.

Two engineering consequences that hold regardless of the enumeration.

Anything outside the protected region needs its own detector or has none. If a metadata field is not covered, corruption of it is undetected by the CRC — and since metadata determines where an object goes and what it means, that is a serious exposure. §25 is the failure.

And the zero-extension rule is a real implementation constraint, not a formality. A checker that computes over the actual bytes while the transmitter computes over the zero-extended 128 bytes will mismatch on every short message. This is a false-positive generator (§38) whose signature is "every short unit fails, every full-length unit passes" — a highly diagnostic pattern, and one that is easy to produce by reading the rule casually.

10. Streaming CRC State — With No Fabricated Polynomial

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE streaming CRC accumulator.
//
// THE POLYNOMIAL IS DELIBERATELY NOT IMPLEMENTED. UCIe's CRC polynomial is
// not published in any official source I could reach (Section 3), and putting
// a plausible-looking one here would be worse than useless — it would look
// authoritative and be wrong. crc_update() is therefore an ABSTRACT function.
// Substitute your revision's polynomial; every structural property below is
// independent of which polynomial it is.
localparam int CRC_W  = 16;                 // 2-byte CRC — this IS verified
localparam int BEAT_W = 64;
 
// Abstract, polynomial-agnostic. Any table-driven or LFSR implementation of
// the specified polynomial substitutes here without changing anything else.
function automatic logic [CRC_W-1:0] crc_update(
  input logic [CRC_W-1:0]  state,
  input logic [BEAT_W-1:0] data,
  input logic [BEAT_W/8-1:0] byte_valid
);
  // Placeholder. Structure only: fold each VALID byte into the state.
  logic [CRC_W-1:0] s = state;
  for (int b = 0; b < BEAT_W/8; b++)
    if (byte_valid[b]) s = crc_byte(s, data[b*8 +: 8]);   // polynomial-specific
  return s;
endfunction
 
logic [CRC_W-1:0] crc_q;
logic [CRC_W-1:0] crc_next;
 
// The single most important line in the chapter: the state advances ONLY on a
// beat that was actually transferred. valid alone is not enough (Section 12).
wire beat_accepted = in_valid && in_ready;
 
assign crc_next = unit_start ? crc_update(CRC_INIT, in_data, in_byte_valid)
                             : crc_update(crc_q,    in_data, in_byte_valid);
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)                 crc_q <= CRC_INIT;
  else if (beat_accepted)     crc_q <= crc_next;     // ONLY on transfer
  else if (unit_complete)     crc_q <= CRC_INIT;     // rearm for the next unit
end

Architecture. One accumulator, advanced per accepted beat, compared at the unit boundary. The crc_update abstraction is deliberate and it is also good engineering independent of the sourcing problem: it makes the polynomial a swappable detail and keeps the interesting logic — accept qualification, initialisation, masking, boundary handling — visible and reviewable.

State. crc_q has per-transport-unit lifetime. It is meaningless outside a unit, must be initialised at the unit's start, and must be re-armed after the comparison. Note the two reset paths: architectural reset and unit completion. Only the second is exercised in normal operation, which is why it is the one that gets broken.

Cycle behaviour. beat_accepted = in_valid && in_ready. That conjunction is the entire content of §12 and §13.

Contract. The transmitter and receiver must agree on four things, and every one is a place designs diverge: the initial value (CRC_INIT), the byte order of the fold, the protected region (§9), and the treatment of invalid bytes in a partial beat (§14). A mismatch in any one of the four produces a total false-positive failure — every unit fails — which is at least loud. A mismatch that affects only some units, such as the zero-extension rule for short messages, is far more dangerous because it looks like a marginal channel.

Failure. §12 (advancing during a stall), §14 (masking wrong), §15 (misalignment), and initialisation left at a stale value after reset — which produces §38's "every packet fails on a clean channel."

DV. A known-answer test per polynomial. Then: a unit with a stall in the middle; a unit with a stall on the first beat; a unit with a stall on the last beat; a partial final beat at every valid byte count; back-to-back units with no idle cycle between them, which is where the re-arm path is exercised.

11. CRC State Lifetime

Stated separately because it is the property that generates three of this chapter's bugs.

A CRC accumulator exists per transport unit. It is initialised at unit start, updated only on accepted data, compared at unit end, and re-armed. Outside that window it holds no meaning.

EventRequired actionBug if omitted
Unit startinitialise to CRC_INITresidue from the previous unit corrupts this one — every unit after the first fails
Accepted beatupdate
Stalled cycledo nothing§12 — the computed CRC no longer corresponds to the payload
Invalid bytes in a beatexclude per the framing rule§14 — mismatch on partial beats only
Unit endcompare, then re-armback-to-back units fail from the second onward
Resetinitialise§38 — every unit fails on a clean channel

Two observations about this table.

Four of the six rows produce a false positive rather than a missed detection. That is characteristic of integrity checkers and it is why §38 treats false positives as first-class bugs: the failure mode of a broken checker is usually to condemn good data, not to pass bad data. A link that appears catastrophically noisy on a clean channel is far more likely to be a checker bug than a physical problem.

And the back-to-back case is the one integration testing misses. A testbench that inserts idle cycles between units — most do, because it is easier to write — never exercises the re-arm path under pressure. The design then fails at full rate in silicon and passes every simulation.

12. Wrong RTL — the CRC Advances While Data Is Stalled

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the CRC updates whenever valid is asserted, whether or not the beat
// was accepted. The single most common bug in a streaming integrity checker.
always_ff @(posedge clk) begin
  if (in_valid)              // MISSING: && in_ready
    crc_q <= crc_update(crc_q, in_data, in_byte_valid);
end

The trace, with a two-cycle stall. Payload beats are B0, B1, B2.

Cyclein_validin_readyData on the busCorrect CRC foldsBuggy CRC folds
011B0B0B0
110B1 (held)B1
210B1 (held)B1 again
311B1B1B1
411B2B2B2

Correct accumulation: B0, B1, B2. Buggy accumulation: B0, B1, B1, B1, B2. B1 has been folded three times.

Four properties make this bug the archetype of the chapter.

It is invisible without backpressure. Run the testbench with in_ready tied high and the two implementations are bit-identical. The bug requires a stall to appear, and stalls are the thing performance-oriented testbenches suppress.

It presents as a physical-layer problem. The receiver computes a CRC over B0, B1, B2 and the transmitter — if it has the same bug — computes over a different multiset. The mismatch looks exactly like channel corruption. A debugger will go to the PHY, the eye margin, and the temperature, and find nothing. §45's taxonomy exists specifically to redirect this: CRC errors that appear only during stalls are a handshake bug, not a channel problem.

Its rate is a function of the traffic pattern, not the error rate. More backpressure means more corruption. So the "error rate" rises under load — which reads as a signal-integrity problem that worsens with activity, one of the most plausible-looking false leads available.

And it can corrupt in the undetectable direction. Usually the mismatch is detected and the good unit is rejected — a false positive. But because the transmitter and receiver may both hold the bug and stall at different times, there exists a set of stall patterns for which their two wrong accumulations coincidentally agree. Then a corrupted unit passes. That is rare and it is real, and it is why the assertion in §13 is mandatory rather than advisory.

Every state machine that consumes a stream must advance on valid && ready, never on valid alone. The CRC is the case where getting it wrong is hardest to diagnose.

13. SVA — CRC State Changes Only on Accepted Data

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. This is the assertion that catches Section 12, and it costs
// nothing.
property p_crc_stable_when_not_accepted;
  @(posedge clk) disable iff (!rst_n)
    (!beat_accepted && !unit_complete) |=> $stable(crc_q);
endproperty
a_crc_stable_when_not_accepted: assert property (p_crc_stable_when_not_accepted);
 
// The companion: it MUST advance when a beat IS accepted (unless the update
// happens to be the identity, which a good polynomial makes vanishingly rare
// — so this is written as "was updated", not "changed").
property p_crc_advances_on_accept;
  @(posedge clk) disable iff (!rst_n)
    beat_accepted |=> (crc_q == $past(crc_next));
endproperty
a_crc_advances_on_accept: assert property (p_crc_advances_on_accept);
 
// And the lifetime property: the accumulator is armed at the start of a unit.
property p_crc_initialised_at_unit_start;
  @(posedge clk) disable iff (!rst_n)
    (unit_start && beat_accepted) |=> (crc_q == $past(crc_update(CRC_INIT, in_data, in_byte_valid)));
endproperty
a_crc_initialised_at_unit_start: assert property (p_crc_initialised_at_unit_start);

Architecture. Three properties covering the three ways the accumulator's lifetime can be violated: advancing when it should not, not advancing when it should, and starting from the wrong value.

Why the second property is written against crc_next rather than as !$stable. A CRC update can, for particular data, leave the state unchanged — so asserting that the state changed would fail on legitimate data. Comparing against the combinational crc_next from the previous cycle checks that the update was applied, which is the actual requirement. This is a general lesson about writing properties over data-dependent functions: check that the function was applied, not that the result differs.

Contract. All three are pure safety properties over the checker's own interface. No reference model, no scoreboard, no polynomial knowledge. They are the cheapest high-value assertions in the chapter and they should be in every regression, always enabled.

Failure they catch. The first fails on §12 in the first stalled cycle, with a two-cycle counterexample. The third fails on a stale-initialisation bug, which is §38's every-packet-fails signature.

DV. These need backpressure in the stimulus. A regression with in_ready tied high satisfies all three vacuously and proves nothing. Cover the stalled-cycle-during-unit condition explicitly (§42).

14. Partial Beats and Byte Validity

A final beat rarely fills the datapath, and what the checker does with the unused lanes must match what the transmitter did.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE mask-aware update. The generic requirement is that the
// checker's treatment of invalid lanes matches the framing rule EXACTLY.
// UCIe's own rule for its formats is not something I could verify beyond the
// zero-extension statement in Section 3 — so this shows the mechanism, and
// the mechanism must be configured to your revision.
function automatic logic [CRC_W-1:0] crc_update_masked(
  input logic [CRC_W-1:0]    state,
  input logic [BEAT_W-1:0]   data,
  input logic [BEAT_W/8-1:0] byte_valid
);
  logic [CRC_W-1:0] s = state;
  for (int b = 0; b < BEAT_W/8; b++) begin
    if (byte_valid[b])
      s = crc_byte(s, data[b*8 +: 8]);        // fold the real byte
    // Invalid lanes: EITHER skip (as here) OR fold a defined constant.
    // WHICH ONE is a framing-rule question. Getting it wrong mismatches
    // ONLY on partial beats — see the failure note below.
  end
  return s;
endfunction
 
// Byte-valid must be contiguous-from-LSB on a final beat, and that is
// checkable rather than assumable.
property p_byte_valid_contiguous;
  @(posedge clk) disable iff (!rst_n)
    beat_accepted |-> $onehot0(in_byte_valid + 1'b1);   // 0, 1, 11, 111, ...
endproperty
a_byte_valid_contiguous: assert property (p_byte_valid_contiguous);

Architecture. A masked fold, plus an assertion on the mask's shape. The second is worth as much as the first: a non-contiguous byte-valid on a final beat almost always indicates an upstream framing bug, and detecting it at the checker is much cheaper than detecting its consequences.

State. None additional — this is a function of the beat.

Cycle behaviour. Per accepted beat, with only valid lanes folded.

Contract — and this is the section's whole point. The transmitter and receiver must treat invalid lanes identically. Two defensible rules exist (skip them, or fold a defined constant such as zero), and either works if both ends agree. What does not work is one end skipping and the other folding zeros.

Failure, with its diagnostic signature. A mismatch here produces errors only on units whose final beat is partial. Full-length units pass. That signature is extremely informative and extremely easy to misread as a length-dependent physical effect. The verified zero-extension rule (§3) is exactly this class of requirement, and a checker computing over the actual bytes while the transmitter zero-extends to 128 will fail every short message and pass every full one.

DV. Sweep the final beat's valid-byte count across every legal value from 1 to BEAT_W/8. This is a small, cheap, fully directed sweep, and it is the only thing that finds this class of bug — random data does not vary the length systematically.

15. Wrong RTL — the Checker Result Misaligned From the Object

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the CRC result is compared against whatever is at the compare stage
// this cycle, with no binding between the result and the object it describes.
always_ff @(posedge clk) begin
  crc_result_q <= (crc_q == received_crc);     // computed for object N
  if (unit_complete)
    object_ok <= crc_result_q;                 // applied to object N+1
end

The trace. Object A is clean; object B is corrupted.

CycleObject at compare stagecrc_result_q holdsobject_ok applied toVerdict
0A completes(A's result being computed)
1B completesA's result — passBB passes. Corrupted object delivered.
2C completesB's result — failCC fails. Clean object rejected.

One misalignment produces both failure directions from a single bug: a corrupted object delivered and a clean object rejected, on consecutive units.

Three reasons this is a particularly serious bug.

The false-pass direction is a silent data corruption. B's payload is wrong and it reached the consumer with a clean verdict. Nothing downstream will ever question it. This is the outcome the entire chapter exists to prevent, and it is produced not by a weak polynomial but by a pipeline register.

The false-fail direction masks the false pass. C's rejection triggers a retry, an error count, and possibly a recovery — so the visible symptom is "C failed", and an engineer investigating C finds nothing wrong with C. The real event is one unit earlier and invisible.

And it is a one-off-by-one, so it survives review. The RTL contains a CRC comparison and a registered result. Nothing looks wrong. The missing element is not a check — it is an identity.

The fix binds the verdict to the object:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — the verdict travels WITH an identity, so the consumer can
// prove the verdict it is applying belongs to the object it is applying it to.
// obj_id is a VERIFICATION AND LOCAL PIPELINE tag, not a protocol field.
typedef struct packed {
  logic                valid;
  logic [OBJ_ID_W-1:0] obj_id;      // which object this verdict describes
  logic                crc_ok;
  logic                framing_ok;
} verdict_t;
 
verdict_t verdict_q;
 
always_ff @(posedge clk) begin
  if (unit_complete) begin
    verdict_q.valid      <= 1'b1;
    verdict_q.obj_id     <= completing_obj_id;      // captured together
    verdict_q.crc_ok     <= (crc_q == received_crc);
    verdict_q.framing_ok <= framing_ok;
  end else begin
    verdict_q.valid      <= 1'b0;
  end
end
 
// The consumer checks the identity matches before acting on the verdict.
assign verdict_applies = verdict_q.valid && (verdict_q.obj_id == delivering_obj_id);

A checker result is meaningless without the identity of the object it describes. Bundle them at the point of computation, and check the identity at the point of use.

16. SVA — Verdict Aligned to Object Identity

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The alignment contract. Uses the verification/pipeline tag, because the
// binding is a local implementation property with no protocol field.
property p_verdict_matches_object;
  @(posedge clk) disable iff (!rst_n)
    object_deliver |-> (verdict_q.valid && (verdict_q.obj_id == delivering_obj_id));
endproperty
a_verdict_matches_object: assert property (p_verdict_matches_object);
 
// And the stronger claim the scoreboard needs: the verdict applied to an
// object must equal the verdict computed for THAT object. Requires a
// verification-only record of what was computed.
property p_verdict_is_the_computed_one;
  @(posedge clk) disable iff (!rst_n)
    object_deliver |-> (verdict_q.crc_ok == tb_expected_crc_ok[delivering_obj_id]);
endproperty
a_verdict_is_the_computed_one: assert property (p_verdict_is_the_computed_one);

Architecture. Two properties: the identity matches, and the verdict is the right one. The first is checkable from the design's own signals; the second needs a testbench record and belongs with the scoreboard.

Why both. The first alone can be satisfied by a design that carries the identity correctly and computes the verdict wrongly. The second alone cannot be written without a reference. Together they close §15 completely.

Contract. obj_id is explicitly a local pipeline and verification tag, not a protocol field. That distinction is the same one 13.1 §12 made for its replay monitor tag: the protocol carries no field saying "this verdict belongs to that object", so the binding is an implementation obligation and the assertion documents it.

DV. Drive consecutive units with alternating clean and corrupted payloads and no idle cycles between them. §15's bug requires back-to-back units to appear, so a testbench with gaps will not find it.

17. Detection Latency, and What It Forces

A checker takes time. That has a structural consequence most designs discover late.

The object must be held until its verdict exists. Detection latency is therefore also a buffering requirement, and it couples error detection to Chapter 13.2's capacity and Chapter 13.5's throughput.

Detection latencyStorage requiredConsequence
0 cycles (combinational compare at last beat)none beyond the unit itselfbest latency; hardest timing, since the compare is in the same cycle as the last fold
1–2 cyclesthe unit, plus one or two in flightthe usual design point
Many cycles (deeply pipelined checker)the unit, plus latency units in flightstorage grows with checker depth, and so does the delay before an error is known

Three consequences worth naming.

The storage is not optional and it is not small. A checker with 4 cycles of latency, on a datapath accepting one unit every 2 cycles, must hold 2 additional complete units. That is receive buffering that exists purely because of the checker, and it must be counted in 13.2 §13's sizing rather than discovered afterwards.

Detection latency delays the recovery, not just the report. Every cycle before the verdict is a cycle in which more units have been accepted behind the bad one. If the recovery mechanism must discard everything after a failure, deeper checkers discard more. That is a 14.2 concern, and it is created here.

And it interacts with the delivery gate. §19's gate cannot admit an object before its verdict exists, so detection latency is a lower bound on delivery latency. A design that wants both low delivery latency and a deep checker is asking for §21's speculative forwarding, with everything that entails.

18. Pipelining the Checker — Latency Is Not Initiation Interval

The resolution of the previous section's tension is the idea 13.5 §9 established.

A CRC checker can be pipelined. Fold stage, combine stage, compare stage. Each stage handles one beat per cycle, so:

LatencyInitiation intervalThroughput
Combinational checker0 extra cycles1 beat/cycle, at low Fmaxlimited by the fold's critical path
3-stage pipelined checker3 cycles1 beat/cycle, at high Fmaxfull rate

So the checker does not have to cost throughput — only latency and storage. That is the same trade as 13.5 §25's Fmax argument, applied to the integrity path, and it is why deeply pipelined CRC is standard in high-rate links.

And UCIe's format design supports it. The Consortium's statement that "performance critical bits (such as Flit Header) appear early on in the Flit to enable pipelined operations" (§3) is exactly this: a checker that can start work on the first beat rather than waiting for the whole unit is a checker that can be pipelined, and the format was arranged to permit it.

The cost, restated so it is not lost: a 3-cycle checker means 3 units of additional in-flight storage at full rate, and 3 more cycles before an error is known. Free throughput; paid-for latency and area.

19. The Delivery Gate

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE delivery gate. The single most important structural property
// in the chapter: the object reaches a semantic consumer ONLY through a
// conjunction of every mandatory check.
//
// Note this is a CONJUNCTION of named terms, exactly like 13.1 Section 14's
// acceptance gate and 13.5 Section 17's concurrency gate — for the same
// reason: a collapsed single signal cannot be attributed.
assign object_deliver = object_complete      // the whole unit has arrived
                     && verdict_applies      // the verdict is for THIS object (Sec 16)
                     && framing_ok           // structure and length are legal
                     && crc_ok               // redundancy check passed
                     && sequence_ok;         // history check passed

Architecture. One AND, five named terms, and every term must be independently observable. The naming is not cosmetic: §35's error record needs to say which check failed, and a collapsed object_good signal cannot.

State. None here — this is the gate. The state is in the checkers behind it and the buffer holding the object (§17).

Cycle behaviour. Combinational, and it must be. A registered gate is a gate that admits objects one cycle before it decides to, which is §20.

Contract. The consumer's obligation is to act on nothing else. The gate's obligation is to be complete — every mandatory check must be a term. A check that exists but is not wired into the gate is a check that produces a log entry and prevents nothing, which is a surprisingly common outcome when a checker is added late.

Failure. Two, in opposite directions. Omit a term and corrupted objects are delivered with the error dutifully counted. Add a term that is not actually mandatory — a check that legitimately fails on some valid traffic — and good objects are blocked, which is §38.

DV. For each term, force it false in isolation and verify that delivery does not occur and the object is not visible to the consumer. Five directed tests, one per term, and they catch the "counted but not gated" failure that no random regression will find.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The safety property the whole chapter reduces to.
property p_no_delivery_of_failed_object;
  @(posedge clk) disable iff (!rst_n)
    object_deliver |-> (framing_ok && crc_ok && sequence_ok);
endproperty
a_no_delivery_of_failed_object: assert property (p_no_delivery_of_failed_object);

20. Wrong RTL — Delivering Before the Verdict

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the object is forwarded on completion, and the integrity result
// arrives a cycle later. Catastrophic, not merely incorrect.
always_ff @(posedge clk) begin
  if (unit_complete) begin
    consumer_valid <= 1'b1;              // consumer acts THIS cycle
    consumer_data  <= assembled_object;
  end
  crc_error <= (crc_q != received_crc);  // arrives one cycle too late
end

Why "catastrophic" rather than "a bug". The severity depends entirely on what the consumer does, and for the traffic UCIe carries the answer is bad in every case:

The object wasConsumer action on arrivalConsequence of a late error
A memory writedata committed to memory or cachememory now holds corrupt data. No retry helps — the write already happened
A coherence responsea cache line's state changedthe coherence protocol has advanced on false information. 11.3 established there is no safe recovery from this
A snoopa line invalidated or downgradeda valid line destroyed, or worse, a stale one retained
A completiona transaction retired (12.4)the tracking entry is gone. Nothing remains to retry
A read responsedata returned to a requestercorrupt data delivered to software

Three properties of the bug.

Retry cannot undo it. 14.3's replay mechanism re-sends the object. It does not un-commit a write, un-invalidate a line, or un-retire a transaction. Detection that arrives after the effect has no recovery path, which is why the gate must be combinational.

It is a one-cycle error with unbounded consequences. The window is a single clock. Under the specified BER — one bit error per lane roughly every 15.6 seconds at 64 GT/s (13.5 §29) — the window is entered rarely, so this bug can survive an enormous amount of testing and then corrupt memory in the field.

And it is invisible to a scoreboard that checks only the error signal. The error was reported. A testbench asserting "a corrupted unit raises an error" passes. The property that fails is "a corrupted unit is not delivered" (§19), which is a different assertion and the one that matters.

Detection must gate the effect, not annotate it. An error signal that arrives after the consumer has acted is a log entry, not a protection mechanism.

21. Buffer Until Validated, or Forward Speculatively

The two architectures, and the honest comparison.

Buffer until validatedSpeculative forwarding
Structurehold the object until the verdict, then releaseforward immediately, invalidate afterwards
Delivery latencyverdict latency addedminimal
Storagethe object plus in-flight units (§17)less at the boundary
Consumer requirementsnone — it sees only good objectsmust support rollback, poison, or quarantine
Failure modenone inherent§20, unless the full contract is implemented
Verification costmoderatehigh — every consumer's rollback path needs its own verification

Buffer-until-validated is the correct default, and the reason is asymmetric risk. Its cost is latency and storage, both of which are bounded, measurable, and paid uniformly. Speculative forwarding's cost is a contract obligation on every consumer, and a single consumer that does not honour it turns the optimisation into §20.

When speculative forwarding is legitimate, it needs all four of:

  1. An architected poison or invalidate indication the consumer must observe;
  2. A consumer that can genuinely roll back — which for a memory write means it has not committed, and for a coherence transition means the state change is reversible;
  3. A bounded window during which the forwarded object is revocable, enforced rather than assumed;
  4. Verification of the rollback path at every consumer, including the case where the rollback and a subsequent legitimate operation collide.

Absent any one of the four, it is not an optimisation — it is §20 with extra steps.

Do not forward speculatively because the latency looks attractive. Forward speculatively only when every consumer's rollback contract exists, is architected, and is verified.

22. Parity in UCIe — What Is Actually Defined

This section exists because the registry says "per layer" and because the intuitive assumption about parity in UCIe is wrong.

UCIe does define parity. It defines it in three places, and none is a data-path or memory protection rule:

Verified UCIe parity mechanismWhat it isSource
Periodic parity Flit injection and checkinga run-time link-health test used during mission mode; errors reported in the UCIe 1.1 per-Lane error log register, with interrupt capabilityUCIe 1.1 white paper; Consortium Q&A
Parity, one bit per 8 UIan optional physical-layer marker, alongside periodic synchronisation markers, carried using the retimer encodings that are used for credit exchangesUCIe 3.0 white paper
1 parity bit in the sideband priority packeta field in a defined 32 UI packet: 23 bits priority vector, 5 bits opcode, 3 reserved, 1 parityUCIe 3.0 white paper

What that tells you, and it is genuinely interesting architecture.

The first is a test mechanism, not a protection mechanism. Injecting parity flits periodically during mission mode and checking them is a way to measure link health continuously rather than a way to protect traffic. It detects a degrading channel before it corrupts real data, which is a fundamentally different goal from detecting corruption in a specific object. The reporting destination — a per-Lane error log with interrupts — confirms it: the output is a lane diagnosis, not an object verdict.

The second shows parity used where a CRC cannot go. One bit per 8 UI is a continuous, extremely low-overhead check on a physical stream, at a granularity far finer than a flit. A CRC operates per unit; this operates per 8 UI. Different granularity, different purpose.

The third is parity used because the field is tiny. A 32 UI packet with 28 bits of content cannot justify a 16-bit CRC. One bit of parity on a small, critical control field is the right engineering choice, and it is a useful demonstration that detector strength should scale with field size rather than being uniform.

23. Parity for Internal Storage — Representative Protection

Representative implementation protection, not a UCIe rule (§22).

The failure it exists for is the one no link check can see. Consider a replay buffer. An object is received or generated, its CRC is computed, it is written to the replay RAM, and later it is retransmitted. If the RAM entry is corrupted after the write and before the retransmission — a soft error, a marginal cell, a write-enable glitch — then:

  1. The sender reads corrupt data from the RAM.
  2. The sender computes a fresh, correct CRC over the corrupt data.
  3. The receiver's CRC passes.
  4. Corrupt data is delivered with a clean verdict.

The link's integrity check is structurally incapable of detecting this, because the corruption happened on the sender's side of the point where the CRC is computed. The only detector is one inside the storage, recomputing on read.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE replay-buffer entry protection. Representative implementation
// technique — NOT a UCIe-defined rule (Section 22).
//
// The entry stores payload AND metadata AND a parity bit computed over BOTH,
// for the reason Section 25 develops.
localparam int PAY_W  = 256;
localparam int META_W = 24;                     // seq, ids, flags
localparam int PAR_W  = 1;
 
typedef struct packed {
  logic [PAY_W-1:0]  payload;
  logic [META_W-1:0] meta;
  logic [PAR_W-1:0]  parity;                    // over payload AND meta
} replay_entry_t;
 
// Odd or even is a design choice; what matters is that both ends of the
// storage path agree. Even parity (XOR reduction) is used here.
function automatic logic entry_parity(logic [PAY_W-1:0] p, logic [META_W-1:0] m);
  return ^{p, m};
endfunction
 
replay_entry_t ram [DEPTH];
 
// Write: compute and store together.
always_ff @(posedge clk)
  if (replay_write)
    ram[write_idx] <= '{ payload: wr_payload,
                         meta:    wr_meta,
                         parity:  entry_parity(wr_payload, wr_meta) };
 
// Read: recompute and compare. THIS is the detector.
wire replay_entry_t rd_entry   = ram[read_idx];
wire               rd_parity_ok = (rd_entry.parity ==
                                    entry_parity(rd_entry.payload, rd_entry.meta));
 
// A parity failure is a LOCAL error, distinct from a link error, and it must
// be reported as such (Section 36) — otherwise it is misdiagnosed as a
// channel problem for the rest of the debug session.
assign local_parity_error = replay_read && !rd_parity_ok;

Architecture. One redundant bit per entry, computed on write and checked on read. Cheap — one XOR tree and one bit of storage per entry — and it closes a detection hole that no amount of link CRC strength can reach.

State. The parity bit has exactly the lifetime of the entry it protects, which is why it must be written in the same cycle, from the same data, in the same always block. Computing it in a separate process invites a skew where the parity describes a different write.

Cycle behaviour. Write: compute and store atomically. Read: recompute and compare, in the read path, before the data is used.

Contract. The protection boundary must span everything whose corruption matters — which is §25's point and the reason entry_parity covers meta as well as payload.

Failure — the coverage limit, stated honestly. A single parity bit over a wide entry detects any odd number of flipped bits and is blind to any even number. Over 280 bits that is a real limitation, and if the soft-error rate justifies it, the answer is more parity groups (one per byte lane, say) or ECC — not a stronger single bit, which does not exist. State the coverage; do not overstate it.

And note what parity does not buy: it detects, it does not correct. A parity failure on a replay entry means the object cannot be retransmitted correctly, which is a 14.2 escalation rather than something the read path can fix.

DV. Force a single-bit flip in the payload after write and before read, and check local_parity_error. Then flip a bit in the metadata and check the same (§25). Then flip two bits and observe that it is not detected — that negative test documents the coverage limit in the regression, so nobody later assumes parity is stronger than it is.

24. Multi-Group Parity, When One Bit Is Not Enough

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE per-lane parity. One bit per byte raises coverage from "any
// odd number of bits across the whole entry" to "any odd number of bits
// within EACH byte" — which detects all single-bit errors and all
// odd-multiplicity errors per byte, at a cost of one bit per byte.
localparam int NBYTES = (PAY_W + META_W) / 8;
 
function automatic logic [NBYTES-1:0] lane_parity(logic [PAY_W+META_W-1:0] d);
  logic [NBYTES-1:0] p;
  for (int b = 0; b < NBYTES; b++) p[b] = ^d[b*8 +: 8];
  return p;
endfunction
 
wire [NBYTES-1:0] rd_lane_par_calc = lane_parity({rd_entry.payload, rd_entry.meta});
wire [NBYTES-1:0] lane_par_fail    = rd_lane_par_calc ^ rd_entry.lane_parity;
wire              any_lane_fail    = |lane_par_fail;

Architecture. NBYTES independent parity groups instead of one. The storage cost is NBYTES bits per entry — for a 280-bit entry, 35 bits, or 12.5% overhead.

Why this is the right escalation and a stronger single bit is not. Parity strength is not a dial. One bit over N bits detects odd multiplicities and nothing more, regardless of how it is computed. The only way to detect more patterns is more bits, arranged either as more groups (parity) or as a code with distance greater than 2 (ECC).

The diagnostic bonus is worth as much as the coverage. lane_par_fail is a vector, so a failure identifies which byte was corrupted. In post-silicon that is the difference between "an entry was corrupt" and "byte 17 of the entry was corrupt", and the second is what correlates against a physical layout to find a marginal cell.

Contract. The group boundaries must match how the data is actually written. If the RAM is written in 32-bit words but parity is computed per byte, a partial write must update exactly the parity bits for the bytes it wrote — a partial write that recomputes all parity bits from a partially stale read is a corruption generator, not a detector.

DV. Single-bit flips in every byte position, checking that the correct bit of lane_par_fail sets. Then two flips in the same byte (not detected) and two flips in different bytes (detected) — the pair documents the coverage boundary precisely.

25. Wrong RTL — Protecting the Payload and Not the Metadata

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — parity covers the payload only. The metadata that says WHERE the
// payload belongs is unprotected.
always_ff @(posedge clk)
  if (replay_write)
    ram[write_idx] <= '{ payload: wr_payload,
                         meta:    wr_meta,
                         parity:  ^wr_payload };      // meta NOT covered

The failure, and it is worse than a corrupt payload. A bit flips in the entry's meta field — specifically in the transaction identity.

  1. The entry is read. Parity over the payload passes, because the payload is intact.
  2. The object is retransmitted with a correct CRC over correct data.
  3. The receiver's CRC passes.
  4. The receiver's sequence check passes, because the sequence number was not the corrupted field.
  5. Perfectly intact data is delivered against the wrong transaction identity.

Every detector in the chapter passes, and the result is wrong. This is §6's row six — a valid payload paired with valid-looking metadata that does not belong to it — and it is the failure mode that no integrity check catches, reached here through an unprotected field rather than through a pipeline misalignment.

Three consequences of that specific corruption.

The waiting requester gets someone else's data. 12.4's response-matching machinery matches on the identity, and the identity is now a different valid identity. So the response is delivered, matched, and accepted — against the wrong request.

The real transaction times out. Its response was consumed by another entry, so nothing arrives for it. The visible symptom is a timeout on transaction X while transaction Y received corrupt data — two symptoms, neither pointing at a parity gap in a replay RAM.

And a well-formed data corruption is the worst outcome available. 12.3 established that data delivered against the wrong request is undetectable at the protocol layer and detectable only by an end-to-end scoreboard. In silicon there is no scoreboard.

The protection boundary must span everything whose corruption changes the meaning of the object — payload and metadata together. A detector that protects the data and not the identity protects the least dangerous half.

26. Sequence and History Checking

The second detector class, and the one that catches what a CRC structurally cannot.

The receiver holds a model of transport history. At minimum: which unit it expects next. From that, four faults become detectable:

FaultHow the model detects it
Lossthe arriving unit's sequence is ahead of the expectation
Duplicationthe arriving unit's sequence is one already committed
Reorderingthe arriving unit's sequence is behind the expectation but not committed
Stale replaya unit from a previous epoch arrives after re-initialisation

27. Expected-Sequence State

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE expected-sequence tracking. Generic transport-history
// architecture — UCIe's sequence semantics are not published (Section 26).
localparam int SEQ_W = 8;                       // width IS verified; rules are not
 
logic [SEQ_W-1:0] expected_seq_q;
logic [SEQ_W-1:0] last_committed_q;
 
// THE CRITICAL QUALIFICATION: the expectation advances only when a unit is
// ACCEPTED — meaning it arrived, and it passed the checks that precede the
// sequence check. A discarded unit must not move the expectation (Section 28).
wire unit_accepted = unit_complete && framing_ok && crc_ok;
 
wire seq_matches   = (rx_seq == expected_seq_q);
assign sequence_ok = unit_accepted && seq_matches;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    expected_seq_q   <= '0;
    last_committed_q <= '0;
  end else if (epoch_reinit) begin
    // A link re-initialisation re-baselines transport history. This is
    // link-epoch state — the same lifetime class as credits (13.1 Sec 15).
    expected_seq_q   <= epoch_initial_seq;
    last_committed_q <= epoch_initial_seq - SEQ_W'(1);
  end else if (sequence_ok) begin
    expected_seq_q   <= expected_seq_q + SEQ_W'(1);   // modular, wraps
    last_committed_q <= rx_seq;
  end
end

Architecture. Two registers: what is expected next, and what was last committed. The second is what makes duplicate detection possible (§31) — with only an expectation, a duplicate and a reordering are indistinguishable.

State. Both are link-epoch state, the same lifetime class 13.1 §15 identified for credits: they describe a transport relationship that a re-initialisation re-establishes. They are not per-transaction and must not be reset by anything narrower than the event that re-baselines transport history. Getting that wrong in either direction is a real bug — reset too often and legitimate units are rejected; reset too rarely and stale units from a previous epoch are accepted.

Cycle behaviour. One writer, three arms, and sequence_ok — which requires unit_accepted — is the advance condition. That conjunction is §28.

Contract. The sequence checker depends on the CRC checker's verdict, so it is downstream of it in Figure 1. That ordering is not arbitrary: a unit whose CRC failed may have a corrupted sequence number, so its sequence number carries no information and must not be acted on.

Failure. §28 (advancing on a discarded unit), resetting on the wrong event, and comparing with unsigned magnitude instead of modular arithmetic (§30).

DV. In-order units; one dropped; one duplicated; one out of order; a unit with a bad CRC followed by its legitimate replay; and a wrap of the sequence space in every one of those cases.

28. Wrong RTL — Expectation Advances on a Discarded Unit

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the expectation advances whenever a unit arrives with the expected
// sequence, regardless of whether the unit passed its integrity check.
always_ff @(posedge clk)
  if (unit_complete && (rx_seq == expected_seq_q))     // MISSING: && crc_ok
    expected_seq_q <= expected_seq_q + SEQ_W'(1);

The trace, and it is the strongest cross-mechanism bug in the chapter. Expectation is at N.

StepEventCorrect behaviourBuggy behaviour
1Unit N arrives, CRC failsdiscard; expectation stays at Ndiscard; expectation advances to N+1
2Sender replays unit Nsequence N matches expectation N → acceptedsequence N vs expectation N+1 → REJECTED
3Receiver's viewrecovereda valid replay looks like a duplicate or a reorder
4Sender's viewacknowledged, entry retiredno acknowledgement — replay again
5Steady statenormal operationreplay N forever; the link never progresses

The result is a livelock produced by the interaction of two mechanisms that are each individually reasonable. The CRC checker correctly discarded a corrupt unit. The sequence tracker correctly advances on the expected sequence. Together they have made recovery impossible, and neither one is wrong in isolation.

Four properties worth naming.

It converts a recoverable error into an unrecoverable one. A single bit flip — an event that occurs, per the specified BER, roughly every 15.6 seconds per lane at 64 GT/s (13.5 §29) — becomes a permanent link stall. The reliability mechanism has made the link less reliable than having no mechanism at all, because without replay the unit would simply have been lost.

It requires two mechanisms to be exercised together to appear. A testbench that injects CRC errors without a replay path sees the discard and nothing more. A testbench that tests replay without injecting errors never triggers a replay of a discarded unit. Only the intersection finds it, which is precisely why §39's injection matrix must cross error injection with the retry path rather than testing them separately.

Every safety assertion passes. No corrupt data was delivered. No sequence was accepted out of order. Occupancy is legal, credits are legal, the conservation scoreboard balances — nothing was lost, it is being retransmitted. This is 13.3 §19's vacuity again, and the detection requires either the bounded-progress liveness property or a watchdog.

And its symptom points at the wrong mechanism. The visible behaviour is "replay is not working" or "the link hangs after a CRC error", which sends a debugger to the retry logic. The bug is in the sequence tracker, three modules away, in a line that does not mention replay.

Transport-history state must advance only on a unit that was actually accepted. A discarded unit did not happen, and the receiver's model of history must reflect that — or the recovery mechanism is aimed at a target that has moved.

29. SVA — Sequence Advances Only on Accepted Units

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The assertion that catches Section 28.
property p_seq_advances_only_on_accept;
  @(posedge clk) disable iff (!rst_n)
    (!sequence_ok && !epoch_reinit) |=> $stable(expected_seq_q);
endproperty
a_seq_advances_only_on_accept: assert property (p_seq_advances_only_on_accept);
 
// The specific, sharper form: a failed CRC must never move the expectation.
property p_bad_crc_does_not_advance_seq;
  @(posedge clk) disable iff (!rst_n)
    (unit_complete && !crc_ok) |=> $stable(expected_seq_q);
endproperty
a_bad_crc_does_not_advance_seq: assert property (p_bad_crc_does_not_advance_seq);
 
// And the advance itself is exactly one, modularly — not a jump.
property p_seq_advances_by_one;
  @(posedge clk) disable iff (!rst_n)
    sequence_ok |=> (expected_seq_q == SEQ_W'($past(expected_seq_q) + 1));
endproperty
a_seq_advances_by_one: assert property (p_seq_advances_by_one);

Architecture. Three properties: it does not advance when it should not, it specifically does not advance on a bad CRC, and when it does advance it advances by exactly one.

Why the second property exists when the first subsumes it. The first is the general contract; the second names the specific failure. When the second fails, the counterexample and the assertion name together say what is wrong — which matters when a failure is triaged by someone who did not write the checker. A general property that fails tells you something is wrong; a specific one tells you what.

Why the third property matters. A jump of more than one indicates either a lost-unit path that advances speculatively, or a wrap handled with non-modular arithmetic (§30). It is written with an explicit width cast so that the wrap case passes — writing it as plain + 1 would fail at the wrap boundary, and the property would then be weakened or deleted.

Contract. All three are safety properties over the sequence tracker's own state, and all three should be permanently enabled.

DV. These must be exercised with CRC errors injected while a replay path is active, which is §28's intersection. Cover it explicitly — the properties are meaningless if their antecedents never occur.

30. Modular Comparison, and Why Magnitude Fails

Finite sequence counters wrap, and a wrap is where naive comparison breaks. With SEQ_W = 8 the space is 0 to 255, and after 255 comes 0.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — magnitude comparison. Inverts at the wrap.
wire seq_is_newer_wrong = (rx_seq > last_committed_q);
// last_committed = 254, rx_seq = 1 (legitimately newer, after a wrap)
//   → 1 > 254 is FALSE → a valid newer unit is judged older.
 
// Correct — modular distance. Valid for any true distance under half the
// sequence space, which is the standard constraint on such comparisons.
wire [SEQ_W-1:0] fwd_distance   = rx_seq - last_committed_q;      // wraps
wire             seq_is_newer   = (fwd_distance != '0)
                               && (fwd_distance < SEQ_W'(1 << (SEQ_W-1)));
wire             seq_is_older   = !seq_is_newer && (fwd_distance != '0);
wire             seq_is_current = (fwd_distance == '0);

Architecture. Subtract, then interpret the result as a signed distance. The subtraction wraps naturally in fixed-width arithmetic, which is exactly what is wanted.

The constraint this imposes, and it is a real design rule. The comparison is only valid while the true distance between any two live sequence numbers is less than half the sequence space. So:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
max_in_flight_units  <  2**(SEQ_W-1)

With SEQ_W = 8, at most 127 units may be simultaneously in flight or awaiting confirmation. That is a sizing relationship between the sequence width and the outstanding-unit capacity13.5 §17's replay-window term and the sequence width are not independent parameters, and a design that grows one without checking the other creates an aliasing hazard.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The relationship, checked at elaboration rather than hoped for.
initial assert (MAX_UNITS_IN_FLIGHT < (1 << (SEQ_W-1)))
  else $fatal(1, "sequence space too small for the in-flight window — comparisons will alias");

Failure. Magnitude comparison inverts the ordering across the wrap, so approximately one in every 2**SEQ_W units is misjudged — a periodic, low-rate, data-independent failure that looks like a rare intermittent channel error and is one of the hardest signatures to attribute correctly.

DV. Drive the sequence space through a full wrap, several times, with units arriving in order, out of order, and duplicated across the wrap boundary. Random stimulus reaches the wrap only after 256 units and will not systematically test around it — this needs a directed test that starts near the boundary.

31. Duplicate Detection, and Where the Response Lives

A unit whose sequence is already committed is a duplicate, and a duplicate is normal rather than exceptional: a lost confirmation causes the sender to retransmit an object that did arrive.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE duplicate classification. DETECTION only — the response is
// 14.2's and 14.3's, and deliberately not decided here.
wire is_duplicate     = seq_is_current || seq_is_older;
wire is_gap           = seq_is_newer && (fwd_distance > SEQ_W'(1));
wire is_next_expected = seq_is_newer && (fwd_distance == SEQ_W'(1));
 
// A duplicate must NOT be delivered a second time — the semantic consumer
// already acted on it. That much IS a detection-layer obligation.
assign deliver_allowed = is_next_expected && crc_ok && framing_ok;

Architecture. Three mutually exclusive classifications, and one gate. Note what the code does not do: it does not decide whether to send a negative acknowledgement, request a replay, drop silently, or escalate. Those are recovery decisions and they belong to 14.2 and 14.3.

Why one part of the response does belong here. A duplicate must not be delivered twice. That is not a recovery policy — it is the delivery gate doing its job, because delivering the same write or the same coherence response twice is a semantic corruption. Detection owns "do not deliver it again"; recovery owns "and here is what we tell the sender."

The distinction is worth stating precisely because it is where chapters blur:

ConcernOwner
Recognising that a unit is a duplicate14.1 — this chapter
Not delivering it a second time14.1 — this chapter
Whether to acknowledge it, and how14.3 — Retry Mechanisms
Whether repeated duplicates escalate to recovery14.2 — Error Recovery

Failure. Treating every duplicate as an error and escalating. A duplicate is expected traffic in any design with a confirmation mechanism, and a design that escalates on the first one will escalate constantly on a healthy link — §38's false-positive class, arriving through the sequence checker rather than the CRC.

DV. A duplicate of the immediately previous unit; a duplicate from far back in the space; a duplicate arriving after a wrap; and a duplicate arriving during a gap, which is where the classification logic is most likely to be wrong.

32. Error Pulse Versus Sticky Status

Detection produces an event. Debug needs a record. Those have different lifetimes and need different registers.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE error state, THREE lifetimes in one block.
logic                  crc_error_pulse;      // 1 cycle — for the gate and the log
logic                  crc_error_seen_q;     // STICKY  — was there ever one?
logic [ERR_CNT_W-1:0]  crc_error_count_q;    // SATURATING — how many?
 
assign crc_error_pulse = unit_complete && !crc_ok;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    crc_error_seen_q  <= 1'b0;
    crc_error_count_q <= '0;
  end else begin
    // Sticky: set once, cleared only by an explicit software or epoch action.
    if (crc_error_pulse) crc_error_seen_q <= 1'b1;
    else if (error_status_clear) crc_error_seen_q <= 1'b0;
 
    // Saturating: a diagnostic, so it must not wrap (Section 33).
    if (crc_error_pulse && !(&crc_error_count_q))
      crc_error_count_q <= crc_error_count_q + 1'b1;
    else if (error_status_clear)
      crc_error_count_q <= '0;
  end
end

Architecture. Three representations of the same event because three consumers need different things. The gate needs the pulse, this cycle. The software status read needs the sticky bit, which may be read milliseconds later. The rate analysis needs the count.

State. Three lifetimes: per-cycle, until-cleared, and until-cleared-with-magnitude. The sticky bit's clear condition is an explicit action, never a reset of the surrounding logic — an error record that a local reset erases is a record that disappears exactly when something went wrong enough to cause a reset.

Cycle behaviour. The pulse is combinational from the checker. The other two are registered with explicit clear paths.

Contract. Any consumer reading error status must not assume the pulse is still visible. A one-cycle pulse is invisible to software and to a slow debug interface, and a design that provides only the pulse has provided nothing usable — the error happened, was correctly detected, correctly blocked delivery, and left no trace anyone can read.

Failure. Pulse only, per above. Or a sticky bit that is cleared by reading it, which loses the record if two readers exist. Or a count that wraps (§33).

DV. Inject one error and check all three: the pulse for one cycle, the sticky bit set and remaining set for many cycles, the count at one. Then inject 2**ERR_CNT_W + 5 errors and check the count saturates rather than wrapping.

33. Saturating Diagnostics, Never Saturating Accounting

The rule this module and Module 13 share, stated once with both halves.

CounterMust saturate?Why
Error counts (§32)yesa wrapped error count can read as zero during a storm — the worst possible reading, since it says "healthy" at the moment of maximum unhealth
Congestion age (13.4 §13)yesa wrapped age de-escalates a hang
Credit counters (13.1 §7)neversaturation invents permission — the counter would claim credits that were never returned
Occupancy counters (13.2 §5)neversaturation loses track of real entries, and the buffer overflows

Saturate diagnostics. Never saturate accounting. The test is whether the counter's value is a report or a permission — a report may be clipped without harming anything, and a permission may not.

The reasoning generalises, and it is worth internalising because both mistakes are made regularly. An error count of "255 or more" is fully actionable. An error count that wrapped to 3 is worse than no counter at all, because it is actively misleading. Conversely a credit count clipped at its maximum will authorise a transmission the receiver has no room for, which is 13.1 §2's catastrophic over-advertisement direction.

34. First-Error Capture

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE first-error capture. Latch once; never overwrite.
logic                first_error_valid_q;
logic [CAUSE_W-1:0]  first_error_cause_q;
logic [SEQ_W-1:0]    first_error_seq_q;
logic [LAYER_W-1:0]  first_error_layer_q;
logic [31:0]         first_error_time_q;      // cycle stamp
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    first_error_valid_q <= 1'b0;
  end else if (!first_error_valid_q && error_event) begin
    first_error_valid_q <= 1'b1;              // the ONLY set, and it is once
    first_error_cause_q <= error_cause;
    first_error_seq_q   <= error_seq;
    first_error_layer_q <= error_layer;
    first_error_time_q  <= cycle_counter;
  end else if (error_status_clear) begin
    first_error_valid_q <= 1'b0;
  end
end

Architecture. Five registers and one guard: !first_error_valid_q. That guard is the mechanism.

State. Until-explicitly-cleared. This is the record that survives a storm.

Cycle behaviour. Latched on the first error_event after a clear, and then frozen.

Contract. Nothing may overwrite it. That includes a "more severe" error arriving later — severity is not the ordering that matters, causality is. A parity failure followed by a thousand CRC failures is very likely one root cause and a thousand symptoms, and the parity failure is the one that identifies it.

Failure — and this is the most common diagnostic error in error handling. Keeping only the most recent error. §37 develops why: a single physical event corrupts multiple consecutive units, and a design that overwrites reports the last symptom. The debugger then investigates a unit that failed because of something that happened earlier, in a different layer.

DV. Inject three errors of different kinds in successive cycles and verify the record holds the first. Then inject a more severe error second and verify it does not overwrite. Then clear and inject again, verifying the record re-arms.

35. The Error Record — Localisation

An error event should answer five questions. If it cannot, the debug session begins with guesswork.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE diagnostic record. NOT a UCIe-visible register format — no
// official source defines one beyond the existence of the UCIe 1.1 per-Lane
// error log register (Section 3), whose layout is not published.
typedef enum logic [2:0] {
  ELAYER_PHY     = 3'd0,     // physical / lane
  ELAYER_ADAPTER = 3'd1,     // framing, CRC, sequence
  ELAYER_STORAGE = 3'd2,     // local RAM parity (Section 23)
  ELAYER_PROTO   = 3'd3      // semantic / association
} err_layer_e;
 
typedef enum logic [2:0] {
  EKIND_FRAMING  = 3'd0,
  EKIND_CRC      = 3'd1,
  EKIND_SEQ_GAP  = 3'd2,
  EKIND_SEQ_DUP  = 3'd3,
  EKIND_PARITY   = 3'd4
} err_kind_e;
 
typedef struct packed {
  logic              valid;
  err_layer_e        layer;      // WHICH LAYER owns the detector
  err_kind_e         kind;       // WHICH DETECTOR fired
  logic [SEQ_W-1:0]  seq;        // WHICH OBJECT
  logic              repeated;   // FIRST or one of many (Section 37)
  logic              retryable;  // classification, where defined
} error_record_t;

Architecture. A record whose fields map one-to-one onto the questions a triage needs: which layer, which detector, which object, first or repeated, and whether the class is one recovery can address.

Why layer and kind are separate fields. They answer different questions and they are not redundant. kind = EKIND_CRC says a redundancy check failed; layer = ELAYER_ADAPTER says where. A parity failure at ELAYER_STORAGE and a CRC failure at ELAYER_ADAPTER may describe the same root cause with two detectors firing, and only the pair distinguishes that from two unrelated faults.

Why repeated is one bit rather than a count. The count lives in §32's per-detector counters. This bit answers "is this record the first?" — which is what tells a reader whether to trust it as a root cause (§37).

Contract — stated because it is a real constraint. This is not a UCIe-visible register format. UCIe 1.1 defines a per-Lane error log register with interrupt capability (§3), and its layout is not published. So this record is an internal diagnostic structure; mapping it to whatever software-visible registers your revision defines is an integration step, not something to infer.

Failure. Collapsing layer and kind into one enum, which loses the two-detectors-one-cause case. Or omitting seq, which makes it impossible to correlate the error against a trace.

DV. For each combination of layer and kind that the design can produce, inject it and check the record. That is a small matrix and it is fully directed.

36. Separate Counters Per Detector

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE per-detector counters. All saturating (Section 33).
logic [ERR_CNT_W-1:0] cnt_framing_q;
logic [ERR_CNT_W-1:0] cnt_crc_q;
logic [ERR_CNT_W-1:0] cnt_seq_gap_q;
logic [ERR_CNT_W-1:0] cnt_seq_dup_q;
logic [ERR_CNT_W-1:0] cnt_parity_q;

Why not one error_count. Because the ratios between these five are the diagnosis, and a single total destroys them:

Pattern across the five countersDiagnosis
CRC high, everything else zerochannel corruption, or §12's stall bug — check whether errors correlate with backpressure
Parity high, CRC zerolocal storage corruption (§23) — the link is fine and the fault is on this die
Sequence-gap high, CRC high, similar magnitudesunits are being discarded for CRC and the gaps are the consequence — one cause, two counters
Sequence-gap high, CRC zerogenuine loss with no corruption — a framing or physical dropout, not a bit-error problem
Sequence-dup high, CRC zeroconfirmations are being lost — a reverse-path problem, and 13.1 §11's piggyback hazard is a candidate
Sequence-gap high after every CRC error, and the link stalls§28 — expectation advancing on discarded units
Framing higha length or structure disagreement; check the partial-beat rule (§14)

Three of those seven rows are impossible to reach with a single counter, and two of them — local storage corruption, and §28's livelock — are the highest-value diagnoses in the chapter. Five registers, and they replace a debug session with a table lookup.

And the third row is the one that prevents double-counting a root cause. CRC and sequence-gap counters rising together, in step is one fault with two symptoms. §37 is that point in general.

37. Error Bursts Versus Independent Faults

A single physical disturbance corrupts multiple consecutive units. A crosstalk event, a supply droop, a thermal excursion — these have durations, and a duration at 64 GT/s spans many units.

Do not count every symptom as an independent root cause. A burst of 40 CRC failures in 40 consecutive units is one event, and a design that reports 40 errors has reported one event 40 times.

Three implications.

The counters measure symptoms, and that is fine as long as it is known. A CRC error count of 40,000 does not mean 40,000 independent faults; it means 40,000 corrupted units, which may be a handful of bursts. Both numbers are useful and they are different numbers, so a design that wants both needs a burst-detection heuristic — a count of errors, and a count of error episodes separated by a clean gap, which is structurally the same distinction 13.4 §29 made for congestion episodes.

First-error capture is what makes the burst interpretable. Within a burst, the first error's layer and kind identify the mechanism; the rest are consequences. §34's non-overwriting record is exactly the right structure for burst behaviour, and that is not a coincidence — it is why the guard is !first_error_valid_q rather than a severity comparison.

And a burst can cross detector types, which is the diagnostic gift. A disturbance long enough to corrupt several units produces CRC failures and, if any unit is lost entirely, sequence gaps. Seeing both rise together with the same timing is evidence for a single physical event; seeing CRC rise while sequence stays flat is evidence for corruption without loss. Two counters, two different physical stories.

38. False Positives Are Bugs Too

A detector that rejects good objects is broken, and it is broken in a way that is easy to misattribute to the channel.

SymptomLikely checker bugWhy it looks physical
Every unit fails, immediately after resetCRC_INIT wrong, or the accumulator not initialised (§11)reads as a catastrophically bad channel
Only short units failthe zero-extension or byte-masking rule (§9, §14)reads as a length-dependent signal-integrity effect
Only units with a stall fail§12 — CRC advancing while stalledreads as an activity-dependent noise problem
Failures alternate with passes on consecutive units§15 — verdict misaligned by onereads as periodic interference
Roughly one unit per sequence-space wrap fails§30 — magnitude comparison instead of modularreads as a rare intermittent fault
Every duplicate escalates§31 — duplicates treated as errorsreads as a noisy link whenever a confirmation is lost
Failures only at full rate, none with idle gaps§11 — the accumulator not re-armed between back-to-back unitsreads as a rate-dependent margin problem

Every row's rightmost column is the reason this section exists: a broken checker's signature is indistinguishable from a bad channel unless you know the patterns. An engineer who does not will spend the session on eye margin, temperature and equalisation, and find nothing — because there is nothing there.

And the cost of a false positive is not just wasted debug. Each false rejection triggers a retry, which consumes replay bandwidth, arbitration opportunities and queue residency — so a false-positive bug enters 13.4 §26's amplification chain and presents as a throughput collapse. A checker that condemns 10% of good traffic produces a link that is slow, noisy-looking, and physically perfect.

The most useful first question when a link looks noisy is not "how bad is the channel?" but "does the pattern of failures match a physical mechanism, or a logical one?" The patterns above are logical, and they are cheaper to check than an eye diagram.

39. Error Injection — the Matrix

UCIe's own compliance methodology asks for this. UCIe 1.1 requires the golden die to be able to "inject errors in Flits, inject errors in the sideband", and PHY compliance to "inject errors and cause time-outs in various phases of training" (§3). The matrix below is that requirement, expanded into the cases that find this chapter's bugs.

#InjectionWhich bug it targets
1Single-bit payload corruptionbaseline CRC detection
2Multi-bit corruption within the detection guaranteethe 3-bit guarantee (§8)
3Corruption of metadata inside the protected regionthat the protected region is what you think it is (§9)
4Corruption of a field outside the protected regionproves the exposure — and that a second detector is needed (§25)
5Corruption of the transmitted CRC itselfthe checker fails safe when the redundancy is corrupt, not the data
6Corruption during a stalled beat§12 — the archetype
7Corruption on the first beat and on the last beatboundary handling in the accumulator (§11)
8Corruption of a unit with a partial final beat§14's masking rule
9Two consecutive corrupted units, back to backthe re-arm path (§11), and §15's misalignment
10A clean unit immediately after a corrupted one§15 and §28 together — the highest-value case in the matrix
11A duplicated sequence§31
12A skipped sequence§26's gap detection
13A duplicate arriving after a wrap§30's modular comparison
14A stale unit from a previous epoch§27's epoch lifetime
15Parity bit corruption in a stored entry§23
16Payload corruption in storage after the CRC was computed§23's whole reason — the CRC will pass
17Metadata corruption in storage§25 — every detector passes and the result is wrong
18A burst spanning many consecutive units§37, and first-error preservation (§34)
19A CRC error while the replay path is active§28's livelock — requires the intersection
20A clean channel with an intentionally mis-initialised checker§38's false positives

Three properties of this matrix worth stating.

Random bit-flipping covers rows 1, 2 and maybe 18, and nothing else. Rows 6, 9, 10, 16, 17 and 19 all require the injection to be correlated with a design state — a stall, a boundary, a storage write, an active replay. Random error injection is the least effective part of an error-injection strategy, and it is usually all that exists.

Row 19 is the one to build first. It requires error injection and the retry path to be exercised simultaneously, and it finds §28 — the bug that turns a recoverable single-bit error into a permanent link stall. A design that has never run this test has never verified that its error recovery can actually recover.

And rows 16 and 17 need an injection point most testbenches do not have: inside a storage array, after the write and before the read. Building that hook is real work, and it is the only way to verify §23 and §25 at all.

40. SVA Inventory

The complete assertion set, by category. The category matters as much as the property, because it determines what a failure means.

PropertyCategoryCatches
CRC state stable when not accepted (§13)safety§12 — the archetype
CRC state advances on accept (§13)safetya checker that stalls its own accumulation
CRC initialised at unit start (§13)safety§38's every-unit-fails
Byte-valid contiguous (§14)safetyupstream framing bugs
Verdict matches object identity (§16)safety§15 — the false-pass generator
Verdict is the computed one (§16)safety, needs a reference§15 completely
No delivery of a failed object (§19)safety — the chapter's core property§20 and any un-gated check
Sequence advances only on accept (§29)safety§28 — the livelock
Bad CRC does not advance sequence (§29)safety§28, named specifically
Sequence advances by exactly one (§29)safetyspeculative advance; wrap arithmetic
Parity failure raises a local error (§23)safetya detector that detects and does not report
First error preserved (§34)safetyoverwriting a root cause
Error counters monotonic and saturating (§32, §33)safetywrapping diagnostics
Bounded progress after a detected errorliveness — assumptions required§28's stall, which no safety property sees

The last row is the one to write deliberately. Every safety property above passes on §28's livelock: no corrupt data delivered, no sequence accepted out of order, everything legal, link permanently stalled. 13.3 §19's lesson applies here in full — detection of a stall caused by the error mechanism requires a bounded-progress property with its assumptions written down, or a watchdog that works in silicon.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// LIVENESS. Under the assumption that the channel eventually delivers a clean
// unit, a detected error must not permanently prevent progress.
//
//   A1: a clean unit is eventually offered (the channel is not permanently dead)
//
// Without A1 this property MUST fail, correctly — a dead channel cannot progress.
assume property (@(posedge clk) disable iff (!rst_n)
  ##[1:RECOVERY_BOUND] clean_unit_offered);
 
property p_progress_after_error;
  @(posedge clk) disable iff (!rst_n)
    (unit_complete && !crc_ok) |-> ##[1:RECOVERY_BOUND] object_deliver;
endproperty
a_progress_after_error: assert property (p_progress_after_error);

41. The Detection Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Verification-only reference model. Not synthesisable.
//
// The scoreboard's job is to check the DELIVERY DECISION against ground truth,
// which the DUT cannot do for itself. It distinguishes the two failure
// directions, and both matter (Section 38).
class detection_scoreboard;
 
  typedef struct {
    bit [PAY_W-1:0]   payload;      // as TRANSMITTED
    bit [META_W-1:0]  meta;         // as TRANSMITTED
    bit [SEQ_W-1:0]   seq;
    bit               was_corrupted; // did the injector corrupt it?
    bit               was_dropped;
    bit               was_duplicated;
  } tx_record_t;
 
  tx_record_t     sent[$];
  bit [SEQ_W-1:0] model_expected_seq;
  bit [SEQ_W-1:0] model_last_committed;
 
  int unsigned    misses;            // bad object DELIVERED
  int unsigned    false_positives;   // good object REJECTED
  int unsigned    assoc_errors;      // right bits, WRONG transaction
 
  // ---- The central check. Called on every delivery decision.
  function void check_decision(bit delivered, bit [PAY_W-1:0] got_payload,
                               bit [META_W-1:0] got_meta, bit [SEQ_W-1:0] got_seq);
    tx_record_t exp = lookup_by_seq(got_seq);
 
    // Direction 1: a corrupted object was delivered. DETECTOR MISS.
    if (delivered && exp.was_corrupted) begin
      misses++;
      $error("DETECTOR MISS: corrupted unit seq %0h delivered", got_seq);
    end
 
    // Direction 2: a clean object was rejected. FALSE POSITIVE.
    if (!delivered && !exp.was_corrupted && !exp.was_dropped
                   && (got_seq == model_expected_seq)) begin
      false_positives++;
      $error("FALSE POSITIVE: clean in-order unit seq %0h rejected", got_seq);
    end
 
    // Direction 3: the bits are right and they arrived against the wrong
    // identity. NO DETECTOR IN 14.1 CATCHES THIS (Section 6, row six).
    if (delivered && (got_payload == exp.payload) && (got_meta != exp.meta)) begin
      assoc_errors++;
      $error("ASSOCIATION ERROR: seq %0h payload correct, metadata mismatch", got_seq);
    end
 
    // Direction 4: a duplicate delivered twice.
    if (delivered && already_delivered(got_seq)) begin
      misses++;
      $error("DUPLICATE DELIVERY: seq %0h delivered a second time", got_seq);
    end
  endfunction
 
  // ---- The model of expectation, maintained independently of the DUT so a
  //      shared bug cannot hide (Section 28 specifically).
  function void model_advance(bit accepted, bit [SEQ_W-1:0] seq);
    if (accepted) begin
      model_last_committed = seq;
      model_expected_seq   = seq + 1;
    end
    // NOT accepted: the model does NOT advance. Section 28 in one line.
  endfunction
 
  function void report();
    $display("detection: misses %0d  false positives %0d  association %0d",
             misses, false_positives, assoc_errors);
    if (misses != 0)          $error("detector misses — corrupted data was delivered");
    if (false_positives != 0) $error("false positives — good data was rejected");
    if (assoc_errors != 0)    $error("association errors — no 14.1 detector sees these");
  endfunction
 
endclass

Architecture. Four checks in three categories, and the categorisation is the design.

Direction 1 — detector miss — is the failure everyone tests for. A corrupted object reached the consumer. It is the obvious check and it is not the most interesting one.

Direction 2 — false positive — is the failure that gets skipped, and §38 established it is both common and expensive. A scoreboard that only checks direction 1 will pass a checker that rejects 10% of good traffic, and the design will look like a bad channel forever.

Direction 3 — association error — is the one no detector in this chapter catches. The payload is right, the metadata is wrong, every check passes. Only a scoreboard holding the transmitted pair can see it, which is why §6 row six lists it as detector-proof and why this check exists here rather than in RTL.

Why the model maintains its own expectation. If the scoreboard read the DUT's expected_seq_q, it would agree with the DUT about §28's bug. An independent model is the only thing that catches a systematically wrong expectation — the same argument 13.4 §30 made for recomputing occupancy from ingress and egress rather than reading the DUT's counter.

Failure it catches that assertions do not. Direction 3 entirely, and direction 2 in the general case — an assertion can catch "delivery without checks passing" but cannot know whether a rejection was justified, because that requires knowing what was sent.

42. Coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_error_detection @(posedge clk);
  option.per_instance = 1;
 
  // --- Clean traffic must dominate, and must be covered.
  cp_outcome : coverpoint unit_outcome {
    bins clean         = {OUT_CLEAN};
    bins crc_fail      = {OUT_CRC};
    bins framing_fail  = {OUT_FRAMING};
    bins seq_gap       = {OUT_SEQ_GAP};
    bins seq_dup       = {OUT_SEQ_DUP};
    bins parity_fail   = {OUT_PARITY};
  }
 
  // --- WHERE in the unit the error landed. Boundary beats are where the
  //     accumulator's lifetime logic is weakest (Section 11).
  cp_err_position : coverpoint error_beat_position {
    bins first  = {0};
    bins middle = {[1:BEATS-2]};
    bins last   = {BEATS-1};
  }
 
  // --- THE BIN THAT MATTERS MOST: was the unit stalled during accumulation?
  //     Section 13's assertions are vacuous without this.
  cp_stall_during_crc : coverpoint stalled_cycles_in_unit {
    bins none    = {0};
    bins one     = {1};
    bins several = {[2:$]};
  }
 
  // --- Partial final beat, every legal byte count (Section 14).
  cp_final_bytes : coverpoint final_beat_valid_bytes {
    bins each[] = {[1:BEAT_BYTES]};
  }
 
  // --- Back-to-back units, which is where re-arm and alignment fail.
  cp_gap : coverpoint cycles_between_units {
    bins back_to_back = {0};
    bins one          = {1};
    bins spaced       = {[2:$]};
  }
 
  // --- Sequence-space position, so the wrap is actually exercised.
  cp_seq_region : coverpoint rx_seq {
    bins low  = {[0 : (1<<SEQ_W)/4]};
    bins mid  = {[(1<<SEQ_W)/4+1 : 3*(1<<SEQ_W)/4]};
    bins high = {[3*(1<<SEQ_W)/4+1 : (1<<SEQ_W)-1]};
  }
  cp_wrap_seen : coverpoint seq_wrapped;
 
  // --- Was the replay path active when the error occurred? Section 39 row 19.
  cp_replay_active : coverpoint replay_in_progress;
 
  // --- Diagnostics.
  cp_first_err_kind : coverpoint first_error_cause_q;
  cp_burst_len      : coverpoint consecutive_error_units {
    bins single = {1};
    bins short  = {[2:4]};
    bins burst  = {[5:$]};
  }
 
  // --- The crosses that carry the real information.
  x_crc_stall     : cross cp_outcome, cp_stall_during_crc;   // Section 12
  x_err_position  : cross cp_outcome, cp_err_position;       // Section 11
  x_err_gap       : cross cp_outcome, cp_gap;                // Sections 15, 11
  x_err_replay    : cross cp_outcome, cp_replay_active;      // Section 28
  x_dup_wrap      : cross cp_outcome, cp_seq_region;         // Section 30
endcovergroup

Five bins whose value is being non-zero, each proving a mechanism was exercised:

cp_stall_during_crc.several, crossed with a clean outcome. Without a stall during accumulation, §13's three assertions have never had a meaningful antecedent. A regression reporting none only has not tested the archetype bug in the chapter, and cannot distinguish a correct checker from §12's.

x_err_replay with crc_fail and replay_active both true. This is §39's row 19 and §28's livelock. Testing CRC errors with the replay path idle, and replay with a clean channel, exercises neither.

cp_gap.back_to_back crossed with crc_fail. §15's misalignment and §11's re-arm both need consecutive units with no idle cycle.

cp_final_bytes — every bin. §14's masking rule fails on specific byte counts, and the sweep is cheap.

cp_wrap_seen. §30's modular comparison is only tested past a wrap, which takes 2**SEQ_W units to reach naturally.

And one bin whose value should be zero in a clean regression: any misses count from §41's scoreboard. A non-zero detector-miss count in a nightly run is the highest-severity result this chapter can produce.

43. Flagship Trace — One Clean Unit, One Corrupted

Illustrative. 4-beat units, a 1-cycle checker latency, expectation starting at sequence 0x40. Unit A (seq 0x40) is clean; unit B (seq 0x41) has a corrupted payload beat.

CycBeatvalidreadyCRC folds?crc_qUnit eventVerdictExpected seqDeliveredError record
0A.011yesinit→a0A starts0x40
1A.110noa0 (held)stalled0x40
2A.111yesa1resumes0x40
3A.211yesa20x40
4A.311yesa3A complete0x40
5B.011yesinit→b0B startsA: pass0x40A
6B.111yesb10x41
7B.2✗11yesb2 (corrupt)0x41
8B.311yesb3B complete0x41
901noinitB: FAIL0x41noneCRC, seq 0x41, first
10C.011yesinit→c0C (replay of B)0x41held
11C.111yesc10x41held
12C.211yesc20x41held
13C.311yesc3C complete0x41held
1401noinitC: pass0x41C (as 0x41)held
1501noinit0x42held

Seven things to read, and the negative events are the important ones.

Cycle 1 is the chapter's central lesson in one row. valid is high, ready is low, the data on the bus is beat A.1 — and the CRC does not fold. The accumulator holds a0. §12's bug would fold A.1 here and again at cycle 2, and unit A would then fail with no channel error whatsoever.

Cycle 5 shows the verdict trailing the unit by one cycle, and shows delivery happening at the verdict rather than at completion. A completed at cycle 4; it is delivered at cycle 5, when its verdict exists. The one-cycle gap is §17's detection latency, and A had to be held across it.

Cycles 5 to 8 have B accumulating while A's verdict is being applied. Two objects are in the checker pipeline simultaneously. This is exactly the condition §15's misalignment bug corrupts — and the reason the verdict must carry an identity is visible right here: at cycle 5 there is a verdict for A and a partially accumulated B, and nothing in the datapath inherently says which is which.

Cycle 9: B fails and is not delivered. The Delivered column reads none. Not "delivered with an error flag" — none. That is §19's gate, and §20's bug is the version of this table where the Delivered column reads B at cycle 8 and the verdict arrives at cycle 9.

Cycle 9 also holds the expectation at 0x41. B was discarded, so the receiver's model of history does not advance. §28's bug advances it to 0x42 here, and then C — the legitimate replay of 0x41 — is rejected at cycle 14, and the link never progresses.

Cycles 10 to 14: the replay arrives and passes. C is the same object with sequence 0x41. It matches the expectation, its CRC is clean, and it is delivered at cycle 14 as sequence 0x41. The recovery worked because the expectation was preserved — and that is the entire dependency between §27 and §12 made visible.

Cycle 15: the expectation finally advances to 0x42, after a unit was actually accepted. And the error record still holds CRC, seq 0x41, firstthe record survives the recovery, because a successful recovery does not mean the error did not happen, and post-silicon needs to know it did.

What this trace deliberately does not show is what caused the replay. The sender learning that B failed, deciding to retransmit, and managing its replay buffer are 14.2's and 14.3's material. This chapter's contribution ends at "B was detected, B was not delivered, the expectation was preserved, and enough was recorded to diagnose it."

44. Second Trace — Misalignment, and the Fix

Same units, now with §15's bug: the verdict register is not bound to an object identity. Unit A is clean, unit B is corrupted, and they arrive back to back.

Broken:

CycUnit completingVerdict register holdsVerdict applied toResult
4A(computing A)
5A: pass
8BA: passBB DELIVERED. Corrupted data accepted.
9B: fail
12C (clean)B: failCC REJECTED. Clean data discarded.

One bug, both failure directions, on consecutive units — and note the visible symptom is C's rejection at cycle 12, while the damage was B's delivery at cycle 8. An engineer investigating "why did C fail" will examine C, find it perfect, and conclude the channel is intermittent.

Fixed, with the verdict carrying an identity:

CycUnit completingverdict_qdelivering_obj_idverdict_appliesResult
4A
5{id: A, ok: 1}AyesA delivered
8B{id: A, ok: 1}Bno — id mismatchB held, not delivered
9{id: B, ok: 0}ByesB rejected correctly
12C{id: B, ok: 0}Cno — id mismatchC held
13{id: C, ok: 1}CyesC delivered correctly

The identity check does two things at once, and the second is the subtle one. It prevents the wrong verdict from being applied — that is obvious. And it converts a silent corruption into an explicit stall: at cycle 8, verdict_applies is false, so the object waits rather than proceeding on a stale verdict. A design that waits for the right answer is correct; a design that proceeds on the available answer is §15.

This is the most valuable RTL lesson in the chapter, because it generalises past error detection: any result that travels through a pipeline separately from the data it describes must carry an identity, and the consumer must check it.

45. Debug Taxonomy

SignatureMost likely causeFirst instrument
CRC errors correlate with link rate or temperaturegenuine physical margineye margin, BER measurement (UCIe 1.1 defines both)
CRC errors only during stalls§12 — checker advancing while not acceptedcorrelate error events against ready deassertion
CRC errors only on short units§9 / §14 — zero-extension or masking ruleunit-length histogram against error events
Alternating pass/fail on consecutive units§15 — verdict misaligned by onecheck whether the verdict carries an identity at all
Every unit fails immediately after reset§11 — CRC_INIT or initialisationread CRC_INIT and the reset path
Failures only at full rate, never with idle gaps§11 — accumulator not re-armed back to backinter-unit gap against error events
Sequence errors appear after every CRC failure, and the link stalls§28 — expectation advanced on a discarded unitis expected_seq_q qualified by crc_ok?
Parity errors with a clean link CRC§23 — local storage or datapath corruptionper-detector counters; the link is not the problem
Roughly one failure per sequence wrap§30 — magnitude instead of modular comparisonthe comparison expression
Duplicates escalating constantly on a healthy link§31 — duplicates treated as errorsconfirmation path; 13.1 §11's piggyback hazard
Wrong semantic data with every check clean§25 / §6 row six — association, not integritythe scoreboard, because no detector sees this
A throughput collapse with a rising error countfalse positives entering the amplification chainuseful over transmitted flits (13.5 §30)
A large error count with one root cause§37 — a burst counted as independent faultsthe first-error record, not the counters

The pattern to internalise is in the second column. Of thirteen signatures, three are physical and ten are logic bugs in the detection path. The prior probability favours a checker bug over a channel problem, and every logical cause above is cheaper to check than an eye diagram.

46. Debug Checklist

  1. Which detector fired first? Not which fired most — §34's first-error record.
  2. Which layer owns that detector? PHY, Adapter, local storage, or protocol (§35).
  3. Which object? Sequence number, so the event can be correlated against a trace.
  4. Was the unit stalled during accumulation? If errors correlate with backpressure, stop and check §12 before looking at anything physical.
  5. Did the CRC state update only on accepted beats? The one-line check: is the update qualified by valid && ready?
  6. Was the byte-valid mask handled per the framing rule? If only short or partial units fail, this is it (§14).
  7. Was the verdict aligned to the object identity? If alternating units pass and fail, this is it (§15).
  8. Did the sequence expectation advance? Read expected_seq_q across the error.
  9. Was it supposed to advance? It must not on a discarded unit (§28) — this is the question that separates a recoverable error from a permanent stall.
  10. Did a parity check fail locally? If parity is high and CRC is zero, the link is fine and the fault is on this die (§23, §36).
  11. Was semantic delivery actually blocked? Not "was an error reported" — was the object withheld (§19, §20)?
  12. Was the first error preserved, or overwritten? A record showing the last of a burst is pointing at a symptom (§34, §37).
  13. Is this one error or a burst? Error count versus episode count (§37).
  14. Does the error rate correlate with a physical variable? Rate, temperature, voltage — and if not, it is logic.
  15. Does the reference detector agree? §41's scoreboard, and specifically: are there any false positives?
  16. Are the bits right and the association wrong? If every check is clean and the data is wrong, no detector in this chapter sees it (§6, §25).

47. Common Misconceptions

"CRC detects every possible error." It has a stated guarantee — 3 bits for the Adapter's CRC (§3) — and detects most larger patterns without one. More fundamentally it is blind to whole classes: a unit that never arrived, a field outside the protected region, a corruption that occurred before the CRC was computed, and a valid unit associated with the wrong metadata (§5, §6).

"Parity and CRC solve the same problem." In a data path, parity duplicates a CRC less effectively. Its value is where a CRC does not reach: inside local storage, on a narrow control field, or at a granularity a per-unit check cannot serve. And UCIe's own defined parity mechanisms are a link-health test, a PHY marker at one bit per 8 UI, and a field in a 32 UI sideband packet — none of them a data-path protection rule (§22).

"A clean CRC proves the object is correct." It proves the protected region's bits survived transit within the detection guarantee. It says nothing about whether the unit is the one that should have arrived, whether it arrived twice, or whether its payload has been paired with the right metadata (§4, §6).

"Sequence state should advance whenever a unit appears." It must advance only on a unit that was accepted. Advancing on a discarded unit makes the legitimate replay look like a duplicate and turns a single-bit error into a permanent link stall, with every safety assertion passing (§28).

"CRC logic can update while valid is stalled." It cannot. The accumulation must be qualified by valid && ready, and the bug's signature — errors that correlate with backpressure rather than with any physical variable — is one of the most misleading in the module (§12).

"Error detection and recovery are the same mechanism." Detection decides that trust is lost and withholds the object. Recovery restores trust. Conflating them produces a detector that discards and reports without a defined path back, and a recovery that fires on conditions detection never confirmed (§2, §31).

"A one-cycle error pulse is sufficient." It is invisible to software and to any slow debug interface. Three lifetimes are needed: the pulse for the gate, a sticky bit for status, and a saturating count for rate (§32).

"Only false negatives matter." A checker that rejects good objects triggers retries, consumes replay bandwidth and arbitration, enters the congestion-amplification chain, and presents as a physically noisy link that is physically perfect. Of thirteen debug signatures in §45, ten are logic bugs and most of those are false-positive generators (§38, §45).

"A parity error means the physical link is bad." A parity failure on internal storage means this die's storage is corrupting data — and the link CRC will pass, because the corruption happened before the CRC was computed. Parity high with CRC zero is a local fault (§23, §36).

"Checker latency does not affect buffering." The object must be held until its verdict exists, so detection latency is a storage requirement and a lower bound on delivery latency. A 4-cycle checker on a path accepting a unit every 2 cycles needs 2 extra units of storage (§17).

"Retries belong inside the error detector." Detection's obligations end at not delivering the object and recording the event. What is re-sent, when, and how is 14.3's mechanism, and mixing them produces a detector that cannot be reasoned about independently (§2, §31).

"An error count of zero means the link is healthy." If the counter wrapped, zero is what a storm looks like. Diagnostic counters must saturate, and accounting counters must not (§33).

48. Understanding Check

49. Summary and What Comes Next

Error detection is comparison against redundancy or expectation, and the two catch complementary failures. CRC is blind to a unit that never arrived; a sequence check is blind to corruption inside a unit that arrived in order. A design with only one of them has an entire failure class it cannot see, and in both cases the failure mode is silent delivery of wrong data.

The CRC accumulator's lifetime is the source of three of this chapter's bugs. It is per transport unit: initialised at unit start, advanced only on valid && ready, compared at unit end, re-armed for the next. Advancing during a stall folds a held beat repeatedly and produces mismatches that correlate with backpressure rather than with any physical variable — the most misleading signature in the module.

A checker result is meaningless without the identity of the object it describes. Bundle the verdict with an identity at the point of computation and check it at the point of use. One misalignment produces both a corrupted object delivered and a clean object rejected, on consecutive units, with the visible symptom one unit later than the damage. This lesson generalises past error detection: any result travelling separately from the data it describes must carry an identity.

Detection must gate the effect, not annotate it. The delivery gate is a combinational conjunction of every mandatory check, and an object reaches a semantic consumer only through it. An error signal arriving after the consumer has acted is a log entry — retry re-sends an object, it does not un-commit a write or un-invalidate a line. Buffer-until-validated is the correct default; speculative forwarding needs an architected poison path, a genuinely reversible consumer, an enforced window, and verification at every consumer.

Transport-history state must advance only on an accepted unit. Advancing on a discarded one makes the legitimate replay look like a duplicate and converts a single-bit error into a permanent link stall — with every safety property passing, because nothing was delivered wrongly and nothing was lost. Finding it requires error injection and an active replay path in the same test, and detecting it requires bounded-progress liveness or a watchdog.

Protection must span everything whose corruption changes the object's meaning. Payload and metadata together — a detector that covers the data and not the identity protects the less dangerous half, and a corruption inside local storage passes the link CRC by construction because the CRC is computed after the corruption. Parity high with CRC zero is the signature that says the channel is definitively not the problem.

Diagnostics need three lifetimes and per-detector separation. A one-cycle pulse for the gate, a sticky bit for status, a saturating count for rate — and separate counters per detector, because the ratios between five counters are the diagnosis and a single total destroys them. First-error capture must never be overwritten, including by a later error that looks more severe: causality is the ordering that matters, not severity, and a burst is one event reported many times.

And false positives are bugs of the same rank as missed detections. Of thirteen debug signatures, ten are logic bugs in the detection path and most generate false rejections — each of which triggers a retry, consumes replay bandwidth and arbitration, and presents as a throughput collapse on a physically perfect channel. When a link looks noisy, ask whether the failure pattern matches a physical mechanism or a logical one, because the logical checks are cheaper than an eye diagram and the prior probability favours them.

Detection tells us only that trust has been lost. The next chapter asks how the link restores that trust — without losing semantic state, and without exposing partially recovered traffic.

Browse the full path on the UCIe tutorials index.