Skip to content

UCIe · Module 19

Adapter Design

What the UCIe Adapter must retain so a transport object can be flow-controlled, protected, retried and delivered exactly once — reservation before ownership transfer, staging and replay as separate lifetimes, a ring whose allocation pointer must never pass retirement, CRC that must stay aligned with the object it covers, duplicate suppression that needs a window rather than a last-sequence register, and retirement that waits for resolution rather than for the send.

Chapter 19.2 converted semantic obligations into stable transport objects and handed them across FDI. This chapter owns those objects from the moment they arrive until the far end no longer needs a copy.

1. The One-Sentence Model

The Adapter owns transport reliability, not higher-level semantics. Its whole obligation is that an accepted object is eventually delivered exactly once or explicitly failed — while the number of physical attempts underneath that promise may be zero, one, or many.

2. What This Chapter Owns, and What It Does Not

LayerOwnsChapter
Protocol enginesemantic operations, identity, generation, per-protocol completion19.2
Adapteradmission, staging, integrity, replay history, duplicate suppression, retirementthis chapter
Bufferingthe storage structures themselves, sizing, watermarks, ping-pong19.4
Flow controlthe credit machine in RTL19.5 (planned)
Link top levelthe block partition and the state-ownership table19.1

The boundaries are deliberate and this chapter respects them. Credits appear here only as an admission term and a resource-indexing question (§34–§36); the arithmetic, the return path and the CDC hazards are 19.5's. Buffers appear here only as lifetimes and ownership; depth, watermarks and structure are 19.4's.

Six things exist only here:

Reservation before ownership transfer (§9–§12). An object may be accepted only if every resource needed to retain it is already secured — and §10 is the design that accepts first and discovers the shortage afterwards.

Staging and replay are different buffers with different lifetimes (§13–§16), even when a design merges them into one memory.

The ring's three pointers and the invariant between them (§21–§24). Allocation passing retirement overwrites history a retry still needs.

CRC alignment (§26–§31). A result computed over one object and attached to another fails at the far end on data that is perfectly correct.

Duplicate suppression needs a window (§32–§35). A last-sequence register accepts a duplicate that is two behind.

And retirement waits for resolution (§41–§43) — not for the send, not for the PHY, and not for reaching the far end.

3. Sourcing

4. The Obligation, Stated Precisely

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ACCEPTED transport object
    →  delivered semantically exactly once at the far end
    OR →  explicitly failed and reported
 
while physical attempts may be:  0, 1, 2, ... N
QuantityBound
semantic deliveries per accepted objectexactly ≤ 1, and eventually 1 or an explicit failure
physical attempts per object≥ 0 — an object accepted and then failed may never be sent
replay entries per objectexactly 1 while it is unresolved
staging entries per object1, until replay owns it (§13)

Everything in this chapter is a mechanism for keeping the first row true while the second varies. Reservation makes the promise affordable; the ring makes retry possible; the window makes exactly-once achievable; and retirement decides when the promise is discharged.

5. The Adapter

A UCIe die to die Adapter drawn as nine blocks. On the transmit side, protocol objects arrive from FDI into an admission stage that consults staging space, a replay slot and a credit before accepting. Accepted objects enter a staging buffer, pass through an integrity generation pipeline, and are recorded in a replay and history table that retains a copy for retransmission. A scheduler chooses between new traffic and pending replays and hands the selected object across RDI to the physical layer. Acknowledgement or resolution feedback returns from the far end into the replay table, which is the only thing that retires an entry. On the receive side, beats arrive from the physical layer into an integrity check, then a duplicate and window check, then reassembly, and only then through a semantic delivery gate to the protocol engine. A link and recovery controller sits alongside, able to stop new admission without disturbing objects already owed.From FDIprotocol objectsAdmissionreserve, then accept(§9)Stagingabsorbs backpressureIntegrity genaligned to the object(§26)Replay + historyretains the copy(§21)Schedulernew against replay(§38)RX: check, windowthen reassemble (§32)Delivery gateexactly once (§30)Link controllerstops admission only12
Transmit and receive as two independent paths, with the replay history sitting beside the transmit path rather than in it. Admission reserves staging, a replay slot and a credit before the object is accepted; the receive path checks integrity and the duplicate window before anything is delivered; and the link controller stops admission without touching what is already owed.

Read the replay block's position. It is beside the transmit path, not in series with it — because the object must reach the PHY and be retained, and a design that puts replay in series has one structure doing two jobs with two lifetimes (§13).

6. Adapter State Categories

The chapter's backbone table. Every section below is a row of it.

StateAllocated whenReleased whenLifetimeOwner
Staging entryat admissionwhen replay owns the object (§14)short — pipeline depthadapter_tx
Replay entrywhen integrity and the copy are committedon resolution (§41) — never on senda full round trip, plus retriesreplay
Attempt countwith the replay entrywith the replay entryas the entryreplay
Transmitted attemptat launchat feedback or timeoutone flightphy_if
RX reconstruction stateat the first beat of an objectat completion or abandonmentone objectrx_ingress
Duplicate / history windowcontinuouslyslides forward (§32)a window, not an entryrx_ingress
Creditat advertisementconsumed on transfer; returned on releaselink epochcredit_mgr — 19.5
Configuration epochat commitat the next commitconfiguration epochcfg
Recovery stateat triggerat validated resumeone recoverylink_mgr
First faultat the first errordeliberate clear onlysurvives everything but power-onfault_mgr

Three readings.

Rows 1 and 2 have very different lifetimes — one pipeline depth against one round trip plus retries. §13 is why they are separate concepts even when they share a memory.

Row 6 is a window, not a set of entries. Duplicate suppression is not "remember every object"; it is "remember a bounded range" (§32), and the bound is what makes it implementable.

And row 10's reset domain is power-on. 19.1 §35 established why; the Adapter is where the first fault usually originates.

7. The Transport Object

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. NOT a UCIe format, and no field corresponds to any specified
// field (Section 3). Symbolic widths throughout.
typedef struct packed {
  logic [OBJ_ID_W-1:0]     object_id;    // the ADAPTER's identity for this object
  logic [SEM_ID_W-1:0]     sem_id;       // carried opaquely for 19.2's benefit
  logic [GEN_W-1:0]        sem_gen;      // ditto — the Adapter never interprets it
  logic [CLASS_W-1:0]      tclass;       // resource class for admission (Section 34)
  logic [LEN_W-1:0]        length;
  logic [CFG_EPOCH_W-1:0]  cfg_epoch;    // Section 44
  logic                    has_data;
  logic [DATA_W-1:0]       data;
} adapter_object_t;

Architecture. The Adapter's own identity, plus the protocol engine's identity carried opaquely. That opacity is the layering guarantee: the Adapter routes, protects, retains and delivers the object without ever interpreting sem_id or sem_gen, which is what lets it carry any protocol (19.2 §9).

State. One register per pipeline stage, plus a staging entry and a replay entry.

Cycle behaviour. Formed at admission and held stable while offered (§29). cfg_epoch is captured once (§44).

Contract. The replay path needs enough to retransmit the object byte-identically; the receive side needs enough to reassemble and to decide novelty. Everything else is carried, not used.

Failure. Interpreting sem_id — for instance to order objects by it — which puts protocol knowledge in the Adapter and breaks the moment a protocol's identities are not ordered that way.

DV. Assert sem_id and sem_gen are byte-identical at the far end; assert cfg_epoch immutable while the object is live.

8. Three Identities, One Object

IdentityNamesRecycled whenOwner
Semantic identitythe protocol operationat semantic completion19.2
Object identitythis transport objectwhen the object retiresthe Adapter
Sequence / history identitythis object's place in the reliability streamwhen the window slides past itthe replay and RX window
Physical attemptone flightimmediatelythe PHY

A retransmission reuses the sequence identity and creates no new object and no new semantic operation. 19.1 §7 makes the general statement; here it is a wiring requirement, because the three identities are three different fields that a careless design will index by whichever is nearest.

9. Admission Reserves Before It Accepts

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Every resource needed to RETAIN the object, secured before the
// object is accepted. Section 10 is the design that accepts first.
assign adapter_accept =
      in_valid
   && staging_space_available                    // somewhere to put it now
   && replay_slot_available                      // somewhere to KEEP it (Section 10)
   && credit_available[in_obj.tclass]            // the far end can take it (19.5)
   && link_accepting                             // the link manager permits it
   && cfg_stable;                                // not mid-commit (Section 44)
 
assign in_ready = adapter_accept;

Architecture. Five terms, and the second is the one designs omit. Staging says "I can hold it for a few cycles"; the replay slot says "I can hold a recoverable copy for a full round trip". They are different promises and both must be made before the object is taken.

State. None of its own — a conjunction over five facts owned by five mechanisms.

Cycle behaviour. Evaluated combinationally into in_ready, and all reservations are taken in the same cycle as the acceptance. Reserve-then-accept, never accept-then-hope.

Contract. Accepting an object promises §4's obligation. The Adapter cannot make that promise without the means to keep it, and the replay slot is the means.

Failure. §10.

DV. Force each term false alone and confirm no acceptance — five directed tests, which catch the term that is computed and not used.

10. Wrong RTL — Accept Before the Replay Slot Exists

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the object is accepted on staging space alone, and the replay slot
// is sought later, when the object reaches the integrity stage.
assign adapter_accept = in_valid && staging_space_available && link_accepting;
 
// ... several stages later:
always_ff @(posedge clk)
  if (integrity_done && !replay_slot_available)
    stall_or_drop(obj);            // ← neither option is acceptable

Both branches of that if are failures.

The design choosesConsequence
drop the objectan accepted object vanished (10.1 §10) — §4's obligation broken
stall it in placeit holds staging, blocks everything behind it, and still cannot be retained
send it without retaining a copythe retry contract is broken — an error makes it unrecoverable

Four properties.

The third option is the one that gets written, because it looks like it works: the object goes out, and most objects are not retried. The design fails only when that particular object needs a retransmission — which is rare, load-dependent and catastrophic.

The stall option deadlocks under sustained load. The stalled object waits for a replay slot; replay slots free on resolution; resolution requires objects to be sent; sending requires the staging the stalled object is occupying.

And the drop is silent. No overflow, no underflow, no CRC failure — an object simply is not there any more, and the protocol engine above waits forever with a live semantic entry.

The fix is one term in the admission conjunction (§9), and its cost is that the Adapter refuses work slightly earlier. That is the correct behaviour: refusing is a promise not made, and accepting is a promise broken.

11. SVA — an Accepted Object Is Always Owned

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Every accepted object is held by staging, by replay, or by both.
// tb_object_live() is TESTBENCH knowledge — the design has no signal for it.
property p_object_always_owned(int unsigned rid);
  @(posedge clk) disable iff (!rst_n)
    tb_object_live(rid) |-> (tb_in_staging(rid) || tb_in_replay(rid));
endproperty
a_object_always_owned: assert property (p_object_always_owned(REF_UT));
 
// Acceptance implies a replay slot was reserved.
property p_accept_implies_replay_slot;
  @(posedge clk) disable iff (!rst_n)
    adapter_accept |-> replay_slot_available;
endproperty
a_accept_implies_replay_slot: assert property (p_accept_implies_replay_slot);
 
// An accepted object is never dropped.
property p_no_silent_drop;
  @(posedge clk) disable iff (!rst_n)
    tb_object_accepted(REF_UT) |-> ##[1:$] (tb_object_retired(REF_UT)
                                         || tb_object_failed(REF_UT));
endproperty
a_no_silent_drop: assert property (p_no_silent_drop);
 
// Reservation and acceptance happen together, never one without the other.
property p_reserve_with_accept;
  @(posedge clk) disable iff (!rst_n)
    adapter_accept |=> (staging_alloc_fired && replay_reserved_for(prev_obj_id));
endproperty
a_reserve_with_accept: assert property (p_reserve_with_accept);

Architecture. Four properties: universal ownership, the reservation implication, no drop, and simultaneity.

Why the first must use a verification reference. The design has no signal saying "object 47 exists as an obligation". The testbench generated it and therefore knows — building that into the design would create a third tracker with its own bugs.

Why the third is written as an unbounded eventuality. It is a safety claim about no-drop rather than a liveness claim about promptness. Liveness bounds are §40's, with their assumptions written down; this one says only that the object does not vanish.

DV. The first needs an error injected in the acceptance window (§56).

12. Staging and Replay Are Different Lifetimes

StagingReplay / history
Purposeabsorb pipeline and backpressureretain a recoverable copy
Populated atadmissionwhen the copy is committed (§14)
Freed atreplay ownershipresolution (§41)
Typical residencypipeline deptha full round trip, plus retries
If it overflowsbackpressure — a performance eventa retry contract violation
May be the same memory?yes, and the lifetimes still differyes

A design may implement both in one RAM. It must not implement both with one lifetime. §15 is the design that frees one memory's entry when the PHY consumes it, and thereby frees both.

13. The Ownership Handoff

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Staging releases only when replay CONFIRMS it holds a durable
// copy. Both own it in between, deliberately (19.1 Section 16).
logic replay_commit;      // the entry is written and its metadata is valid
logic staging_release;
 
assign replay_commit   = replay_alloc_fire && replay_entry_written;
assign staging_release = replay_commit;              // NOT phy_tx_fire
assign staging_pop_fire = staging_release;
CycleStaging holds itReplay holds itSafe?
n✓ — staging can re-offer
n+1✓ — the deliberate window
n+2✓ — replay can retransmit
(the bug)✗ — nothing can recover it

Architecture. The release is gated on replay's confirmation, not on the integrity stage completing and not on the PHY accepting.

Contract. Staging guarantees it will not release until replay confirms; replay guarantees it will not confirm until the entry is durable. Neither guarantee is visible at the other's interface, which is why §11's first property asserts the conjunction.

Failure. §15, and the one-cycle unowned window if the pop is gated on the wrong signal (19.1 §17).

DV. Cover the window occurring; inject an error inside it.

14. Wrong Architecture — One Slot Freed When the PHY Consumes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — one memory, one lifetime, freed when the PHY takes the object.
always_ff @(posedge clk)
  if (phy_tx_fire)
    tx_mem_valid_q[tx_rd_ptr] <= 1'b0;     // ← the ONLY copy, gone at send
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The object is written into the shared TX memory at admission.
2. The PHY consumes it. The entry is freed.
3. The far end reports an integrity failure — or reports nothing at all,
   and a timeout fires.
4. A retransmission is required.
5. -> there is no copy. The object cannot be retransmitted.
6. The Adapter must fail an object it promised to deliver, for a fault
   that was recoverable.

Four properties.

It works for every object that is not retried, which under a healthy link is nearly all of them. The design passes every clean-link test.

The failure rate equals the retry rate, so it scales with exactly the condition the replay buffer exists for. A marginal link turns an occasional recoverable event into an occasional lost object.

And it converts a recoverable error into an unrecoverable one, which is the specific harm: the link had a mechanism for this and the RTL threw the mechanism's input away.

The fix is not necessarily two memories. One memory can serve both, provided the entry is freed on resolution rather than on send — the lifetime is the thing that must be separate, not the storage.

15. The Transmit Object State Machine

An illustrative seven-state Adapter transmit object controller. From FREE, admission moves to STAGED, where the object is held in the staging buffer but no recoverable copy exists yet. From STAGED, committing the history entry moves to HISTORY OWNED, at which point a durable copy exists. From HISTORY OWNED, launching a physical attempt moves to SENT WAIT ACK. From SENT WAIT ACK, a retry indication or a timeout moves to REPLAY PENDING, and from REPLAY PENDING launching another attempt returns to SENT WAIT ACK, which is the retry loop. From SENT WAIT ACK, resolution moves to RETIRED. From any working state a fatal condition moves to FAILED. Both RETIRED and FAILED return to FREE once the slot is reclaimed. Note that no semantic protocol state appears anywhere in this machine.FREESTAGEDHISTORYOWNEDSENTWAIT ACKREPLAYPENDINGRETIREDFAILEDadmittedadmittedhistory committedhistory committedattempt launchedattempt launchedretry indicationretry indicationattempt relaunchedattempt relaunchedresolvedresolvedfatalfatalbudget spentbudget spentslot reclaimedslotreclaimedslot reclaimedslotreclaimed
Illustrative Adapter transmit-object control states, not UCIe normative state naming. The object is staged, then owned by history, then sent and awaiting resolution; a retry indication returns it to a pending state and it is sent again; and only resolution retires it.

Read the loop between SENT WAIT ACK and REPLAY PENDING. That is the retry, and it does not pass through STAGED or FREE — a retransmission is the same object, so it never re-enters the states where a new object is created (19.2 §43).

And notice what is absent. There is no semantic state here at all. The protocol engine's operation is meanwhile sitting in its own machine (19.2 §24), untouched by any of these transitions.

16. The Transmit Next-State Function

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. ONE next-state owner. Every simultaneous event resolved by an
// explicit arm order — Section 17 is the alternative.
typedef enum logic [2:0] {
  TX_FREE          = 3'd0,
  TX_STAGED        = 3'd1,
  TX_HISTORY_OWNED = 3'd2,
  TX_SENT_WAIT_ACK = 3'd3,
  TX_REPLAY_PENDING= 3'd4,
  TX_RETIRED       = 3'd5,
  TX_FAILED        = 3'd6
} tx_state_e;
 
tx_state_e tx_state_q [MAX_TX_OBJ];
tx_state_e nxt;
 
always_comb begin
  nxt = tx_state_q[i];
  unique case (tx_state_q[i])
    TX_FREE:           if (admit_fire[i])                     nxt = TX_STAGED;
 
    TX_STAGED:         if (fatal[i])                          nxt = TX_FAILED;
                       else if (history_commit[i])            nxt = TX_HISTORY_OWNED;
 
    TX_HISTORY_OWNED:  if (fatal[i])                          nxt = TX_FAILED;
                       else if (attempt_launch[i])            nxt = TX_SENT_WAIT_ACK;
 
    // Priority here is the section's point: fatal, then retry, then resolve.
    TX_SENT_WAIT_ACK:  if (fatal[i])                          nxt = TX_FAILED;
                       else if (retry_indication[i])          nxt = TX_REPLAY_PENDING;
                       else if (resolved[i])                  nxt = TX_RETIRED;
 
    TX_REPLAY_PENDING: if (fatal[i])                          nxt = TX_FAILED;
                       else if (attempts_q[i] >= ATTEMPT_BUDGET)
                                                              nxt = TX_FAILED;
                       else if (attempt_launch[i])            nxt = TX_SENT_WAIT_ACK;
 
    TX_RETIRED:        if (slot_reclaim[i])                   nxt = TX_FREE;
    TX_FAILED:         if (slot_reclaim[i])                   nxt = TX_FREE;
    default:                                                  nxt = TX_FAILED;
  endcase
end
 
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) tx_state_q[i] <= TX_FREE;
  else        tx_state_q[i] <= nxt;

Architecture. One owner, one unique case, and an explicit priority in TX_SENT_WAIT_ACK that is a real design decision: a retry indication beats a resolution in the same cycle.

Why that order and not the reverse. If both arrive together the safe direction is to retry an object that may already have been resolved — which produces a duplicate the far end's window suppresses (§32) — rather than to retire an object that needs retransmitting, which loses it. Choosing the recoverable failure over the unrecoverable one is the principle, and it is only reviewable because it is written in one place.

State. Three bits per object plus an attempt counter.

Contract. The scheduler reads TX_REPLAY_PENDING and TX_HISTORY_OWNED; the ring reads TX_RETIRED; the fault manager reads TX_FAILED. Three consumers, three states.

Failure. §17. Also omitting the attempt budget, which loops on a persistently failing object forever (14.3 §37).

DV. §18; cover simultaneous retry-and-resolve, and exhaust the budget.

17. Wrong TX State — Two Writers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the acknowledgement path and the retry path each write the state.
// In the ack handler:
always_ff @(posedge clk) if (resolved[i])          tx_state_q[i] <= TX_RETIRED;
// In the retry handler, a different module:
always_ff @(posedge clk) if (retry_indication[i])  tx_state_q[i] <= TX_REPLAY_PENDING;

On a cycle where both fire, the tool's ordering decides — and both outcomes are wrong in different ways.

Which write winsConsequence
TX_RETIRED winsan object needing retransmission is retired — it is lost, and its slot is reused
TX_REPLAY_PENDING winsan already-resolved object is retransmitted — a duplicate, suppressed at the far end (§32)

Four properties.

One outcome is recoverable and the other is not, which is exactly the choice §16 makes deliberately. Here the choice is made by the tool.

Simulation and synthesis may disagree, and with the writes in different modules and different gating conditions synthesis may not report a conflict.

The failure is rare. A retry indication and a resolution for the same object in the same cycle requires specific timing — so it appears in the field and not in regression.

And the retired-wrongly case is silent. The object is gone, the slot is reused, and the protocol engine above waits forever with a live semantic entry (19.2 §16).

18. SVA — Transmit State Discipline

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Only legal transitions, and the state comes from one function.
property p_legal_tx_transitions;
  @(posedge clk) disable iff (!rst_n)
    $changed(tx_state_q[IDX])
      |-> is_legal_tx_transition($past(tx_state_q[IDX]), tx_state_q[IDX]);
endproperty
a_legal_tx_transitions: assert property (p_legal_tx_transitions);
 
property p_tx_state_single_source;
  @(posedge clk) disable iff (!rst_n)
    $changed(tx_state_q[IDX]) |-> (tx_state_q[IDX] == $past(nxt_for(IDX)));
endproperty
a_tx_state_single_source: assert property (p_tx_state_single_source);
 
// A retry indication and a resolution together must NOT retire (Section 16).
property p_retry_beats_resolve;
  @(posedge clk) disable iff (!rst_n)
    ((tx_state_q[IDX] == TX_SENT_WAIT_ACK) && retry_indication[IDX] && resolved[IDX])
      |=> (tx_state_q[IDX] == TX_REPLAY_PENDING);
endproperty
a_retry_beats_resolve: assert property (p_retry_beats_resolve);
 
// An object in a working state always has a history entry.
property p_working_implies_history;
  @(posedge clk) disable iff (!rst_n)
    ((tx_state_q[IDX] == TX_SENT_WAIT_ACK) || (tx_state_q[IDX] == TX_REPLAY_PENDING))
      |-> replay_entry_valid_for(IDX);
endproperty
a_working_implies_history: assert property (p_working_implies_history);

Architecture. Four properties: legality, single source, the priority decision, and the history invariant.

Why the third is worth asserting explicitly. It encodes a design decision rather than a structural fact. If a later change reverses the arm order, this property fails and someone has to justify the change — which is exactly what a design-decision assertion is for.

DV. Cover the simultaneous case; run the legal-transition function formally where possible.

19. The Replay Entry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. NOT a UCIe sequence format, and no width is claimed (Section 3).
typedef struct packed {
  logic                    valid;
  logic [OBJ_ID_W-1:0]     object_id;
  logic [SEQ_W-1:0]        seq;          // reliability identity (Section 8)
  logic [LEN_W-1:0]        length;
  logic [CFG_EPOCH_W-1:0]  cfg_epoch;    // Section 44
  logic [ATTEMPT_W-1:0]    attempts;     // evidence, and the budget (Section 16)
  logic [DATA_W-1:0]       data;         // the retained copy
} replay_entry_t;
 
// Payload storage — inferred RAM, deliberately NOT reset (19.1 Section 12).
replay_entry_t replay_mem [REPLAY_DEPTH];

Architecture. One entry per unresolved object. attempts is both a control input — it feeds the budget — and evidence, which is why it survives a recovery (§6).

State. REPLAY_DEPTH entries. The depth is a reliability window, not a throughput parameter — 19.4 derives it, and §22 states the invariant it must satisfy.

Cycle behaviour. Written once at history commit; data is never modified afterwards, because a retransmission must be byte-identical to the original.

Contract. The scheduler retransmits from this copy. If the copy can differ from what was sent, the far end's duplicate detection may not recognise it — and a "duplicate" that differs is a second object.

Failure. Modifying data in place — for instance to update an epoch field — which makes the retransmission a different object.

DV. Assert the retained copy is stable while the entry is valid; assert a retransmission is byte-identical to the original attempt.

20. The Ring and Its Three Pointers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE circular replay buffer with three pointers. Wrap-safe compares
// via an extra bit — Section 23 is the version that uses equality alone.
localparam int PTR_W = $clog2(REPLAY_DEPTH);
 
logic [PTR_W:0] alloc_ptr_q;    // next entry to allocate      (extra MSB = wrap)
logic [PTR_W:0] send_ptr_q;     // next entry to transmit
logic [PTR_W:0] retire_ptr_q;   // oldest unresolved entry
 
// Index into the RAM with the low bits; compare with all bits.
wire [PTR_W-1:0] alloc_idx  = alloc_ptr_q[PTR_W-1:0];
wire [PTR_W-1:0] send_idx   = send_ptr_q[PTR_W-1:0];
wire [PTR_W-1:0] retire_idx = retire_ptr_q[PTR_W-1:0];
 
// Occupancy is a subtraction on the wide pointers — never an equality test.
wire [PTR_W:0] outstanding = alloc_ptr_q - retire_ptr_q;
wire           replay_full  = (outstanding == REPLAY_DEPTH[PTR_W:0]);
wire           replay_empty = (outstanding == '0);
wire           replay_slot_available = !replay_full;

Architecture. Three pointers because there are three distinct positions: what has been allocated, what has been sent, and what is still unresolved. A two-pointer ring cannot express "sent but unresolved", which is exactly the state a retry operates on.

State. Three pointers, each one bit wider than the index. The extra bit is what distinguishes full from empty (§23).

Cycle behaviour. alloc_ptr_q advances at history commit; send_ptr_q at an attempt launch; retire_ptr_q at resolution. Each advances on an actual event, never on a decision that did not fire.

Contract. replay_slot_available gates admission (§9). The whole retention promise rests on outstanding being exactly right.

Failure. §21 and §23.

DV. §24; cover wrap on each pointer, and cover full and empty.

21. Wrong Pointer Architecture — Allocation Passes Retirement

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — allocation is gated on a check that does not account for wrap.
wire replay_full_wrong = (alloc_ptr_q[PTR_W-1:0] == retire_ptr_q[PTR_W-1:0]);
assign replay_slot_available = !replay_full_wrong;    // ← also true when EMPTY

When the buffer is empty the indices are also equal, so this reads "full" as "empty" and vice versa — and in the direction that matters, it permits allocation when the ring is full.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The ring is full: REPLAY_DEPTH unresolved entries.
2. alloc_idx == retire_idx, so replay_full_wrong is TRUE... but the design
   may be using the opposite polarity, or checking only in one direction.
3. Allocation proceeds and overwrites the OLDEST UNRESOLVED entry.
4. That entry's object may still need retransmission.
5. A retry is later requested for it.
6. -> the ring returns the NEW object's data under the OLD sequence identity.

Four properties, and this is the chapter's most catastrophic failure.

The far end receives a well-formed object under a sequence identity it was expecting. It passes integrity, it passes the duplicate check, and it is entirely the wrong data. No mechanism anywhere detects it.

Both objects are corrupted. The overwritten one is lost; the new one is delivered under the wrong identity and may itself be delivered again later. One overwrite, two failures.

It requires the ring to be genuinely full, which happens only under sustained load with a slow acknowledgement path — so it is a soak-test failure, not a regression failure.

And the correct form is one extra bit. Wide pointers with a subtraction give an unambiguous occupancy, and §24's assertion turns the remaining risk into a reported error.

22. The Ring Invariant

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
retire_ptr  <=  send_ptr  <=  alloc_ptr                (in wrap-extended order)
 
alloc_ptr - retire_ptr  <=  REPLAY_DEPTH               <- Section 21
send_ptr  - retire_ptr  <=  alloc_ptr - retire_ptr     <- cannot send unallocated
RelationshipMeaningViolated by
retire ≤ sendnothing is retired before it is senta resolution for an unsent object
send ≤ allocnothing is sent before it is allocateda scheduler reading past the allocation point
alloc − retire ≤ DEPTHallocation never passes retirement§21

All three are checkable in one assertion each, and all three are cheap. The third is the one that prevents silent data substitution.

23. SVA — the Ring Is Safe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Occupancy never exceeds the depth (Section 21).
property p_replay_never_overfull;
  @(posedge clk) disable iff (!rst_n)
    ((alloc_ptr_q - retire_ptr_q) <= REPLAY_DEPTH);
endproperty
a_replay_never_overfull: assert property (p_replay_never_overfull);
 
// Allocation requires a free slot.
property p_alloc_requires_slot;
  @(posedge clk) disable iff (!rst_n)
    replay_alloc_fire |-> !replay_full;
endproperty
a_alloc_requires_slot: assert property (p_alloc_requires_slot);
 
// A live entry is never overwritten.
property p_no_overwrite_live_entry;
  @(posedge clk) disable iff (!rst_n)
    replay_alloc_fire |-> !replay_mem[alloc_idx].valid;
endproperty
a_no_overwrite_live_entry: assert property (p_no_overwrite_live_entry);
 
// Pointer ordering holds (Section 22).
property p_pointer_ordering;
  @(posedge clk) disable iff (!rst_n)
    (((send_ptr_q - retire_ptr_q) <= (alloc_ptr_q - retire_ptr_q))
     && ((alloc_ptr_q - retire_ptr_q) <= REPLAY_DEPTH));
endproperty
a_pointer_ordering: assert property (p_pointer_ordering);
 
// The retained copy is stable while the entry is valid (Section 19).
property p_replay_data_stable;
  @(posedge clk) disable iff (!rst_n)
    replay_mem[IDX].valid |-> $stable(replay_mem[IDX].data);
endproperty
a_replay_data_stable: assert property (p_replay_data_stable);

Architecture. Five properties covering occupancy, allocation, overwrite, ordering and content stability.

Why the third is not implied by the first. Occupancy can be correct while the allocation index points at a valid entry — through a pointer that advanced without its counterpart. The third checks the memory, not the arithmetic, and it is the one that fires on §21's overwrite at the exact cycle.

DV. All five always-on. Drive the ring to full under sustained load with delayed resolutions (§56) — the only condition that reaches §21.

24. Simultaneous Allocate and Retire

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. At high load these coincide constantly. Pointers are
// independent, so each advances on its own event; occupancy is DERIVED and
// therefore never drifts.
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) begin
    alloc_ptr_q  <= '0;
    send_ptr_q   <= '0;
    retire_ptr_q <= '0;
  end else begin
    if (replay_alloc_fire)  alloc_ptr_q  <= alloc_ptr_q  + 1'b1;
    if (attempt_launch_any) send_ptr_q   <= send_ptr_q   + 1'b1;
    if (replay_retire_fire) retire_ptr_q <= retire_ptr_q + 1'b1;
  end

Architecture. Three independent pointers and no separate occupancy register at all. outstanding is a subtraction (§20), so a simultaneous allocate and retire needs no special case — both pointers advance and the difference is unchanged automatically.

This is why the wide-pointer form is preferable to a separate count. A count register requires the four-case unique case that every other counter in this curriculum needs; a derived difference cannot drift because there is nothing to drift.

Failure. Maintaining a redundant occupancy register alongside the pointers and updating it independently — which reintroduces the drift the derivation avoided, and now there are two answers to one question.

DV. Cover simultaneous allocate-and-retire; assert the derived occupancy matches a testbench count.

25. The Integrity Stage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE integrity pipeline. The function is abstract on purpose.
logic [CRC_W-1:0] crc_partial_q;
logic             crc_active_q;
 
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) begin
    crc_partial_q <= CRC_INIT;
    crc_active_q  <= 1'b0;
  end else if (crc_start) begin
    crc_partial_q <= crc_next(CRC_INIT, chunk_in);      // abstract (Section 25)
    crc_active_q  <= 1'b1;
  end else if (crc_feed && crc_active_q) begin
    crc_partial_q <= crc_next(crc_partial_q, chunk_in);
  end else if (crc_final && crc_active_q) begin
    crc_active_q  <= 1'b0;
  end
 
assign crc_result = crc_finalise(crc_partial_q);

Architecture. A chunk-fed accumulator with an explicit active flag. crc_active_q is what prevents a chunk from a different object folding into a partial result — the flag is the object boundary, and without it a stall that interleaves two objects corrupts both results.

State. One partial register and one flag. Per object in flight, if the pipeline supports more than one.

Cycle behaviour. Fed on crc_feed, which must be qualified by the same enable that advances the data (§26). A chunk consumed by the CRC and not by the datapath — or vice versa — is a divergence.

Contract. The far end recomputes over what it received. The result must be over exactly the bytes that were sent, in exactly that order.

Failure. §27 and §29.

DV. §28's alignment property; assert crc_active_q bounds exactly one object.

26. The Integrity Input Bundle

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Everything the result covers travels as ONE object, so a stall
// cannot advance one part without the other.
typedef struct packed {
  logic [META_W-1:0] meta;      // whatever the architecture requires covered
  logic [DATA_W-1:0] data;
  logic              last;
  logic              valid;
} crc_input_t;
 
crc_input_t crc_in_q;

Architecture. One packed bundle. meta is inside it — not beside it — because §27 is the failure of metadata changing while data stalls, and a shared register makes that structurally impossible.

Contract. The result is a function of this bundle. If any covered field can change between the computation and the transmission, the result no longer describes what was sent (§27).

DV. §28's stability property.

27. Wrong CRC Pipeline — Metadata Changes While Data Stalls

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — metadata is taken from a live signal while the data path stalls.
assign crc_input = {live_meta, staged_data};       // ← two different lifetimes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The CRC is computed over {meta = A, data = D}.
2. The data path stalls; the object waits.
3. live_meta changes to B — a new configuration, a new class, a re-derived field.
4. The object is finally transmitted as {meta = B, data = D}.
5. The far end recomputes over {B, D} and gets a different result.
6. -> integrity FAILS on data that is completely correct.

Four properties.

The data is perfect. Every byte of D is exactly what the protocol engine handed over. The failure is entirely an accounting mismatch between what was covered and what was sent.

The far end does the right thing and rejects it, which triggers a retransmission — and the retransmission has the same problem, so the object can fail repeatedly and eventually exhaust the attempt budget.

The symptom is "intermittent CRC failures on a healthy link", which sends everyone to look at the physical layer. The physical layer is fine.

And it only occurs when the data path stalls, which is load-dependent. A design that never backpressures during test never sees it.

28. SVA — Integrity Alignment

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The covered bundle does not change while the object waits.
property p_crc_input_stable_under_stall;
  @(posedge clk) disable iff (!rst_n)
    (crc_in_q.valid && !crc_consume) |=> $stable(crc_in_q);
endproperty
a_crc_input_stable_under_stall:
  assert property (p_crc_input_stable_under_stall);
 
// The result accompanies the object it was computed over.
property p_crc_accompanies_its_object(int unsigned rid);
  @(posedge clk) disable iff (!rst_n)
    (phy_tx_fire && (tb_ref_id == rid))
      |-> (tx_crc == tb_expected_crc(rid));
endproperty
a_crc_accompanies_its_object:
  assert property (p_crc_accompanies_its_object(REF_UT));
 
// The accumulator is bounded to exactly one object.
property p_crc_active_one_object;
  @(posedge clk) disable iff (!rst_n)
    crc_start |-> !crc_active_q;
endproperty
a_crc_active_one_object: assert property (p_crc_active_one_object);
 
// The result's valid is aligned with the object's (Section 29).
property p_crc_valid_aligned;
  @(posedge clk) disable iff (!rst_n)
    tx_obj_valid |-> tx_crc_valid;
endproperty
a_crc_valid_aligned: assert property (p_crc_valid_aligned);

Architecture. Four properties: stability, end-to-end correctness, accumulator scoping, and valid alignment.

Why the second uses a testbench reference. The expected result is a function of the bytes the testbench generated. Computing it in the design would use the same pipeline that produced the bug.

DV. §27 needs a stall injected between computation and transmission with a live metadata source; §29 needs two back-to-back objects.

29. Wrong CRC Valid Alignment

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the result path is one stage longer than the object path.
always_ff @(posedge clk) begin
  obj_q1 <= obj_in;   obj_q2 <= obj_q1;                       // 2 stages
  crc_q1 <= crc_in;   crc_q2 <= crc_q1;   crc_q3 <= crc_q2;   // 3 stages
end
assign tx_obj = obj_q2;
assign tx_crc = crc_q3;          // ← object N is sent with the result for N-1
CycleObject sentResult attachedFar end
nO0C(O0)✓ by luck — the first
n+1O1C(O0)✗ rejected
n+2O2C(O1)✗ rejected

Four properties.

It fails only once the pipeline is full. With a single object in flight there is no previous result to attach, so the first object of any burst is correct — and a single-object smoke test passes completely.

Then every subsequent object fails, so the failure rate is near 100% under continuous traffic and 0% under isolated traffic. That discontinuity is the diagnostic signature.

The far end rejects correct data, triggering retransmission — and the retransmission is also misaligned, so objects fail repeatedly and the link appears to be failing catastrophically.

And it is a classic. The fix is to build the result into the object's bundle (§26) so the two cannot have different depths, which is the same structural argument as 19.1 §25 and 18.2 §41.

30. The Receive Delivery Gate

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Five terms, evaluated COMBINATIONALLY into the delivery.
// Section 31 is the version that delivers first.
assign rx_semantic_deliver =
      rx_object_complete       // every part of the object arrived (Section 39)
   && rx_integrity_ok          // it passed (Section 25)
   && !rx_duplicate            // the window says it is new (Section 32)
   && rx_in_window             // and it is within the acceptable range
   && rx_downstream_space;     // the protocol engine can take it

Architecture. Five named terms between reconstruction and the protocol engine. Naming them separately makes a non-delivery attributable to one of five causes rather than "the RX path dropped it".

Cycle behaviour. Combinational into the delivery. A registered gate delivers one object before deciding not to — and the protocol engine may already have allocated against it.

Contract. The protocol engine assumes exactly-once semantic delivery (19.2 §4). That assumption has no signal at the interface.

Failure. §31.

DV. Force each term false alone — five directed tests.

31. Wrong RX — Delivering Before the Verdict

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the object is delivered as it is reconstructed, and checked after.
always_ff @(posedge clk)
  if (rx_object_complete) begin
    deliver_to_protocol(rx_obj);        // ← delivered
    start_integrity_check(rx_obj);      // ← checked
    update_duplicate_window(rx_obj);    // ← and novelty decided
  end
The object isCorrect outcomeWith this design
valid and newdelivered oncedelivered once ✓
corruptdiscarded; retransmission requesteddelivered, then flagged
a duplicatesuppressed; the reply still sentdelivered a second time
outside the windowrejecteddelivered

Three of four combinations are handled wrongly, and in every one the transport behaved exactly as specified.

Two consequences worth separating.

The corrupt case is a correctness failure at the protocol engine, which may allocate, complete or fail an operation based on garbage. "Undoing" it is not available — the engine has no mechanism to un-complete an operation.

And the duplicate case creates a second semantic delivery, which is the one thing §4's obligation forbids. The engine's own guards — generation checks, part bitmaps (19.2 §32) — may catch it, but relying on that is relying on the layer above to fix the layer below's bug.

32. Duplicate Suppression Needs a Window

A retried physical attempt may carry an object the far end already accepted. The receiver must distinguish three cases:

The arriving object isResponse
new and in windowaccept, deliver once, record it
a duplicate of something in the windowsuppress the delivery, still acknowledge (14.3 §22)
outside the window entirelyreject and report — it is stale or corrupt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE receive window. A BITMAP over a bounded sequence range, not a
// single last-sequence register — Section 33 is that version.
logic [WINDOW-1:0] received_q;        // one bit per sequence position
logic [SEQ_W:0]    window_base_q;     // wrap-extended, like the ring (Section 20)
 
wire [SEQ_W:0] offset = rx_seq - window_base_q;
wire           in_window = (offset < WINDOW[SEQ_W:0]);
wire           is_duplicate = in_window && received_q[offset[$clog2(WINDOW)-1:0]];
 
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) begin
    received_q     <= '0;
    window_base_q  <= '0;
  end else begin
    // Record, guarded so a duplicate changes nothing.
    if (rx_accept_fire && in_window && !is_duplicate)
      received_q[offset[$clog2(WINDOW)-1:0]] <= 1'b1;
 
    // Slide the base past a contiguous run of received positions.
    if (received_q[0]) begin
      received_q    <= {1'b0, received_q[WINDOW-1:1]};
      window_base_q <= window_base_q + 1'b1;
    end
  end

Architecture. A sliding window with one bit per position. The window is what makes duplicate suppression implementable — remembering every object ever received is unbounded; remembering a bounded range is a shift register.

State. WINDOW bits plus a wrap-extended base. The base is wide for the same reason the ring pointers are (§20): comparison must survive wrap.

Cycle behaviour. A position is recorded under a guard, so a duplicate finds its bit already set and changes nothing — idempotent by construction. The base slides past contiguous received positions, so out-of-order arrivals do not stall it forever.

Contract. The window's size must cover the maximum number of objects that can be outstanding and reorderable. A window smaller than the sender's replay depth cannot suppress every duplicate the sender can produce — which is a sizing relationship 19.4 makes precise.

Failure. §33. Also sliding the base on any received position rather than on a contiguous run, which discards positions that were never received and lets a genuinely missing object be forgotten.

DV. §34; cover duplicates at the base, in the middle and at the far edge of the window.

33. Wrong Duplicate Suppression — a Last-Sequence Register

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — only the most recent accepted sequence is remembered.
always_ff @(posedge clk)
  if (rx_accept_fire) last_seq_q <= rx_seq;
 
assign is_duplicate = (rx_seq == last_seq_q);       // ← only ONE back

Illustrative, with three objects in flight and a retransmission of the oldest:

Arrivallast_seq_q beforeDetected as duplicate?Correct?
seq 109no✓ new
seq 1110no✓ new
seq 10 replayed11no✗ delivered twice

Four properties.

It works for exactly one pattern: an immediate retransmission with nothing in between. Any other object arriving between the original and the duplicate defeats it entirely.

And that pattern is the least likely one. A retransmission follows a round trip plus a retry decision, during which the sender has almost certainly sent other objects. The register is correct in the case that essentially never happens.

The result is a second semantic delivery — §4's obligation broken, and the protocol engine above must then catch it with its own guards or complete an operation twice.

And the fix is a window whose size is a real design parameter, related to the sender's replay depth and the reordering the path permits. A single register is a window of size one, which is a sizing decision nobody made deliberately.

34. SVA — Exactly-Once Delivery

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The property the entire receive architecture exists for.
int unsigned tb_deliveries [int];      // reference object id -> semantic deliveries
 
always @(posedge clk) if (rx_semantic_deliver)
  tb_deliveries[tb_ref_id_of(rx_obj)]++;
 
property p_delivery_at_most_once(int unsigned rid);
  @(posedge clk) disable iff (!rst_n)
    (tb_deliveries[rid] <= 1);
endproperty
a_delivery_at_most_once: assert property (p_delivery_at_most_once(REF_UT));
 
// A duplicate delivers nothing.
property p_duplicate_delivers_nothing;
  @(posedge clk) disable iff (!rst_n)
    (rx_object_complete && rx_duplicate) |-> !rx_semantic_deliver;
endproperty
a_duplicate_delivers_nothing: assert property (p_duplicate_delivers_nothing);
 
// A duplicate is still acknowledged (14.3 Section 22).
property p_duplicate_still_acknowledged;
  @(posedge clk) disable iff (!rst_n)
    (rx_object_complete && rx_duplicate) |-> ##[1:ACK_BOUND] rx_ack_sent;
endproperty
a_duplicate_still_acknowledged:
  assert property (p_duplicate_still_acknowledged);
 
// A failed integrity check delivers nothing.
property p_corrupt_delivers_nothing;
  @(posedge clk) disable iff (!rst_n)
    (rx_object_complete && !rx_integrity_ok) |-> !rx_semantic_deliver;
endproperty
a_corrupt_delivers_nothing: assert property (p_corrupt_delivers_nothing);
 
// The window record is idempotent.
property p_window_record_idempotent;
  @(posedge clk) disable iff (!rst_n)
    (rx_accept_fire && is_duplicate) |=> $stable(received_q);
endproperty
a_window_record_idempotent: assert property (p_window_record_idempotent);

Architecture. Five properties: exactly-once, the two gate conditions, the acknowledgement obligation, and window idempotence.

Why the third is not optional. Suppressing the effect and the reply leaves the sender retransmitting forever — a livelock built from two individually correct behaviours (14.3 §22). A design that gets suppression right and acknowledgement wrong has replaced a duplicate with a hang.

DV. All five; the first needs a retry that actually occurs while traffic is live.

35. Reassembly

If an object spans several beats or fragments in a given architecture, the receiver must track completeness — and duplicates make a counter insufficient, for the seventh time in this curriculum.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Position-indexed storage, and a BITMAP of what has arrived.
logic [MAX_FRAGS-1:0] frag_received_q [MAX_RX_INFLIGHT];
logic [DATA_W-1:0]    reasm_mem       [MAX_RX_INFLIGHT][MAX_FRAGS];
 
always_ff @(posedge clk)
  if (frag_valid && rx_obj_q[frag.obj_slot].valid) begin
    reasm_mem[frag.obj_slot][frag.frag_idx] <= frag.data;   // by INDEX
    if (!frag_received_q[frag.obj_slot][frag.frag_idx])     // GUARDED
      frag_received_q[frag.obj_slot][frag.frag_idx] <= 1'b1;
  end
 
assign rx_object_complete[s] =
    (frag_received_q[s] & frag_mask(rx_obj_q[s].frag_count))
    == frag_mask(rx_obj_q[s].frag_count);

Architecture. Storage indexed by fragment index and a bitmap of arrivals. A duplicate writes the same data to the same slot and sets an already-set bit — harmless by construction.

Contract. Nothing downstream may consume the object until complete. Masking is against this object's fragment count, not against MAX_FRAGS — otherwise an object using fewer fragments is permanently incomplete (17.4 §52).

Failure. §36.

DV. Cover in-order, reversed, duplicated and missing fragments.

36. Wrong Fragment Counter

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a count of received fragments.
always_ff @(posedge clk)
  if (frag_valid) frag_count_q[slot] <= frag_count_q[slot] + 1'b1;
 
assign rx_object_complete[slot] = (frag_count_q[slot] == expected_frags[slot]);
ArrivalCounterReality
fragment 111 present
fragment 221, 2 present
fragment 2 replayed31, 2 present — 3 MISSING
complete✗ delivered with fragment 3 absent

Three properties.

The delivered object contains stale or undefined data at fragment 3's position, and the protocol engine above has no way to know.

The duplicate is a correct transport retry, so no other component is wrong.

And fragment 3 arrives later at a slot that has been freed — producing a spurious out-of-window rejection, which is a symptom at the wrong time describing the wrong thing.

37. SVA — Reassembly Completeness

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Completion requires the full unique set for THIS object.
property p_reassembly_complete_unique;
  @(posedge clk) disable iff (!rst_n)
    rx_deliver_fire |-> ((frag_received_q[dslot] & frag_mask(rx_obj_q[dslot].frag_count))
                         == frag_mask(rx_obj_q[dslot].frag_count));
endproperty
a_reassembly_complete_unique:
  assert property (p_reassembly_complete_unique);
 
property p_duplicate_fragment_idempotent;
  @(posedge clk) disable iff (!rst_n)
    (frag_valid && frag_received_q[frag.obj_slot][frag.frag_idx])
      |=> $stable(frag_received_q[frag.obj_slot]);
endproperty
a_duplicate_fragment_idempotent:
  assert property (p_duplicate_fragment_idempotent);
 
property p_stale_fragment_ignored;
  @(posedge clk) disable iff (!rst_n)
    (frag_valid && !rx_obj_q[frag.obj_slot].valid) |=> $stable(frag_received_q);
endproperty
a_stale_fragment_ignored: assert property (p_stale_fragment_ignored);

Architecture. Three properties: completeness against the object's own count, idempotence, and stale rejection.

DV. All three; the second and third must be injected.

38. Flow Control at the Adapter

19.5 owns the credit machine. What the Adapter needs from it here is one question, asked at admission:

May I allocate and transmit this object without exceeding what the far end has advertised for this object's resource class?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Per class, because the classes have independent downstream
// resources. The arithmetic and the return path are 19.5's.
logic [CREDIT_W-1:0] credit_q [NUM_CLASSES];
assign credit_available[c] = (credit_q[c] != '0);

The Adapter's obligation is to index correctly and to consume on the right event — and §39 is the design that does neither.

39. Wrong RTL — a Global Credit Scalar

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — one counter for every class.
logic [CREDIT_W-1:0] credit_q;
assign credit_available = (credit_q != '0);

A scalar is wrong in both directions, and the two failures look nothing alike.

SituationWith per-class creditsWith a scalar
class A exhausted, class B has roomB proceedseverything stalls — throughput lost
class A has room, class B exhaustedA proceedsan object for B is admitted — the far end's B resource overflows

Three properties.

The first row is a performance failure and the second is a correctness failure, from the same line.

The second is the dangerous one. The far end's class-B structure receives an object it has no room for — and flow control existed precisely to prevent that. What happens next depends on the far end: it drops, it overwrites, or it blocks. None of those is recoverable by the sender, which believed it had permission.

And the diagnostic signature of the first row is misleading. Aggregate credit is non-zero right up until it is zero, so the stall looks like a sudden cliff rather than one class's exhaustion.

40. The Transmit Scheduler

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Three contenders with a bounded policy — Sections 41 and 42
// are the two strict-priority designs.
logic new_pending, replay_pending, mgmt_pending;
logic [AGE_W-1:0] replay_age_q;      // saturating
logic [AGE_W-1:0] new_age_q;         // saturating
logic [BUD_W-1:0] replay_budget_q;   // consecutive replays before yielding
 
wire replay_starved = (replay_age_q >= REPLAY_BOUND);
wire new_starved    = (new_age_q    >= NEW_BOUND);
wire replay_capped  = (replay_budget_q == '0);
 
always_comb begin
  tx_select = SEL_NONE;
  if (mgmt_pending)                                    tx_select = SEL_MGMT;
  else if (replay_pending && replay_starved)           tx_select = SEL_REPLAY;
  else if (new_pending && new_starved)                 tx_select = SEL_NEW;
  else if (replay_pending && !replay_capped)           tx_select = SEL_REPLAY;
  else if (new_pending)                                tx_select = SEL_NEW;
  else if (replay_pending)                             tx_select = SEL_REPLAY;
end

Architecture. Management traffic first, then two bounded overrides, then a replay preference bounded by a budget. Replay is preferred because an unreplayed object holds a ring slot and blocks admission — but the budget stops a replay storm from starving new traffic forever (§42).

State. Two saturating ages and a budget counter. Saturating, because a wrapping age reports a fresh requester at the moment it has waited longest.

Cycle behaviour. Both ages advance only when their class wanted service and did not transfer — the eighth appearance of that rule in this curriculum.

Contract. Reliability relies on replay being scheduled within a bound; throughput relies on new traffic being scheduled within a bound. Neither is visible at the interface, which is why §43 asserts both.

Failure. §41 and §42.

DV. Saturate both sources; measure each worst-case wait against its bound.

41. Wrong Policy — New Traffic Always First

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a pending replay waits behind every new object, forever.
assign tx_select = new_pending ? SEL_NEW : SEL_REPLAY;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. Under sustained load new_pending is permanently true.
2. A pending replay is never scheduled.
3. Its ring entry is never resolved, so retire_ptr never advances.
4. The ring fills.
5. Admission stops (Section 9), so new_pending eventually falls.
6. -> the replay finally goes — but only after the entire ring has filled,
     and only after admission has stalled completely.

Three properties.

It self-resolves, and only by stalling everything first. The design is not permanently deadlocked, which is why it survives review — but the resolution mechanism is a full ring and a total admission stall, which is a throughput collapse rather than a policy.

The reliability latency is unbounded in practice. An object needing retransmission waits for the ring to fill, which under light retry rates can be a very long time — and during that whole period the object is unresolved and the far end may be waiting for it.

And it interacts badly with the attempt budget (§16). If the budget is timer-based, an object can exhaust its attempts while never having been scheduled, and be failed for a fault that was never retried.

42. Wrong Policy — Replay Always First

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — new traffic waits behind every pending replay, forever.
assign tx_select = replay_pending ? SEL_REPLAY : SEL_NEW;

Under a persistent error condition, replays regenerate replays.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. A marginal link causes a steady retry rate.
2. Every replay that fails becomes another pending replay.
3. replay_pending is permanently true.
4. New traffic is never scheduled.
5. -> the link is fully utilised and delivers nothing new, indefinitely.

Three properties.

It does not self-resolve. Unlike §41, nothing stops the replay stream — the condition that produces replays is the link's error rate, which the scheduler cannot influence.

And absolute replay priority is correct as a transient policy. Draining the retry backlog quickly is usually right. The error is making it unbounded, which converts a good short-term policy into a permanent one.

The budget is the fix, and it should be a number of consecutive replays rather than a rate — because what must be broken is a run, not an average.

43. SVA — Scheduler Liveness

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// LIVENESS, bounded, both directions, with assumptions stated (15.2 §36).
//
//   A1: the PHY eventually accepts a selected object
//   A2: a pending class stays pending until served
//   A3: recovery terminates (14.2 §30)
assume property (@(posedge clk) disable iff (!rst_n)
  ((tx_select != SEL_NONE) && !recovery_active) |-> ##[1:PHY_BOUND] phy_tx_fire);
assume property (@(posedge clk) disable iff (!rst_n)
  (replay_pending && !phy_tx_fire) |=> replay_pending);
 
property p_replay_eventually_scheduled;
  @(posedge clk) disable iff (!rst_n)
    replay_pending |-> ##[1:REPLAY_BOUND] ((tx_select == SEL_REPLAY) && phy_tx_fire);
endproperty
a_replay_eventually_scheduled:
  assert property (p_replay_eventually_scheduled);
 
property p_new_eventually_scheduled;
  @(posedge clk) disable iff (!rst_n)
    new_pending |-> ##[1:NEW_BOUND] ((tx_select == SEL_NEW) && phy_tx_fire);
endproperty
a_new_eventually_scheduled: assert property (p_new_eventually_scheduled);
 
// The replay budget is real — not a parameter set to infinity.
initial begin
  assert (REPLAY_BUDGET > 0 && REPLAY_BUDGET < 32'hFFFF_FFFF)
    else $fatal(1, "replay budget must be finite and non-zero (Section 42)");
end

Architecture. Two bounded liveness properties in opposite directions, plus an elaboration check.

Why both directions. §41 starves replay and §42 starves new traffic. A single fairness property with one bound cannot distinguish them, and a design corrected for one can fail the other.

Why the elaboration check. A budget set to its maximum removes the guarantee while leaving every line that implements it — the same silent-parameterisation hazard as 18.2 §34's zero reservation.

DV. Prove both; then set the budget to its maximum and confirm the second property fails.

44. Retirement Waits for Resolution

45. Wrong Retirement — Free on Send

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the replay entry is freed when the PHY takes the object.
always_ff @(posedge clk)
  if (phy_tx_fire) begin
    replay_mem[send_idx].valid <= 1'b0;
    retire_ptr_q <= retire_ptr_q + 1'b1;      // ← retirement, at the send
  end

Three properties.

The retained copy is destroyed before anything confirms it was received. The object may be corrupted on the wire, may be lost entirely, or may arrive and be rejected — and in all three cases the retransmission the architecture provides for is now impossible.

And the ring's occupancy becomes a measure of the pipeline rather than of outstanding reliability, so replay_slot_available (§9) reports capacity the design does not have. Admission then over-commits, and the whole reservation discipline in §9 is defeated by one wrong retirement event.

This is the same failure as 14.3 §15's free-on-tx_fire and 19.1 §18's fourth property — and it recurs because phy_tx_fire is local, available this cycle, and feels like completion. Resolution is a message from the far end, several stages and a round trip away.

46. SVA — Retirement Discipline

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. An entry retires only on resolution.
property p_retire_requires_resolution;
  @(posedge clk) disable iff (!rst_n)
    replay_retire_fire |-> resolved_for(retire_idx);
endproperty
a_retire_requires_resolution: assert property (p_retire_requires_resolution);
 
// The entry is not freed at the send.
property p_not_freed_on_send;
  @(posedge clk) disable iff (!rst_n)
    (phy_tx_fire && !resolved_for(send_idx)) |=> $stable(retire_ptr_q);
endproperty
a_not_freed_on_send: assert property (p_not_freed_on_send);
 
// Retirement is in order — the oldest unresolved entry retires first.
property p_retire_in_order;
  @(posedge clk) disable iff (!rst_n)
    replay_retire_fire |-> (retire_idx == retire_ptr_q[PTR_W-1:0]);
endproperty
a_retire_in_order: assert property (p_retire_in_order);
 
// An object is never retired while a retransmission is pending for it.
property p_no_retire_with_replay_pending;
  @(posedge clk) disable iff (!rst_n)
    replay_retire_fire |-> (tx_state_q[retire_obj] != TX_REPLAY_PENDING);
endproperty
a_no_retire_with_replay_pending:
  assert property (p_no_retire_with_replay_pending);

Architecture. Four properties: the resolution requirement, the negative send case, in-order retirement, and consistency with the transmit state.

Why the fourth exists. The ring and the state machine are two views of one object. They can disagree — a resolution processed while a retry indication is in flight — and §16's priority decision is what keeps them consistent. The fourth property checks it did.

DV. All four always-on. The second catches §45 directly, at the cycle of the send.

47. Configuration Epoch

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Requested-versus-active, and the commit guard that a
// retransmission makes necessary.
link_cfg_t              requested_cfg_q, active_cfg_q;
logic [CFG_EPOCH_W-1:0] active_cfg_epoch_q;
 
assign cfg_commit_allowed =
      cfg_validated
   && (staging_occupancy == '0)
   && (replay_outstanding == '0);     // ← the term a block-level design omits
 
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n)
    active_cfg_epoch_q <= '0;
  else if (cfg_commit_fire) begin
    active_cfg_q       <= requested_cfg_q;
    active_cfg_epoch_q <= active_cfg_epoch_q + 1'b1;
  end

Architecture. Two copies committed atomically, guarded by both a drained pipeline and an empty replay ring.

Why the replay term. A retained object was framed under the configuration in force when it was created. A retransmission after a commit would transmit it under a configuration the far end no longer expects — and the object's cfg_epoch (§7) is what makes the mismatch detectable rather than silent.

Contract. Everything in the datapath reads active_cfg_q. They see one complete configuration or the previous one, never a mixture (19.1 §37).

Failure. Committing with a non-empty ring, which produces exactly the mismatch above on the first retransmission.

DV. §48; cover a commit attempted with outstanding replay entries.

48. SVA — Configuration Lifetime

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The active configuration changes only at a guarded commit.
property p_cfg_only_at_commit;
  @(posedge clk) disable iff (!rst_n)
    $changed(active_cfg_q) |-> $past(cfg_commit_fire);
endproperty
a_cfg_only_at_commit: assert property (p_cfg_only_at_commit);
 
property p_commit_requires_drain_and_empty_ring;
  @(posedge clk) disable iff (!rst_n)
    cfg_commit_fire |-> (($past(staging_occupancy) == '0)
                      && ($past(replay_outstanding) == '0));
endproperty
a_commit_requires_drain_and_empty_ring:
  assert property (p_commit_requires_drain_and_empty_ring);
 
// A live object's captured epoch never changes under it.
property p_object_epoch_stable;
  @(posedge clk) disable iff (!rst_n)
    replay_mem[IDX].valid |-> $stable(replay_mem[IDX].cfg_epoch);
endproperty
a_object_epoch_stable: assert property (p_object_epoch_stable);
 
// A retransmission uses the object's captured epoch, not the live one.
property p_retransmit_uses_captured_epoch;
  @(posedge clk) disable iff (!rst_n)
    (phy_tx_fire && is_retransmit) |-> (tx_cfg_epoch == replay_mem[send_idx].cfg_epoch);
endproperty
a_retransmit_uses_captured_epoch:
  assert property (p_retransmit_uses_captured_epoch);

Architecture. Four properties, and the fourth is the retransmission-specific one.

Why it matters. The first three could all hold while a retransmission reads the live epoch instead of the retained one. The fourth is the only property that checks the retry path specifically, and the retry path is exactly where a stale-configuration object lives.

DV. Force a commit with a non-empty ring and confirm the second fires.

49. Recovery

StateOn a recovery
Accepted transport obligationspreserved — §4's promise is not withdrawn by a link event
Object identities and captured epochspreserved, and not recomputed
Replay historypreserved and re-baselined with the peer — not cleared (§50)
Attempt countspreserved — they are evidence
RX partial reassemblyarchitecture-defined: completed, abandoned explicitly, or retried
The duplicate windowre-baselined with the peer — clearing it permits duplicates
Creditsre-advertised (19.5)
Training, lane map, physical staterebuilt
First faultpreserved — this is the point (19.1 §35)

Two rows deserve emphasis.

Clearing the duplicate window is as harmful as clearing the ring. After a recovery the peer may retransmit objects it never got confirmation for; a cleared window accepts every one of them as new, which is a second semantic delivery for each.

And the attempt counts are evidence, not just control. An object on its fourth attempt tells a post-silicon engineer something no other counter does.

50. Wrong Recovery — Clearing the Reliability State

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a recovery is treated as a reason to start clean.
always_ff @(posedge clk)
  if (recovery_entered) begin
    for (int i = 0; i < REPLAY_DEPTH; i++) replay_mem[i].valid <= 1'b0;
    alloc_ptr_q  <= '0;
    send_ptr_q   <= '0;
    retire_ptr_q <= '0;
    received_q   <= '0;                       // the duplicate window too
  end

Four independent failures from one block.

ClearedConsequence
replay entriesobjects the peer never received can never be retransmitted — lost
the pointersthe ring's relationship to the peer's expectation is destroyed
the duplicate windowevery retransmission the peer sends is accepted as new — duplicates
and the local side cannot know which of the two happened for any object

Four properties.

The ambiguity is the core harm. Before the recovery, some objects had been received and some had not, and the state that distinguished them has been erased. A recoverable ambiguity has been converted into an unrecoverable one — the design now cannot even determine what to do.

The two failures are opposite and simultaneous. Some objects are lost because they cannot be replayed; others are duplicated because the window forgot them. A single test sees one or the other depending on timing.

And the link comes back and reports success. Training completes, the link reaches an admitting state, and every link-level metric says the recovery worked — which is 19.1 §9's observation at the Adapter.

The correct action is re-baselining, not clearing. Whatever the architecture's mechanism, both sides must agree on what has been received before traffic resumes — and preserving the local state is a precondition for that agreement, not an obstacle to it.

51. SVA — Reliability State Survives Recovery

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Obligations, identities and evidence survive a recovery.
property p_replay_survives_recovery;
  @(posedge clk) disable iff (!rst_n)
    recovery_entered |=> ($stable(replay_mem[IDX].valid)
                       && $stable(replay_mem[IDX].object_id)
                       && $stable(replay_mem[IDX].cfg_epoch)
                       && $stable(replay_mem[IDX].attempts));
endproperty
a_replay_survives_recovery: assert property (p_replay_survives_recovery);
 
// The duplicate window is not cleared (Section 50).
property p_window_survives_recovery;
  @(posedge clk) disable iff (!rst_n)
    recovery_entered |=> $stable(received_q);
endproperty
a_window_survives_recovery: assert property (p_window_survives_recovery);
 
// A recovery produces neither an acceptance nor a retirement.
property p_recovery_no_transport_event;
  @(posedge clk) disable iff (!rst_n)
    recovery_entered |=> ($stable(accept_count) && $stable(retire_count));
endproperty
a_recovery_no_transport_event:
  assert property (p_recovery_no_transport_event);
 
// No new admission while the link manager forbids it.
property p_no_admission_during_recovery;
  @(posedge clk) disable iff (!rst_n)
    recovery_active |-> !adapter_accept;
endproperty
a_no_admission_during_recovery:
  assert property (p_no_admission_during_recovery);
 
// The first fault survives (19.1 Section 35).
property p_first_fault_survives;
  @(posedge clk) disable iff (!rst_n)
    recovery_entered |=> $stable(first_fault_q);
endproperty
a_first_fault_survives: assert property (p_first_fault_survives);

Architecture. Five properties: entries, window, events, admission, and evidence.

Why the third is the sharpest. It forbids both directions — a recovery must not accept new work and must not retire outstanding work. A design that "helpfully" fails outstanding objects on a link event violates it just as surely as one that clears them.

DV. Inject a recovery with a full ring, a partially-filled window, live reassembly and a captured fault (§56).

52. Error Counters and the Stall Classifier

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Diagnostic only, in the POR reset domain (19.1 Section 7).
logic [63:0] integrity_fail_q;
logic [63:0] retry_count_q;
logic [63:0] replay_attempts_q;
logic [63:0] duplicate_suppressed_q;      // Section 32 — the guard working
logic [63:0] out_of_window_q;             // Section 32 — stale or corrupt
logic [63:0] recovery_cycles_q;
logic [63:0] replay_full_stall_q;         // Section 9's second term biting
logic [63:0] staging_full_stall_q;
logic [63:0] incomplete_object_q;         // Section 35 — abandoned reassembly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. 15.5 Section 10's causal-priority classifier, at admission.
typedef enum logic [3:0] {
  ADP_ACCEPT     = 4'd0,
  ADP_NO_INPUT   = 4'd1,   // nothing offered           <- must be early
  ADP_RECOVERY   = 4'd2,
  ADP_CFG_COMMIT = 4'd3,   // a deliberate quiesce
  ADP_LINK_DOWN  = 4'd4,
  ADP_STAGING_FULL = 4'd5,
  ADP_REPLAY_FULL  = 4'd6, // Section 9's second term
  ADP_NO_CREDIT    = 4'd7,
  ADP_PHY_STALL    = 4'd8,
  ADP_UNATTRIB     = 4'd9  // must stay at zero
} adp_stall_e;
 
adp_stall_e adp_reason_d;
 
always_comb begin
  unique case (1'b1)
    adapter_accept              : adp_reason_d = ADP_ACCEPT;
    !in_valid                   : adp_reason_d = ADP_NO_INPUT;
    recovery_active             : adp_reason_d = ADP_RECOVERY;
    !cfg_stable                 : adp_reason_d = ADP_CFG_COMMIT;
    !link_accepting             : adp_reason_d = ADP_LINK_DOWN;
    !staging_space_available    : adp_reason_d = ADP_STAGING_FULL;
    !replay_slot_available      : adp_reason_d = ADP_REPLAY_FULL;
    !credit_available[in_class] : adp_reason_d = ADP_NO_CREDIT;
    !phy_tx_ready               : adp_reason_d = ADP_PHY_STALL;
    default                     : adp_reason_d = ADP_UNATTRIB;
  endcase
end

Architecture. Ten mutually exclusive reasons summing to elapsed cycles.

ADP_STAGING_FULL against ADP_REPLAY_FULL is the pair that matters most here. They look identical from outside — "the Adapter is not accepting" — and have completely different fixes: staging depth is a pipeline parameter, and replay depth is a reliability window (§19). A design that reports only "buffer full" cannot tell them apart.

And duplicate_suppressed_q is the counter that proves §32 is working. A zero value under a retrying link means the window is not doing its job.

53. The Adapter Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Verification-only. FOUR models. 19.2's semantic model stays separate.
class adapter_scoreboard;
 
  // ---- Layer 1: OBJECT model — accepted, state, epoch, retired.
  typedef struct {
    bit  accepted;
    int  state;
    int  cfg_epoch;
    bit  in_staging, in_replay;      // TWO fields — Section 11's window
    bit  retired, failed;
    bit  crossed_recovery;
  } object_model_t;
  object_model_t objs [int];
 
  // ---- Layer 2: REPLAY / HISTORY model — attempts and resolution.
  typedef struct {
    int  seq;
    int  attempts;                   // >= 1 once sent
    bit  resolved;
    bit  overwritten;                // Section 21 — must never be set
  } history_model_t;
  history_model_t hist [int];
 
  // ---- Layer 3: RX-DELIVERY model — integrity, novelty, delivery count.
  typedef struct {
    bit  integrity_ok;
    bit  was_duplicate;
    bit  in_window;
    int  semantic_deliveries;        // MUST be <= 1
    bit [63:0] frags_received;
    int  frag_count;
  } rx_model_t;
  rx_model_t rx [int];
 
  // ---- Layer 4: RESOURCE model — occupancies and credits by class.
  typedef struct {
    int staging_occ, replay_occ;
    int credits_available, credits_outstanding;
  } resource_model_t;
  resource_model_t res [int];
 
  // ---- Catches Sections 10 and 14 — an object owned by nothing.
  function void check_ownership(int id);
    if (objs[id].accepted && !objs[id].retired && !objs[id].failed
        && !objs[id].in_staging && !objs[id].in_replay)
      $error("OBJECT %0d owned by NOTHING (Sections 10, 14)", id);
  endfunction
 
  // ---- Catches Section 21 — the overwrite, before it corrupts a retry.
  function void check_no_overwrite(int id);
    if (hist[id].overwritten)
      $error("HISTORY %0d overwritten while unresolved (Section 21)", id);
  endfunction
 
  // ---- Catches Sections 31, 33, 36 — delivery more than once.
  function void check_delivery(int id);
    if (rx[id].semantic_deliveries > 1)
      $error("OBJECT %0d delivered %0d times (must be <= 1)", id, rx[id].semantic_deliveries);
    if ((rx[id].semantic_deliveries > 0) && !rx[id].integrity_ok)
      $error("OBJECT %0d delivered with failed integrity (Section 31)", id);
    if ((rx[id].semantic_deliveries > 0)
        && ((rx[id].frags_received & frag_mask(rx[id].frag_count))
            != frag_mask(rx[id].frag_count)))
      $error("OBJECT %0d delivered incomplete (Section 36)", id);
  endfunction
 
  // ---- Catches Section 45 — retired before resolution.
  function void check_retirement(int id);
    if (objs[id].retired && !hist[id].resolved)
      $error("OBJECT %0d retired without resolution (Section 45)", id);
  endfunction
 
  // ---- Catches Section 50.
  function void check_recovery(int id);
    if (objs[id].crossed_recovery && !objs[id].retired && !objs[id].failed
        && !objs[id].in_replay)
      $error("OBJECT %0d lost its history across a recovery (Section 50)", id);
  endfunction
 
endclass

Architecture. Four models: the object, its history, its receipt, and the resources.

Layer 1 tracks in_staging and in_replay as two separate fields rather than one "somewhere in the Adapter" flag — because §10's and §14's failures are precisely the state in which both are false.

Layer 2's overwritten flag exists to be always false. It is a detector rather than a measurement, and a single set of it is §21.

And 19.2's semantic model is deliberately absent. The Adapter has no business modelling semantic operations, and a scoreboard that merges the two loses the ability to say which layer failed (19.2 §49).

54. Flagship Trace 1 — Transmit With a Retry

Illustrative. Cycle numbers illustrative.

CycAdmissionStagingTX stateReplay ring (a/s/r)CRCPHYAttempts
0ready04/4/4idleidle
1accepts1STAGED4/4/4idle0
21STAGED4/4/4startsidle0
51STAGED4/4/4feedingidle0
81STAGED5/4/4finalidle0
91HISTORY_OWNED5/4/4idle0
9both own it§13's window
100HISTORY_OWNED5/4/4idle0
120SENT_WAIT_ACK5/5/4attempt 11
300SENT_WAIT_ACK5/5/4in flight1
380REPLAY_PENDING5/5/41
38retry indication
440SENT_WAIT_ACK5/5/4attempt 22
700RETIRED5/5/52
710FREE5/5/5

Seven readings.

Cycle 9 is the dual-ownership window — staging and replay both hold it. §14's design frees the only copy at cycle 12.

Cycle 10 releases staging, not at cycle 12 when the PHY took it. Staging's job ended when replay confirmed.

The ring's allocation pointer advances at cycle 8 and its retirement pointer at cycle 70 — sixty-two cycles apart. That gap is the reliability window, and it is what §19's depth must cover.

Cycle 38: a retry indication returns the object to REPLAY_PENDING without passing through STAGED (§15). The same object, the same sequence, the same retained copy.

Cycle 44 sends attempt 2 from replay_mem, byte-identical to attempt 1 (§19). A modified copy would not be recognised as a duplicate at the far end.

Cycle 70 retires on resolution. §45's design retires at cycle 12, and the retry at cycle 44 would then have had nothing to send.

And the attempt count reaches 2 and is retained as evidence (§6).

55. Flagship Trace 2 — Receive, Then the Duplicate

CycBeatsIntegrityWindowReassemblyDeliveryAck
20frag 0{1,0,0}
22frag 1{1,1,0}
24frag 2{1,1,1}
25checked: OKcomplete
26not a duplicate
27recordeddelivered ×1
29ack sent
the duplicate, later
60frag 0{1,0,0}
64frag 2{1,1,1}
65checked: OKcomplete
66DUPLICATEnone
67bit already set
68ack sent AGAIN

Five readings.

Cycles 25 to 27: integrity, then novelty, then delivery. Three checks before the object reaches the protocol engine. §31's design delivers at cycle 24.

Cycle 66: the duplicate is recognised because the window remembers position 10 and not merely the last sequence. §33's register would have forgotten it by cycle 60.

Cycle 67: recording is idempotent. The bit is already set and nothing changes.

Cycle 68: the acknowledgement is sent again. §34's third property — suppressing the effect and the reply livelocks the sender.

And the semantic delivery count is exactly one across both arrivals — §4's obligation, observed.

56. Flagship Trace 3 — Recovery With Live Reliability State

CycRing (a/s/r)WindowTX stateAdmissionLinkMust be true
10012/11/64 positions setmixedacceptingoperational6 unresolved
10412/11/64 setmixedacceptingerror
10512/11/64 setunchangedstopsrecoverynothing cleared
10612/11/64 setunchangedstoppedrecoveryfirst fault captured
11012/11/64 setunchangedstoppedretrainingattempts preserved
15012/11/64 setunchangedstoppedre-baseliningboth sides agree
15812/11/64 setunchangedstoppedoperational
16012/11/64 setreplays beginresumesoperational
20012/12/126 setall RETIREDacceptingoperationalall six resolved

Six readings.

Cycle 105: six unresolved entries, six preserved. The pointers, the retained copies, the attempt counts and the window are all untouched. §50's design zeroes all of it here.

Cycle 106: the fault is captured before the retrain (19.1 §33) — the ordering that keeps the evidence.

Cycle 150 is re-baselining, not clearing. Whatever the architecture's mechanism, both sides establish what has been received — which is only possible because the local state survived.

Cycle 160: admission resumes and replays begin. Admission stopped at 105 and nothing was retired in between — §51's third property.

Cycle 200: all six resolve. Some were retransmitted and some were already received; the window and the re-baselining decided which, and no object was delivered twice.

And the link may have returned degraded, which changes throughput and not one field of any entry.

57. Coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_adapter @(posedge clk);
  option.per_instance = 1;
 
  // --- Admission (Sections 9-11).
  cp_admit_refusal : coverpoint admit_refusal_reason {
    bins staging = {0}; bins replay = {1}; bins credit = {2};
    bins link = {3}; bins cfg = {4};                    // each term alone
  }
  cp_staging_occ : coverpoint staging_occupancy {
    bins empty = {0}; bins mid = {[1:STAGING_DEPTH-1]}; bins full = {STAGING_DEPTH};
  }
  cp_replay_occ : coverpoint replay_outstanding {
    bins empty = {0}; bins mid = {[1:REPLAY_DEPTH-1]}; bins full = {REPLAY_DEPTH};
  }
  cp_dual_window : coverpoint dual_ownership_window_active;   // Section 13
  cp_error_in_window : coverpoint error_during_ownership_handoff;
 
  // --- TX state (Sections 15-18).
  cp_tx_state : coverpoint tx_state_q_ut { bins each[] = {[0:6]}; }
  cp_simul_retry_resolve : coverpoint retry_and_resolve_same_cycle;  // Section 16
  cp_attempts : coverpoint attempts_q_ut {
    bins one = {1}; bins few = {[2:3]}; bins budget = {ATTEMPT_BUDGET};
  }
 
  // --- Ring (Sections 20-24).
  cp_ring_wrap : coverpoint ring_pointer_wrapped {
    bins none = {0}; bins alloc = {1}; bins send = {2}; bins retire = {3};
  }
  cp_simul_alloc_retire : coverpoint alloc_and_retire_same_cycle;
  cp_ring_full_sustained : coverpoint ring_full_for_n_cycles {
    bins none = {0}; bins brief = {[1:16]}; bins sustained = {[17:$]};  // Section 21
  }
 
  // --- Integrity (Sections 25-29).
  cp_crc_outcome : coverpoint integrity_outcome {
    bins pass = {0}; bins fail = {1};
  }
  cp_stall_during_crc : coverpoint stall_between_crc_and_send;   // Section 27
  cp_back_to_back : coverpoint consecutive_objects_differing;    // Section 29
 
  // --- Receive (Sections 30-37).
  cp_rx_outcome : coverpoint rx_object_outcome {
    bins delivered = {0}; bins corrupt = {1}; bins duplicate = {2};
    bins out_of_window = {3}; bins no_space = {4};      // all five gate terms
  }
  cp_dup_position : coverpoint duplicate_window_position {
    bins at_base = {0}; bins middle = {1}; bins far_edge = {2};   // Section 33
  }
  cp_frag_event : coverpoint fragment_event {
    bins in_order = {0}; bins reordered = {1}; bins duplicate = {2}; bins missing = {3};
  }
 
  // --- Scheduler (Sections 40-43).
  cp_sched_select : coverpoint tx_select_ut {
    bins none = {0}; bins new_traffic = {1}; bins replay = {2}; bins mgmt = {3};
  }
  cp_replay_budget : coverpoint replay_budget_q_ut {
    bins full = {REPLAY_BUDGET}; bins some = {[1:REPLAY_BUDGET-1]}; bins spent = {0};
  }
  cp_starvation : coverpoint starvation_override_fired {
    bins none = {0}; bins replay_override = {1}; bins new_override = {2};
  }
 
  // --- Configuration and recovery (Sections 47-51).
  cp_cfg_context : coverpoint cfg_commit_context {
    bins idle = {0}; bins blocked_by_staging = {1};
    bins blocked_by_replay = {2}; bins forced = {3};
  }
  cp_recovery_context : coverpoint recovery_with_state {
    bins none = {0};
    bins ring_nonempty = {1};
    bins window_populated = {2};
    bins reassembly_partial = {3};
    bins all = {4};                                    // THE case — Section 56
  }
 
  // --- Attribution (Section 52).
  cp_stall_reason : coverpoint adp_reason_d { bins each[] = {[0:9]}; }
 
  // --- Crosses that carry the information.
  x_ring_retry     : cross cp_ring_full_sustained, cp_attempts;     // Section 21
  x_dup_position   : cross cp_dup_position, cp_rx_outcome;          // Section 33
  x_recovery_ring  : cross cp_recovery_context, cp_replay_occ;      // Section 56
  x_sched_budget   : cross cp_sched_select, cp_replay_budget;       // Section 42
endcovergroup

Nine bins worth calling out:

cp_ring_full_sustained.sustained crossed with cp_attempts. §21's precondition — the ring genuinely full for a long period with retries occurring. Only a soak test reaches it.

cp_error_in_window. §13's one-cycle handoff, with an error injected inside it.

cp_dup_position — all three. §33's argument: a duplicate at the base is caught by a last-sequence register and one at the far edge is not. Only the far-edge bin distinguishes the two designs.

cp_stall_during_crc. §27's precondition — a stall between the computation and the send, with a live metadata source.

cp_back_to_back. §29's off-by-one, undetectable with a single object.

cp_simul_retry_resolve. §16's priority decision, exercised.

cp_rx_outcome — all five. The five gate terms individually (§30).

cp_cfg_context.blocked_by_replay. §47's replay term, shown to block a commit.

And cp_recovery_context.all — a recovery with a non-empty ring, a populated window and partial reassembly simultaneously. Every property in §51 depends on it.

58. Debug Taxonomy

SignatureMost likely causeFirst instrument
Works until sustained load, then corrupts§21 — the ring overwrote a live entrycp_ring_full_sustained; the overwrite detector
A retry delivers the wrong data§21 again — the retained copy was replacedhistory model's overwritten flag
Integrity failures begin only once the pipeline fills§29 — the result is one stage from its objectfirst-object-of-burst passes, rest fail?
Intermittent integrity failures on a healthy link§27 — covered metadata changed during a stallis the covered bundle one register?
A duplicate semantic object after a link error§31 or §33 — delivery before the verdict, or too small a windowduplicate_suppressed_q; window size vs replay depth
Retries never stop on one object§34's third property — the duplicate was not acknowledgedis the reply sent for a recognised duplicate?
An object is lost after a recovery§50 — reliability state clearedwhat changed at the recovery cycle
Duplicates appear after a recovery§50's other half — the window clearedwas received_q preserved?
A replay storm blocks all new traffic§42 — unbounded replay prioritycp_replay_budget.spent; is the budget finite?
A retry waits until the ring fills§41 — unbounded new-traffic priorityreplay age against REPLAY_BOUND
The Adapter stops accepting and nothing is wrong§45 — retirement never happens, so the ring never drainsis retirement on resolution or on send?
The far end rejects after a configuration change§47 — a retransmission under the new configurationis the commit guarded by an empty ring?
"Buffer full" with no idea which§52 — one stall bucketsplit STAGING_FULL from REPLAY_FULL

Row 1 is the worst. Works until sustained load, then corrupts is the ring overwrite, and it substitutes one object's data under another's identity with every check in the system passing.

59. Debug Checklist

  1. Which object — the Adapter's identity? (§7)
  2. Which sequence identity, and which semantic identity does it carry? (§8)
  3. What TX state, and for how long? (§15)
  4. Did the committed state equal the next-state function's output? (§18)
  5. Was the object owned by staging, replay, or both at every cycle? (§11)
  6. Was a replay slot reserved at admission? (§9)
  7. What are the three ring pointers, and what is the derived occupancy? (§20)
  8. Has allocation ever approached retirement? (§22, §23)
  9. Is the retained copy byte-identical to what was sent? (§19)
  10. How many attempts has this object had? (§16)
  11. Was the attempt budget exhausted? (§16)
  12. Which resource class, and were credits available for that class? (§38)
  13. Was the integrity result computed over exactly what was sent? (§28)
  14. Did the covered bundle change during a stall? (§27)
  15. Is the result's valid aligned with the object's? (§29)
  16. On the receive side: did all five gate terms hold? (§30)
  17. Was this object in the duplicate window, and at which position? (§32)
  18. How large is the window relative to the sender's replay depth? (§32)
  19. Was a recognised duplicate still acknowledged? (§34)
  20. Which fragments arrived, and were any duplicated or missing? (§35)
  21. What retired the entry — a resolution, or the send? (§44, §45)
  22. Which configuration epoch did the object carry, and did it change? (§47)
  23. Was a commit attempted with a non-empty ring? (§48)
  24. Did a recovery occur, and did the ring, window and attempts survive? (§51)
  25. Was admission stopped during the recovery, and nothing retired? (§51)
  26. What does the first-fault record say? (19.1 §35)
  27. What does the stall histogram say, and is ADP_UNATTRIB non-zero? (§52)
  28. Which of the four scoreboard layers diverged first? (§53)

60. Common Misconceptions

"The Adapter is just CRC plus a FIFO." It owns admission with reservation, two buffers with different lifetimes, a three-pointer ring, an integrity pipeline whose alignment is a correctness property, a duplicate window, a scheduler with two liveness bounds, and a retirement rule that waits for the far end. The FIFO is one of ten rows in §6's table.

"The replay buffer is a normal TX queue." A queue's entry is freed when it is consumed; a replay entry is freed when the far end no longer needs it — an entirely different event, a full round trip later, and possibly after several retransmissions (§12).

"A sent object can be freed." Sending is the first of four events, and only the last one — resolution — may retire the entry. Freeing at the send destroys the copy the retry mechanism exists to use, and simultaneously makes the ring's occupancy a measure of the pipeline rather than of outstanding reliability (§44, §45).

"One identity is enough for protocol, Adapter and PHY." Three namespaces with three lifetimes, recycled by three layers on three schedules. A retransmission reuses the sequence identity and creates no new object and no new semantic operation (§8).

"A replay is another semantic request." It is the same object, re-sent. Treating it as new allocates a second object, consumes a second credit, and may produce a second semantic delivery at the far end (§8, §15).

"CRC may be checked after delivery." Delivering first and checking after hands the protocol engine an object that may be corrupt, a duplicate, or outside the window — three of the four validity-and-novelty combinations handled wrongly, with the transport behaving exactly as specified (§31).

"A response counter can track fragment completeness." A duplicated fragment — a correct transport retry — increments the count while a different fragment is still missing, and the object is delivered incomplete (§36).

"One last-sequence register is enough to suppress duplicates." It detects only an immediate retransmission with nothing in between, which is the least likely pattern — because a retransmission follows a round trip during which other objects were almost certainly sent (§33).

"Credits are global." A scalar stalls everything when one class is exhausted, and admits an object for an exhausted class when another has room. The first is a throughput loss and the second is the overflow flow control existed to prevent (§39).

"Recovery should clear retry state." Clearing the ring loses objects the peer never received; clearing the window duplicates every object it retransmits. Both happen at once, the local side cannot tell which applies to any object, and the link reports a successful recovery (§50).

"Replay traffic should always have absolute priority." Correct as a transient policy and fatal as a permanent one — under a persistent error rate, replays regenerate replays and new traffic never runs. The fix is a budget on consecutive replays, because what must be broken is a run (§42).

"Buffer full is a performance issue, not a correctness issue." A full replay ring is a correctness constraint: an object accepted without a slot to retain it cannot honour the delivery promise, which is why the slot is an admission term rather than a downstream check (§9, §10).

"A clean CRC proves exactly-once delivery." Integrity proves the bytes survived. Exactly-once needs the window, the reassembly bitmap and the gate ordering — and every one of them can fail with perfect integrity throughout (§30–§37).

61. Understanding Check

62. Summary and What Comes Next

The Adapter owns transport reliability and not semantics. An accepted object is delivered exactly once or explicitly failed, while attempts underneath vary from zero to many.

Reserve before you accept. A replay slot is an admission term, because accepting an object the design cannot retain is a promise it cannot keep — and all three ways of discovering that later are failures.

Staging and replay are different lifetimes even in one memory: one is pipeline depth, the other is a round trip plus retries, and freeing on the send destroys the copy the retry exists to use.

Three pointers, one extra bit. Allocation passing retirement substitutes one object's data under another's identity with every check in the system passing — and a derived occupancy cannot drift.

Integrity is an alignment problem as much as a coverage problem. A covered field that changes during a stall, or a result one stage from its object, rejects perfectly correct data — and one of the two passes every single-object test.

Check before you deliver, and remember more than the last sequence. Three of four validity-and-novelty combinations are mishandled by a design that delivers first, and a window of one detects the least likely duplicate pattern.

Retire on resolution. It is the last of four events, and picking the first breaks the retry contract and defeats the admission discipline simultaneously.

Bound the scheduler in both directions, because starving replay collapses throughput and starving new traffic delivers nothing at all.

And a recovery re-baselines rather than clears — clearing loses some objects, duplicates others, and destroys the state that could have told you which.

The Adapter now has several kinds of retained state — staging, replay history, reassembly, the receive window and the protocol-facing queues. The next chapter stops treating those as generic FIFOs and asks the architectural questions that decide whether they can absorb latency without overflowing, deadlocking, or losing ownership: what each kind is for, how deep each must be, when backpressure must begin, and what a buffer's capacity has to do with correctness.

Browse the full path on the UCIe tutorials index.