Skip to content

UCIe · Module 19

Link Architecture

If you had to implement one side of a UCIe link in RTL, what blocks exist, what state does each own, and what crosses each boundary — the state-ownership table that is the chapter's backbone, a reset hierarchy that must not collapse into one tree, the dual-ownership window between the TX queue and the replay buffer, a receive path that must not deliver before the verdict, three cooperating state machines, and the recovery ordering that captures the fault before retraining erases it.

Eighteen modules have established what a UCIe-based system must do. This chapter asks the question all of it eventually becomes: if you had to build one side of the link, what would be in it?

1. The One-Sentence Model

A UCIe link is not one state machine. It is a hierarchy of state machines and queues whose lifetimes overlap — semantic obligations, reliability state, flow-control state, link-management state, training state, lane state and physical state — and the entire architecture is the discipline of never letting one of them reset, release or decide on behalf of another.

2. What This Chapter Owns

This chapter synthesises; it does not repeat. Every mechanism below has a chapter that teaches it in depth, and the value here is the partition, the boundaries, and the state lifetimes — which no single earlier chapter could show, because each was looking at one mechanism.

MechanismTaught inWhat this chapter adds
Layer responsibilities, Protocol / Adapter / PHY4.2which blocks own which state (§7)
Request and response flow, transaction lifecycle12.1 · 12.2 · 12.4where the queues sit in the top level (§12, §22)
Credit-based flow control13.1the credit manager as a block (§20)
CRC, detection, the delivery gate14.1the RX pipeline that implements it (§24)
Error recovery, ordering, first-fault capture14.2 · 14.5the recovery protocol at top level (§37)
Replay, retirement, duplicate suppression14.3the ownership handoff to the replay block (§17)
Lane repair, requested-versus-active configuration14.4the configuration block and its commit (§41)
Clock domains and CDC bridges at package scope18.4 §17the crossings inside one link (§45)
Protocol-layer engine RTL19.2 — Protocol Engines

The three things that exist only here:

§7's state-ownership table. Every piece of state in a link, with its owner, its index, its allocation and release events, and whether it survives a recovery and a reset. It is the chapter's backbone and everything else refers to it.

§17's dual-ownership window. The only place in this curriculum where an object is deliberately owned by two structures at once — and the exact rule for when that window opens and closes.

And §30's three-FSM partition. Link management, training and lane management are three machines with three lifetimes, and §33 shows what a single merged FSM does wrong.

3. Sourcing

4. The Top Level

One side of a UCIe link drawn as nine blocks. A protocol client feeds a transmit ingress stage which queues objects for the die to die Adapter across the FDI boundary. The Adapter transmit path performs framing and hands objects to a replay and history block that retains them for retransmission, and to a credit manager that gates admission. The Adapter passes objects across the RDI boundary to the physical layer logic and then to the lanes. On the receive side, a receive egress stage reconstructs objects and delivers them to the protocol client only after a delivery gate. Alongside the datapath sit three control machines: a link manager owning operational and recovery policy, a training controller owning the physical training sequence, and a lane manager owning lane mapping, repair and degradation. A fault manager captures the first error before any of them retrains. The point of the drawing is that the control machines are beside the datapath, not inside it, because their state lifetimes differ.Protocol clientsemantic obligationsTX ingress queueaccepted, not yetframedAdapter TXframing, CRC (§3)Replay + historyretained forretransmitCredit managergates admissionPHY logic + lanesacross RDIRX egress + gatedeliver once, afterthe verdictLink managerpolicy: admit,recoverFault + lanemanagerfirst fault, lane map12
One side of a link, drawn as the blocks that own state. The Protocol interface and the Adapter meet at FDI; the Adapter and the PHY logic meet at RDI. Three control machines — link manager, training controller and lane manager — sit alongside the datapath rather than inside it, because their lifetimes differ from the data's.

Read the three control blocks on the right. They are beside the datapath, not in it — because a link manager's state lives for the link's whole operational life, a training controller's for one training sequence, and a datapath object's for a few dozen cycles. §30 is why merging them is wrong.

5. The Block Inventory

BlockOwnsLifetime of its state
ucie_tx_ingressthe TX queue; accepted-but-unframed objectsper object
ucie_adapter_txframing, CRC generation, TX pipeline registersper object, per stage
ucie_replayretained objects, the three pointers, attempt countsuntil acknowledged
ucie_credit_mgrper-class credit countersper link epoch
ucie_rx_ingressreconstruction, validation, the duplicate historyper object
ucie_rx_egressthe RX queue and the delivery gateper object
ucie_link_mgroperational and recovery policythe link's operational life
ucie_training_ctrlthe training sequenceone training attempt
ucie_lane_mgrlane map, repair state, requested/active widthconfiguration epoch
ucie_cfgrequested and active configuration, the epochconfiguration epoch
ucie_fault_mgrfirst-fault record, escalation, countersdeliberate reset only
ucie_perfperformance counters, the stall classifierdeliberate reset only

6. A Top-Level Skeleton

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE top-level. NOT a UCIe-defined module, port list or hierarchy.
// The point is the block boundaries and which clocks and resets reach where.
module ucie_link_top #(
  parameter int  PROTO_W        = 256,   // protocol-side payload width
  parameter int  NUM_LANES      = 16,
  parameter int  TX_Q_DEPTH     = 32,
  parameter int  RX_Q_DEPTH     = 32,
  parameter int  REPLAY_DEPTH   = 64,
  parameter int  NUM_CLASSES    = 4,
  parameter int  CREDIT_INIT    = 16
) (
  // --- Clocks and resets. THREE domains, not one (Section 45).
  input  logic                   proto_clk,
  input  logic                   proto_rst_n,      // power-on scope
  input  logic                   adapter_clk,
  input  logic                   adapter_rst_n,
  input  logic                   phy_clk,
  input  logic                   phy_rst_n,
 
  // --- Protocol-side interface (conceptually FDI-facing).
  input  logic                   tx_valid,
  output logic                   tx_ready,
  input  logic [PROTO_W-1:0]     tx_payload,
  input  logic [$clog2(NUM_CLASSES)-1:0] tx_class,
 
  output logic                   rx_valid,
  input  logic                   rx_ready,
  output logic [PROTO_W-1:0]     rx_payload,
 
  // --- PHY-side interface (conceptually RDI-facing).
  output logic [NUM_LANES-1:0]   phy_tx_data,
  output logic                   phy_tx_valid,
  input  logic                   phy_tx_ready,
  input  logic [NUM_LANES-1:0]   phy_rx_data,
  input  logic                   phy_rx_valid,
 
  // --- Control and observability.
  input  logic                   cfg_write,
  input  logic [CFG_W-1:0]       cfg_wdata,
  output logic                   link_active,
  output logic                   link_degraded,
  output logic [FAULT_W-1:0]     first_fault
);
 
  // Parameter sanity — a bad parameter should fail the build (18.4's argument).
  initial begin
    assert (TX_Q_DEPTH   > 0) else $fatal(1, "TX_Q_DEPTH must be positive");
    assert (REPLAY_DEPTH >= TX_Q_DEPTH)
      else $fatal(1, "replay must hold at least the TX queue's worth (Section 18)");
    assert (CREDIT_INIT  > 0) else $fatal(1, "CREDIT_INIT must be positive");
    assert (NUM_LANES    > 0) else $fatal(1, "NUM_LANES must be positive");
  end
 
  // --- Datapath blocks ---------------------------------------------------
  ucie_tx_ingress   #(.DEPTH(TX_Q_DEPTH))    u_tx_ingress   (...);
  ucie_credit_mgr   #(.NUM_CLASSES(NUM_CLASSES), .INIT(CREDIT_INIT)) u_credit (...);
  ucie_adapter_tx                            u_adapter_tx   (...);
  ucie_replay       #(.DEPTH(REPLAY_DEPTH))  u_replay       (...);
  ucie_rx_ingress                            u_rx_ingress   (...);
  ucie_rx_egress    #(.DEPTH(RX_Q_DEPTH))    u_rx_egress    (...);
 
  // --- Control blocks — beside the datapath (Section 4) -------------------
  ucie_link_mgr                              u_link_mgr     (...);
  ucie_training_ctrl                         u_training     (...);
  ucie_lane_mgr     #(.NUM_LANES(NUM_LANES)) u_lane_mgr     (...);
  ucie_cfg                                   u_cfg          (...);
 
  // --- Diagnostics — a reset domain of their own (Sections 8, 39) ---------
  ucie_fault_mgr                             u_fault_mgr    (...);
  ucie_perf                                  u_perf         (...);
 
endmodule

Architecture. Twelve instances, three clock inputs, three reset inputs, and an elaboration check that encodes one real design rule: the replay buffer must be at least as deep as the TX queue, or the queue can accept objects the replay path cannot retain (§18).

State. None at this level — the top is structural.

Cycle behaviour. Nothing. A top level that contains logic is a top level whose boundaries were not decided, and the discipline of keeping it structural is what makes the block list reviewable.

Contract. Each block's clock and reset are explicit at the port list. A reader can see, without opening a block, which domain it lives in — which is what makes §45's CDC analysis possible from the top.

Failure. One clk and one rst_n for everything, which forces every CDC decision inside the blocks where nobody reviews them, and makes §9's single-reset bug the path of least resistance.

DV. Elaboration checks; a connectivity check that each block's reset comes from the intended domain.

7. The State-Ownership Table

The chapter's backbone. Every subsequent section refers to a row of it.

StateOwner blockIndexed byAllocated whenReleased whenSurvives recovery?Survives a deliberate reset?
Semantic request/obligationthe protocol clientsemantic identitythe client issues itthe protocol's completion pointyespolicy-defined
TX queue entryucie_tx_ingressqueue sloton tx_valid && tx_readywhen the object is owned by replay (§17)yes — heldno
TX pipeline registersucie_adapter_txstageon stage advanceon the next advanceflushed or held, by policyno
Replay entryucie_replayreplay sequencewhen the Adapter accepts the objecton acknowledgement (14.3 §14)re-baselined with the peerno
Attempt countucie_replayreplay sequencewith the entrywith the entrysurvives — it is evidenceno
Creditucie_credit_mgrclassat advertisementconsumed on transfer; returned on releasere-advertisedno
Duplicate historyucie_rx_ingresstransport sequenceon deliveryafter the quarantine windowre-baselinedno
RX queue entryucie_rx_egressqueue sloton validated deliveryon consumptionyes — heldno
Active configurationucie_cfgat a commitat the next commitre-negotiatedre-established
Requested configurationucie_cfgon a writeat a commityesno
Configuration epochucie_cfgwith the active configwith the active configadvancesresets
Lane map / repair stateucie_lane_mgrlaneat training or repairat the next trainingre-establishedre-established
Link-manager stateucie_link_mgrat resetnevertransitions, not clearedresets
Training stateucie_training_ctrlat training entryat training exitre-runresets
First-fault recorducie_fault_mgrat the first errordeliberate clear onlyyes — this is the pointshould survive
Performance countersucie_perfvariousat resetdeliberate clear onlyyesshould survive

Three readings, and each is a section.

Look at the "survives recovery" column. Almost every row says yes, held, or re-established — and only the transport rows are rebuilt. That column is 14.2 §4's rule expressed as an implementation requirement, and §9 is the design that turns the whole column into "no".

Look at the last two rows. Diagnostics survive a deliberate reset in a correct design, because state cleared by the event you are diagnosing has no evidentiary value (14.5). That is a reset-domain decision made once, at the top level, and easy to omit.

And look at rows 2 and 4 together. The TX queue entry is released when replay owns the object, not when the PHY sends it. That handoff is §17, and it is the only deliberate dual-ownership window in the design.

8. The Reset Hierarchy

ScopeTriggerClears
Power-on resetpower sequencingeverything, including diagnostics
Deliberate soft reseta system-level actionmost state; diagnostics should survive (§7)
Protocol flusha policy decision, if the architecture permitssemantic queues at the protocol boundary
Adapter recoverya recoverable transport faultreliability and link state — not semantics
PHY retraina training requesttraining and lane state
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE reset and recovery controls. FIVE separate signals, because
// there are five scopes — and Section 9 is the design that has one.
logic por_n;                 // power-on: everything
logic soft_reset;            // deliberate, system-level
logic protocol_flush;        // semantic queues, by policy only
logic adapter_soft_reset;    // reliability/link state — NOT a reset of semantics
logic phy_retrain;           // training and lane state

A link recovery drives adapter_soft_reset and phy_retrain. It drives neither protocol_flush nor soft_reset, and it never reaches the fault manager or the performance counters. Every arrow in that sentence is a decision, and §9 is what happens when they are collapsed.

9. Wrong Top-Level Architecture — One Recovery Reset

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — one reset, driven by everything.
assign reset_all = !rst_n || link_recovery;
 
// ...and everything uses it:
ucie_tx_ingress   u_tx  (.rst_n(!reset_all), ...);
ucie_replay       u_rep (.rst_n(!reset_all), ...);
ucie_credit_mgr   u_cr  (.rst_n(!reset_all), ...);
ucie_fault_mgr    u_fm  (.rst_n(!reset_all), ...);   // ← the worst one
ucie_perf         u_pf  (.rst_n(!reset_all), ...);

Walk §7's table with this line in place.

StateShould be, on recoveryActually
TX queue entriesheldcleared — accepted objects vanish
Replay entriesre-baselined with the peercleared — nothing can be retransmitted
Creditsre-advertisedcleared — and re-advertised, so this row survives by luck
RX queue entriesheldcleared — validated objects discarded
First-fault recordpreservedcleared — the cause is erased
Performance counterspreservedcleared — the history is erased

Five properties, and this is the chapter's flagship failure.

Accepted objects vanish. The protocol client handed them over; the TX queue accepted them; now they are gone with no error and no record — which is 10.1 §10's rule violated at the top level.

The link comes back and reports success. Training completes, the link reaches its active state, link_active asserts. Every link-level metric says the recovery worked.

The semantic layer is left with obligations that will never complete. Its outstanding table still holds them; nothing will ever respond; and it will eventually time out and face 12.4 §16's ambiguity with no evidence at all.

And the evidence of the cause is gone. The first-fault record — the one structure whose entire purpose is to survive the event (14.5) — was cleared by the event. Post-silicon sees a timeout and nothing else, and the same failure recurs indefinitely because nobody can diagnose it.

The line looks like careful engineering. reset_all reads as thorough, conservative, safe. It is the single most destructive line in this chapter, and it is one expression.

10. SVA — Semantic and Diagnostic State Survive a Recovery

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The property that makes Section 9 impossible.
property p_recovery_preserves_semantics;
  @(posedge adapter_clk) disable iff (!por_n)
    link_recovery_entered |=> ($stable(tx_q_occupancy)
                            && $stable(rx_q_occupancy)
                            && $stable(first_fault_q)
                            && $stable(perf_counters_q));
endproperty
a_recovery_preserves_semantics:
  assert property (p_recovery_preserves_semantics);
 
// A recovery never asserts a semantic flush or a soft reset.
property p_recovery_is_not_a_reset;
  @(posedge adapter_clk) disable iff (!por_n)
    link_recovery_entered |-> (!protocol_flush && !soft_reset);
endproperty
a_recovery_is_not_a_reset: assert property (p_recovery_is_not_a_reset);
 
// The fault record survives everything except power-on.
property p_first_fault_survives_all_but_por;
  @(posedge adapter_clk) disable iff (!por_n)
    (link_recovery_entered || adapter_soft_reset || phy_retrain || soft_reset)
      |=> $stable(first_fault_q);
endproperty
a_first_fault_survives_all_but_por:
  assert property (p_first_fault_survives_all_but_por);

Architecture. Three properties: preservation, scope separation, and evidence.

Why every one is disable iff (!por_n) and not the local reset. The properties are about surviving those resets. Disabling them on the reset they are checking makes them vacuous — the most common way a survival property silently does nothing.

Why the second is a negative property. It forbids a specific wiring — a recovery signal reaching a reset — which is exactly §9's line, and it fails immediately rather than waiting for a symptom.

DV. Inject a recovery with a full TX queue, a full RX queue, live replay entries and a captured fault. All three must hold.

11. The Transmit Path, Walked

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The protocol client offers an object.        (§12 — admission)
2. tx_ingress accepts it into the TX queue.     -> the QUEUE owns it
3. The credit manager gates progress.           (§20)
4. adapter_tx frames it; CRC is generated.      (§3 — where applicable)
5. THE REPLAY BLOCK TAKES OWNERSHIP.            (§17 — the dual window opens)
6. The TX queue entry is released.              (§17 — the window closes)
7. The object crosses RDI to the PHY.
8. The lanes transmit it.                       -> a physical ATTEMPT
9. An acknowledgement resolves the replay entry. (§16)

Two things to notice before any RTL.

Ownership changes hands exactly once in that list, at steps 5 and 6 — and those are two steps rather than one because the handoff is not instantaneous (§17).

And the object is never unowned. At every cycle it is held by the queue, by both, or by replay. §18 is the design where a cycle exists in which neither holds it.

12. The TX Ingress Queue

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE TX ingress. Production-style: one owner per counter, explicit
// simultaneous handling, payload RAM not reset.
typedef struct packed {
  logic [PROTO_W-1:0]                 payload;
  logic [$clog2(NUM_CLASSES)-1:0]     tclass;
  logic [SEM_ID_W-1:0]                sem_id;      // the client's identity
  logic [CFG_EPOCH_W-1:0]             cfg_epoch;   // Section 42
} tx_entry_t;
 
tx_entry_t        tx_mem [TX_Q_DEPTH];             // inferred RAM, NOT reset
logic [PTR_W-1:0] tx_wr_q, tx_rd_q;
logic [OCC_W-1:0] tx_occ_q;
 
wire tx_full  = (tx_occ_q == TX_Q_DEPTH[OCC_W-1:0]);
wire tx_empty = (tx_occ_q == '0);
 
// Admission is a conjunction — every downstream stage that must hold it.
assign tx_ready = !tx_full
               && credit_available[tx_class]        // Section 20
               && replay_space_available            // Section 18 — CRITICAL
               && link_admits_new_traffic           // Section 32
               && cfg_stable;                       // Section 43
 
always_ff @(posedge adapter_clk or negedge adapter_rst_n)
  if (!adapter_rst_n) begin
    tx_wr_q  <= '0;
    tx_rd_q  <= '0;
    tx_occ_q <= '0;
  end else begin
    if (tx_push_fire) tx_wr_q <= tx_wr_q + 1'b1;
    if (tx_pop_fire)  tx_rd_q <= tx_rd_q + 1'b1;
 
    // ONE owner, all four combinations enumerated (Section 13 is the alternative).
    unique case ({tx_push_fire, tx_pop_fire})
      2'b10: tx_occ_q <= tx_occ_q + 1'b1;
      2'b01: tx_occ_q <= tx_occ_q - 1'b1;
      default: ;                                    // 2'b00 and 2'b11 hold
    endcase
  end
 
always_ff @(posedge adapter_clk)
  if (tx_push_fire) tx_mem[tx_wr_q] <= tx_push_data;
 
assign tx_head = tx_mem[tx_rd_q];

Architecture. A standard queue with a five-term admission conjunction. The third term is the one that matters most: replay_space_available must be checked at admission, not at framing — accepting an object the replay path cannot retain makes it unretransmittable (§18), and by then it is too late to refuse.

State. TX_Q_DEPTH payload entries plus three small registers. The payload is inferred RAM and deliberately not reset — occupancy starts at zero and gates every pop, so contents are unobservable before they are written (17.4 §14). A reset loop here would prevent RAM inference.

Cycle behaviour. Push and pop are handshake-qualified. The unique case handles the simultaneous cycle in one place, and §13 is what two independent statements do.

Contract. The Adapter reads tx_head and relies on it being stable while it is not popped (§14). The protocol client relies on tx_ready being a complete answer.

Failure. §13 for the counter, §18 for the admission term. Also making tx_ready equal !tx_full alone — which accepts objects there are no credits for, no replay space for, and no link to carry.

DV. §14's properties; cover empty, mid, full, and the simultaneous push-pop cycle.

13. Wrong RTL — Independent Count Assignments

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — two independent statements for one counter.
always_ff @(posedge adapter_clk) begin
  if (tx_push_fire) tx_occ_q <= tx_occ_q + 1'b1;
  if (tx_pop_fire)  tx_occ_q <= tx_occ_q - 1'b1;    // ← the second wins on a tie
end

On a simultaneous push and pop, the second assignment overwrites the first, so the count decrements when it should hold.

CyclePushPopShould beActually
nhold at 87
n+1hold at 86
n+275

Four properties.

The drift is monotonic in one direction. Every simultaneous cycle loses one, and the count trends downward — so the queue eventually reports space it does not have and overwrites a live entry.

It is invisible at low load. Simultaneous push and pop requires the producer and the consumer to be active in the same cycle, which happens only when the queue is being kept partly full — i.e. at exactly the throughput the design was built for.

Pointers and occupancy diverge. The pointers are correct because they are independently maintained; only the derived occupancy is wrong, so a check comparing the two catches it and a check on either alone does not.

And the fix has no cost. One unique case with four arms is the same logic, written so that the tie is resolved deliberately. The two-statement form is not simpler; it is merely shorter.

14. SVA — Queue Safety

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. No overflow, no underflow, and the head is stable while stalled.
property p_no_overflow;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    tx_push_fire |-> !tx_full;
endproperty
a_no_overflow: assert property (p_no_overflow);
 
property p_no_underflow;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    tx_pop_fire |-> !tx_empty;
endproperty
a_no_underflow: assert property (p_no_underflow);
 
// The offered head does not change while the consumer is not taking it.
property p_head_stable_under_stall;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (!tx_empty && !tx_pop_fire) |=> $stable(tx_head);
endproperty
a_head_stable_under_stall: assert property (p_head_stable_under_stall);
 
// Occupancy equals the pointer difference — this is what catches Section 13.
property p_occupancy_matches_pointers;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (tx_occ_q == ptr_diff(tx_wr_q, tx_rd_q));
endproperty
a_occupancy_matches_pointers: assert property (p_occupancy_matches_pointers);
 
// A simultaneous push and pop leaves occupancy unchanged.
property p_simultaneous_holds;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (tx_push_fire && tx_pop_fire) |=> (tx_occ_q == $past(tx_occ_q));
endproperty
a_simultaneous_holds: assert property (p_simultaneous_holds);

Architecture. Five properties, and the fourth is the one worth emphasising.

Why occupancy-against-pointers catches §13 and bounds do not. The drift stays within [0, DEPTH] for a long time before it breaches anything. The pointer comparison diverges on the very first simultaneous cycle, which is the difference between finding the bug in the first minute of a soak and finding it in the field.

DV. All five always-on. The fifth requires the simultaneous cycle to occur, which is a coverage bin (§53) and not a certainty.

15. The Replay Block at Top Level

14.3 teaches replay in full — three pointers, the retirement point, duplicate suppression, the retry FSM. What the top level adds is where it sits and what it owns:

QuestionAnswer at top level
When does replay take ownership?when the Adapter accepts the object for transmission (§17)
When does it release?on acknowledgement (14.3 §14) — never on PHY send
What backpressures the TX queue?replay_space_available, at admission (§12)
What survives a recovery?the entries are re-baselined with the peer, and the attempt counts survive as evidence (§7)
What must never happen?a cycle in which neither the queue nor replay owns the object (§18)

16. The Dual-Ownership Window

Between the TX queue and the replay block there is a deliberate window in which both own the object. It opens when replay commits the entry and closes when the queue releases it — and the window exists because the handoff takes at least one cycle.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. The queue releases only after replay CONFIRMS it holds the
// object. Both own it in between, and that is correct.
logic replay_commit;        // replay has written the entry and it is durable
logic tx_release;           // the queue may now free its slot
 
assign replay_commit = replay_alloc_fire && replay_entry_written;
assign tx_release    = replay_commit;                 // NOT phy_tx_fire
assign tx_pop_fire   = tx_release;

Architecture. The release is gated on the replay block's confirmation, not on the Adapter accepting or on the PHY sending. That is the ordering constraint, and it is the only reason the window is deliberate rather than accidental.

Cycle behaviour.

CycleTX queue holds itReplay holds itSafe?
n✓ — the queue can re-offer
n+1✓ — the deliberate window
n+2✓ — replay can retransmit
(the bug)✗ — §18

Contract. The queue 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 §19 asserts the conjunction.

Failure. §18.

DV. §19's ownership property, and coverage of the window occurring at all.

17. Wrong Handoff — the Queue Pops Before Replay Commits

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the TX queue frees its entry when the Adapter accepts, and replay
// allocates a cycle later.
assign tx_pop_fire = adapter_accept_fire;      // ← one cycle too early
// ... replay_alloc_fire occurs on the following cycle
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Cycle n   : the queue pops. The queue no longer holds the object.
Cycle n   : replay has NOT yet allocated. Replay does not hold it either.
Cycle n   : an error occurs, or a recovery is triggered.
            -> NO STRUCTURE HOLDS THE OBJECT.
            -> it cannot be retransmitted, and it cannot be re-offered.
            -> the object is lost, silently.

Four properties.

The window is one cycle wide, so it needs an error in exactly that cycle. The probability is low and the consequence is unbounded — a lost object with no record.

Nothing detects it. The queue's accounting is correct; replay's accounting is correct; the object simply is not in either. No overflow, no underflow, no CRC failure, no assertion of the ordinary kind.

It is a classic timing-window bug and it will pass every directed test. Only an ownership property — checking that every live object is held by at least one structure at every cycle — catches it (§19).

And the fix is one signal. Gate the pop on replay's confirmation rather than on the Adapter's acceptance. The cost is one cycle of extra queue occupancy, which is what the elaboration check in §6 sizes for.

18. SVA — Every Object Is Always Owned

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The property that catches Section 17. Uses a verification
// reference model, because "which object is this" is testbench knowledge.
//
// For each object the testbench is tracking, at least one structure holds it.
property p_object_always_owned(int unsigned rid);
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    tb_object_live(rid) |-> (tb_in_tx_queue(rid) || tb_in_replay(rid));
endproperty
a_object_always_owned: assert property (p_object_always_owned(REF_UT));
 
// The queue releases only after replay confirms.
property p_release_after_replay_commit;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    tx_pop_fire |-> replay_commit;
endproperty
a_release_after_replay_commit:
  assert property (p_release_after_replay_commit);
 
// Replay never allocates without space — the admission term was real.
property p_replay_alloc_has_space;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    replay_alloc_fire |-> !replay_full;
endproperty
a_replay_alloc_has_space: assert property (p_replay_alloc_has_space);
 
// The replay entry is not released at PHY send (14.3 Section 15, at top level).
property p_replay_not_freed_on_phy_send;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (phy_tx_fire && !replay_ack_fire) |=> $stable(replay_occupancy);
endproperty
a_replay_not_freed_on_phy_send:
  assert property (p_replay_not_freed_on_phy_send);

Architecture. Four properties: universal ownership, the handoff order, replay admission, and the retirement point.

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

Why the fourth restates a Module 14 property here. It is the same rule, and at top level it is a connectivity claim: that phy_tx_fire is not wired to the replay release. That is exactly the kind of mistake integration introduces and a block-level testbench cannot see.

DV. The first needs an error injected in the one-cycle window, which is §53's cp_error_in_handoff bin.

19. The Credit Manager

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Per class, one owner, simultaneous consume-and-return explicit.
// No normative class count is claimed (Section 3).
logic [CREDIT_W-1:0] tx_credit_q [NUM_CLASSES];
 
always_ff @(posedge adapter_clk or negedge adapter_rst_n)
  if (!adapter_rst_n) begin
    for (int c = 0; c < NUM_CLASSES; c++) tx_credit_q[c] <= CREDIT_INIT[CREDIT_W-1:0];
  end else begin
    for (int c = 0; c < NUM_CLASSES; c++)
      unique case ({consume_fire && (consume_class == c[$clog2(NUM_CLASSES)-1:0]),
                    return_fire  && (return_class  == c[$clog2(NUM_CLASSES)-1:0])})
        2'b10: tx_credit_q[c] <= tx_credit_q[c] - 1'b1;
        2'b01: tx_credit_q[c] <= tx_credit_q[c] + 1'b1;
        default: ;                                     // both or neither: hold
      endcase
  end
 
assign credit_available[c] = (tx_credit_q[c] != '0);

Architecture. One counter per class with one owner. Consumption is on an actual transfer, not on a grant or an admission — an object admitted and then held has not consumed a downstream slot.

State. NUM_CLASSES counters, initialised to what the peer advertised. CREDIT_INIT must equal the peer's actual capacity; advertising more overflows it and advertising fewer wastes it, and only conservation (§20) detects either.

Cycle behaviour. Returns arrive from the peer and may be delayed arbitrarily — including across a clock boundary, which is §47's hazard.

Contract. tx_ready depends on this (§12). The entire flow-control guarantee rests on the counter being exactly right.

Failure. Two independent statements (§13's shape, at a different counter). Or consuming on tx_ready && tx_valid at the protocol boundary rather than on the actual transfer to the peer — which consumes a credit for an object still sitting in the TX queue.

DV. §20.

20. SVA — Credit Conservation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Conservation, not just bounds — bounds miss a slow leak (18.4 §21).
property p_credit_conserved(int c);
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (tx_credit_q[c] + tb_outstanding_at_peer(c) == CREDIT_INIT);
endproperty
a_credit_conserved: assert property (p_credit_conserved(CLS_UT));
 
// Two-sided bounds — the upper one catches a mis-indexed return.
property p_credit_bounded(int c);
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (tx_credit_q[c] <= CREDIT_INIT);
endproperty
a_credit_bounded: assert property (p_credit_bounded(CLS_UT));
 
// No transfer without a credit.
property p_no_transfer_without_credit;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    consume_fire |-> ($past(tx_credit_q[consume_class]) != '0);
endproperty
a_no_transfer_without_credit: assert property (p_no_transfer_without_credit);

Architecture. Conservation, two-sided bounds, and the flow-control guarantee.

Why conservation rather than bounds alone. A lost credit return (18.4 §20) drifts the count downward inside its legal range, and the link stops permanently after a long run with no bound ever breached. Conservation fires on the first lost return.

DV. Run long enough to drain a class; inject a dropped credit return across the CDC (§47).

21. The Receive Path, Walked

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The PHY delivers received data across RDI.
2. rx_ingress aligns and reconstructs the object.
3. Integrity is checked.                             (§3 — where applicable)
4. The duplicate history is consulted.               ([14.1 §26](/protocols/ucie/ucie-error-detection))
5. THE DELIVERY GATE evaluates.                      (§23)
6. Only then is the object placed in the RX queue.   -> semantic delivery
7. The consumer takes it.                            -> the entry is released

The critical ordering is that step 5 precedes step 6, and §22 is the design where it does not.

22. Wrong RX Architecture — Deliver Before the Verdict

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the object is pushed into the RX queue as it is reconstructed, and
// the checks run afterwards.
always_ff @(posedge adapter_clk)
  if (rx_object_complete) begin
    rx_push(rx_reconstructed);              // ← delivered
    crc_check_start(rx_reconstructed);      // ← checked
  end

Two independent failures.

A corrupted object is delivered and then found to be corrupt. The consumer may already have acted on it. 14.1 §20's rule — check, then act, never the reverse — violated at the top level.

And a retransmitted object is delivered twice. A retry re-sends the same object; without the history check before the push, the consumer sees two semantic deliveries of one object — which for anything with an effect is a duplicate action (14.3 §22).

The object isCorrect outcomeWith this design
valid and newdelivered oncedelivered once
corruptdiscarded; replay requesteddelivered, then flagged
a duplicatesuppressed; the reply re-sentdelivered twice

Three of the four combinations of validity and novelty are handled wrongly, and the transport is behaving exactly as specified in each case.

23. The Delivery Gate

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. A conjunction of named terms between reconstruction and the
// RX queue. Combinational into the push — Section 22 is the registered version.
assign rx_semantic_deliver =
      rx_object_complete        // the whole object arrived
   && rx_integrity_ok           // the checks passed (Section 3)
   && !rx_duplicate             // history says this is not a repeat
   && rx_epoch_current          // not from a dead link epoch (Section 42)
   && rx_q_space;               // and there is somewhere to put it
 
assign rx_push_fire = rx_semantic_deliver;

Architecture. Five named terms. Naming them separately is the diagnostic value: a non-delivery is immediately attributable to one of five causes rather than being "the RX path dropped it".

Cycle behaviour. Combinational into the push. A registered gate delivers one object before deciding not to (14.1 §20) — the object is in the queue and the consumer may already have taken it.

Contract. Everything above the RX queue assumes exactly-once semantic delivery. That assumption has no signal at the interface, which is why §24 asserts it.

Failure. §22. Also omitting rx_epoch_current, which admits an object from before a recovery into a queue drained after it.

DV. Force each term false alone and confirm no push — five directed tests, which catch the "counted but not gated" case no random regression finds.

24. SVA — Semantic Delivery At Most Once

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 adapter_clk) if (rx_push_fire)
  tb_deliveries[tb_ref_id_of(rx_object)]++;
 
property p_delivery_at_most_once(int unsigned rid);
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (tb_deliveries[rid] <= 1);
endproperty
a_delivery_at_most_once: assert property (p_delivery_at_most_once(REF_UT));
 
// A duplicate attempt pushes nothing.
property p_duplicate_pushes_nothing;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (rx_object_complete && rx_duplicate) |-> !rx_push_fire;
endproperty
a_duplicate_pushes_nothing: assert property (p_duplicate_pushes_nothing);
 
// A failed integrity check pushes nothing.
property p_corrupt_pushes_nothing;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (rx_object_complete && !rx_integrity_ok) |-> !rx_push_fire;
endproperty
a_corrupt_pushes_nothing: assert property (p_corrupt_pushes_nothing);
 
// And the reply is still sent for a recognised duplicate (14.3 Section 22).
property p_duplicate_still_acknowledged;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (rx_object_complete && rx_duplicate) |-> ##[1:ACK_BOUND] rx_ack_sent;
endproperty
a_duplicate_still_acknowledged:
  assert property (p_duplicate_still_acknowledged);

Architecture. Four properties: exactly-once, and the three specific gate conditions.

Why the fourth is not optional. Suppressing the effect and also suppressing the reply leaves the peer retransmitting forever (14.3 §22). A design that gets duplicate suppression right and acknowledgement wrong has replaced a corruption with a livelock.

DV. The first needs a retry that actually occurs while traffic is live (§53).

25. Metadata Travels With Its Payload

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. One packed bundle through every pipeline stage — the fourth
// appearance of this rule in the curriculum (16.4 §20, 17.4 §35, 18.2 §41).
typedef struct packed {
  logic [PROTO_W-1:0]                payload;
  logic [$clog2(NUM_CLASSES)-1:0]    tclass;
  logic [SEM_ID_W-1:0]               sem_id;
  logic [CFG_EPOCH_W-1:0]            cfg_epoch;
  logic                              valid;
  logic                              error;
} link_bundle_t;
 
link_bundle_t stage_q [PIPE_DEPTH];
 
always_ff @(posedge adapter_clk)
  if (stage_advance)
    for (int s = PIPE_DEPTH-1; s > 0; s--)
      stage_q[s] <= stage_q[s-1];

Architecture. One packed object shifted as a unit. valid and error are inside the bundle — not beside it — because a validity or error flag that advances at a different rate attaches to the wrong payload.

State. PIPE_DEPTH copies. More area than a narrow tag beside a wide bus, and it buys a structural guarantee.

Contract. Every stage associates a payload with a class, an identity and a configuration epoch. If the association can be wrong, every downstream decision is about the wrong object — with the payload perfectly intact.

Failure. §26.

DV. §27, plus an end-to-end check comparing metadata recovered at the far end against what was assigned at acceptance.

26. Wrong RTL — Separate Valid and Payload Pipelines

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the valid/metadata path is one stage shorter than the payload path.
always_ff @(posedge adapter_clk) begin
  meta_q1 <= meta_in;   meta_q2 <= meta_q1;                        // 2 stages
  data_q1 <= data_in;   data_q2 <= data_q1;   data_q3 <= data_q2;  // 3 stages
end
assign out_meta = meta_q2;
assign out_data = data_q3;

Every object after the first carries the previous object's metadata.

CyclePayloadMetadata appliedResult
nD0M0✓ by luck — the first
n+1D1M0✗ wrong class, wrong identity, wrong epoch
n+2D2M1

Four properties.

The payload is perfect and the transport is perfect. Every bit crossed correctly. It is being interpreted as belonging to a different object.

A valid bit in the shorter path is the worst case, because an object can be marked valid when it is not, or invalid when it is — which either delivers garbage or silently drops a good object.

It is systematic and off by exactly one, so every waveform cycle looks reasonable and the pattern is invisible unless you look at two consecutive objects.

And it needs at least two back-to-back objects with different metadata to detecta trivial test that a single-object smoke test does not contain (§27, §53).

27. SVA — the Bundle Is Stable and Aligned

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. An offered bundle does not change while it is waiting.
property p_bundle_stable_under_stall;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (out_valid && !out_ready) |=> (out_valid && $stable(out_bundle));
endproperty
a_bundle_stable_under_stall: assert property (p_bundle_stable_under_stall);
 
// Metadata and payload advance together.
property p_meta_payload_together;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    $changed(out_bundle.payload) |-> $changed(out_bundle.sem_id) || payload_repeats;
endproperty
 
// The identity recovered at the far end equals the one assigned at acceptance.
property p_identity_survives_the_link(int unsigned rid);
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (rx_push_fire && (tb_ref_id == rid)) |-> (rx_bundle.sem_id == tb_sem_id_of(rid));
endproperty
a_identity_survives_the_link:
  assert property (p_identity_survives_the_link(REF_UT));

Architecture. Stability, joint movement, and the end-to-end identity check.

Why the third uses a testbench reference. The wire carries an identity field. The knowledge that "this payload was assigned identity X at acceptance" belongs to whatever generated the traffic — synthesising it would build a second copy of the same logic with the same bug.

DV. §26's off-by-one is caught on the second object of any stream.

28. Three State Machines, Not One

MachineOwnsIts state lives forIts inputs
Link manageroperational and recovery policythe link's operational lifeerrors, training results, requests
Training controllerthe physical training sequenceone training attemptPHY status, timers
Lane managerlane map, repair, width/rate configurationa configuration epochtraining results, error locations

Three lifetimes, three machines. The link manager decides whether to train; the training controller decides how; the lane manager decides with what. §31 shows what a single merged machine gets wrong, and it is not merely inelegant.

An illustrative seven-state link management controller. From RESET, completing initialisation moves to INIT. From INIT, starting training moves to TRAIN. From TRAIN, success moves to ACTIVE, where new traffic is admitted. From ACTIVE, a recoverable error moves to RECOVERY, and a fatal error moves to FAILED. From RECOVERY, a successful retrain returns to ACTIVE; a retrain that succeeds only at reduced capability moves to DEGRADED; and an exhausted attempt budget moves to FAILED. DEGRADED also admits traffic and can return to ACTIVE if full capability is restored, or move to RECOVERY on a further error. FAILED is terminal until a deliberate reset. Note that these are implementation control states rather than any specified link-state encoding.RESETINITTRAINACTIVERECOVERYDEGRADEDFAILEDreset donereset donestart trainingstarttrainingtrain oktrain okrecoverable errorrecoverable errorfatalfatalretrainedretrainedreduced capabilityreduced capabilitybudget spentbudget spentrestoredrestoredfurther errorfurther errortrain failedtrain failed
Illustrative implementation control states, not UCIe normative link-state encoding. From RESET the manager initialises and trains; ACTIVE admits traffic; a recoverable error enters RECOVERY, which retrains and returns to ACTIVE or settles into DEGRADED with reduced capacity; an exhausted attempt budget or a fatal error reaches FAILED.

Read the two admitting states. ACTIVE and DEGRADED both admit traffic — because a degraded link works, more slowly (14.4). A design with no DEGRADED state must classify a reduced-capability link as either fully healthy (and over-schedule it) or failed (and lose it).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. One next-state owner; outputs are state-derived or registered,
// never combinational from a raw error input (Section 31).
typedef enum logic [2:0] {
  LNK_RESET    = 3'd0,
  LNK_INIT     = 3'd1,
  LNK_TRAIN    = 3'd2,
  LNK_ACTIVE   = 3'd3,
  LNK_RECOVERY = 3'd4,
  LNK_DEGRADED = 3'd5,
  LNK_FAILED   = 3'd6
} link_state_e;
 
link_state_e link_state_q, link_state_d;
 
// Inputs are QUALIFIED and SYNCHRONISED before they reach the FSM (Section 31).
logic reset_done_s, train_done_s, train_fail_s;
logic recoverable_err_s, fatal_err_s, recovery_done_s, degrade_required_s;
logic [ATT_W-1:0] retrain_attempts_q;
 
always_comb begin
  link_state_d = link_state_q;
  unique case (link_state_q)
    LNK_RESET:    if (reset_done_s)                       link_state_d = LNK_INIT;
 
    LNK_INIT:     if (fatal_err_s)                        link_state_d = LNK_FAILED;
                  else                                    link_state_d = LNK_TRAIN;
 
    LNK_TRAIN:    if (fatal_err_s || train_fail_s)        link_state_d = LNK_FAILED;
                  else if (train_done_s)                  link_state_d = LNK_ACTIVE;
 
    LNK_ACTIVE:   if (fatal_err_s)                        link_state_d = LNK_FAILED;
                  else if (recoverable_err_s)             link_state_d = LNK_RECOVERY;
 
    LNK_RECOVERY: if (fatal_err_s)                        link_state_d = LNK_FAILED;
                  else if (retrain_attempts_q >= ATTEMPT_BUDGET)
                                                          link_state_d = LNK_FAILED;
                  else if (recovery_done_s && degrade_required_s)
                                                          link_state_d = LNK_DEGRADED;
                  else if (recovery_done_s)               link_state_d = LNK_ACTIVE;
 
    LNK_DEGRADED: if (fatal_err_s)                        link_state_d = LNK_FAILED;
                  else if (recoverable_err_s)             link_state_d = LNK_RECOVERY;
                  else if (full_capability_restored_s)    link_state_d = LNK_ACTIVE;
 
    LNK_FAILED:   if (soft_reset)                         link_state_d = LNK_RESET;
    default:                                              link_state_d = LNK_FAILED;
  endcase
end
 
always_ff @(posedge adapter_clk or negedge adapter_rst_n)
  if (!adapter_rst_n) link_state_q <= LNK_RESET;
  else                link_state_q <= link_state_d;
 
// Outputs are derived from the STATE, not from the raw events (Section 32).
assign link_admits_new_traffic = (link_state_q == LNK_ACTIVE)
                              || (link_state_q == LNK_DEGRADED);
assign start_training          = (link_state_q == LNK_TRAIN);
assign enter_recovery          = (link_state_q == LNK_RECOVERY);
assign notify_fault            = (link_state_q == LNK_FAILED);
assign link_active             = (link_state_q == LNK_ACTIVE);
assign link_degraded           = (link_state_q == LNK_DEGRADED);

Architecture. Seven states, one owner, one unique case. fatal_err_s is checked first in every state, so a fatal error cannot be overtaken by a completion in the same cycle — an explicit priority rather than an emergent one.

State. Three bits plus an attempt counter. The attempt budget is what makes FAILED reachable rather than looping forever (14.3 §37).

Cycle behaviour. All inputs are synchronised and qualified before they reach the FSM (§31). Outputs are pure functions of the current state.

Contract. ucie_tx_ingress reads link_admits_new_traffic; the training controller reads start_training; the fault manager reads notify_fault. Three consumers, three state-derived outputs, no overlap.

Failure. §31. Also making LNK_FAILED reachable only through the attempt budget and not through fatal_err_s, which leaves a fatal error retrying repeatedly.

DV. Cover every state and every legal transition; assert no illegal transition (12.4 §10).

31. Wrong FSM — Combinational Outputs From Raw Events

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — outputs derived combinationally from raw, unsynchronised inputs.
assign enter_recovery          = raw_error_from_phy;          // async, glitchy
assign link_admits_new_traffic = !raw_error_from_phy;         // ← the same signal

Three failures, and the third is the one that corrupts.

A glitch on raw_error_from_phy produces a one-cycle recovery entry and exit. Nothing completes; nothing is captured; the link twitches and continues, and the event is invisible afterwards.

A signal from another clock domain may be metastable (§45). Fanning it out combinationally to several consumers means they may resolve it differently in the same cycle — one block believing recovery is active and another believing it is not.

And the same raw signal drives admission. So during the glitch, admission is disabled for one cycle in one block and not in another, which can admit an object into a path that is simultaneously being told to quiesce. That is how an object is accepted and then lost.

The correct discipline is three rules:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Synchronise, qualify, then derive from state.
logic raw_err_meta_q, raw_err_sync_q;
always_ff @(posedge adapter_clk or negedge adapter_rst_n)
  if (!adapter_rst_n) begin
    raw_err_meta_q <= 1'b0;
    raw_err_sync_q <= 1'b0;
  end else begin
    raw_err_meta_q <= raw_error_from_phy;     // 1: synchronise
    raw_err_sync_q <= raw_err_meta_q;
  end
 
assign recoverable_err_s = raw_err_sync_q && link_error_qualified;   // 2: qualify
// 3: outputs come from link_state_q, never from recoverable_err_s (Section 30).

32. SVA — FSM Discipline

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Only legal transitions occur.
property p_legal_transitions;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    $changed(link_state_q) |-> is_legal_transition($past(link_state_q), link_state_q);
endproperty
a_legal_transitions: assert property (p_legal_transitions);
 
// New traffic is not admitted outside the admitting states.
property p_no_admission_outside_active;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    tx_push_fire |-> ((link_state_q == LNK_ACTIVE) || (link_state_q == LNK_DEGRADED));
endproperty
a_no_admission_outside_active:
  assert property (p_no_admission_outside_active);
 
// No admission during recovery — the specific case that matters (Section 37).
property p_no_admission_during_recovery;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (link_state_q == LNK_RECOVERY) |-> !tx_push_fire;
endproperty
a_no_admission_during_recovery:
  assert property (p_no_admission_during_recovery);
 
// LIVENESS: a recoverable link eventually reaches an admitting state.
//   A1: training completes or fails within a bound
//   A2: the error source eventually stops asserting
//   A3: the attempt budget is finite
property p_recovery_terminates;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (link_state_q == LNK_RECOVERY)
      |-> ##[1:RECOVERY_BOUND] ((link_state_q == LNK_ACTIVE)
                             || (link_state_q == LNK_DEGRADED)
                             || (link_state_q == LNK_FAILED));
endproperty
a_recovery_terminates: assert property (p_recovery_terminates);

Architecture. Three safety properties and one bounded liveness property.

Why the liveness consequent admits FAILED. The requirement is termination, not success (14.2 §30). A recovery that gives up explicitly is a correct outcome; one that loops forever is not, and only a property that accepts FAILED distinguishes them.

DV. Cover every state; inject a fatal error in each state; exhaust the attempt budget.

33. Recovery Entry, In Order

The ordering is the architecture, and §34 is what happens when it is not followed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. STOP new admission.            -> link_admits_new_traffic deasserts (§30)
2. PRESERVE semantic and replay obligations.  -> nothing is cleared (§7, §10)
3. CAPTURE the first fault.       -> BEFORE anything can overwrite it (§35)
4. QUIESCE as the architecture requires.
5. RETRAIN / RECONFIGURE.         -> the training controller runs
6. VALIDATE the result.
7. COMMIT the configuration and RESUME.       -> atomically (Section 36)

Two orderings carry the whole sequence.

Step 3 precedes step 5. Retraining rewrites PHY status, lane state and error registers. A capture after retraining captures the retraining, not the fault (§34).

And step 6 precedes step 7. The configuration is validated before it is committed, so a bad configuration is refused rather than discovered by the first object that uses it (14.2's recovery controller makes this precise).

34. Wrong Recovery Order — Reset Before Capture

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the retrain is started as soon as recovery is entered, and the fault
// is captured afterwards "when things settle".
always_ff @(posedge adapter_clk)
  if (link_state_q == LNK_RECOVERY) begin
    phy_retrain <= 1'b1;                    // ← step 5 before step 3
    if (retrain_done) capture_fault();      // ← by now the syndrome is gone
  end
What existed at the moment of the faultAfter the retrain
the failing lane's error statuscleared by training
the receive alignment statere-established
the error type and syndromeoverwritten
the configuration in forcereplaced
a timestamp or sequence positionusually still available

Three properties.

Post-silicon sees a timeout and a successful retrain, and nothing else. The link recovered; a transaction failed; and there is no information connecting the two.

The same failure then recurs indefinitely, because each occurrence destroys its own evidence. A design can spend months in this state.

And the fix costs a few cycles. Capturing a first-fault record before asserting phy_retrain delays the recovery by the capture latency — illustratively a handful of cycles against a retrain measured in thousands. The trade is not close.

35. The First-Fault Record

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. 14.5's model, at top level. The point here is its RESET DOMAIN.
typedef struct packed {
  logic                  valid;
  logic [ERR_W-1:0]      error_type;
  logic [LANE_W-1:0]     lane;
  logic [CFG_EPOCH_W-1:0] cfg_epoch;      // which configuration was in force
  logic [SEQ_W-1:0]      seq_at_fault;    // where in the stream
  logic [TS_W-1:0]       timestamp;
} first_fault_t;
 
first_fault_t first_fault_q;
 
// FIRST, not latest: the record is written once and held until deliberately cleared.
always_ff @(posedge adapter_clk or negedge por_n)          // <- POR only
  if (!por_n)
    first_fault_q <= '0;
  else if (fault_detected && !first_fault_q.valid)         // <- guarded: FIRST
    first_fault_q <= capture_fault_context();
  else if (fault_record_clear)                             // <- deliberate only
    first_fault_q <= '0;

Architecture. A write-once record with a deliberate clear. The guard !first_fault_q.valid is what makes it the first fault rather than the latest (14.5) — and the latest is almost always a consequence rather than a cause.

State. One record. Its reset is por_n, not adapter_rst_n — which is the entire architectural point of this section, and it is one line in a sensitivity list.

Cycle behaviour. Captured combinationally from the fault context at the moment of detection, before any retrain can begin (§33).

Contract. Every post-silicon investigation depends on it. Its value is entirely a function of surviving the event that produced it, which is why §10's third property exists.

Failure. Using adapter_rst_n in the sensitivity list, which is §9. Or omitting the !valid guard, which makes it a latest-fault register whose contents are the last symptom rather than the first cause.

DV. §10's third property; inject several faults and confirm the first is retained.

36. Requested and Active Configuration

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. 14.4's requested-versus-active discipline, as a top-level block.
typedef struct packed {
  logic [RATE_W-1:0]        rate;
  logic [WIDTH_W-1:0]       width;
  logic [NUM_LANES-1:0]     lane_enable;
  logic [MODE_W-1:0]        mode;
} link_cfg_t;
 
link_cfg_t              requested_cfg_q;    // software and training write here
link_cfg_t              active_cfg_q;       // the datapath reads here
logic [CFG_EPOCH_W-1:0] active_cfg_epoch_q;
 
assign cfg_commit_allowed =
      cfg_validated                         // the staged configuration is legal
   && (tx_q_occupancy == '0)                // the pipeline has drained
   && (replay_outstanding == '0)            // nothing awaits retransmission
   && (link_state_q == LNK_RECOVERY);       // only at a defined safe point
 
always_ff @(posedge adapter_clk or negedge adapter_rst_n)
  if (!adapter_rst_n) begin
    active_cfg_epoch_q <= '0;
  end else if (cfg_commit_fire) begin
    active_cfg_q       <= requested_cfg_q;         // atomic, whole structure
    active_cfg_epoch_q <= active_cfg_epoch_q + 1'b1;
  end
 
assign cfg_stable = !cfg_commit_pending;

Architecture. Two copies committed atomically, with a four-term commit guard. The third and fourth terms are the ones a block-level design forgets: nothing may await retransmission, because a replayed object framed under the old configuration would be transmitted under the new one; and the commit happens only at a defined safe point.

State. Two configuration structures plus an epoch.

Cycle behaviour. The whole structure transfers in one cycle. Not field by field — a partial commit is a window in which the rate and the width disagree, and an object transmitted in it is framed inconsistently.

Contract. The datapath, the lane manager and the PHY interface all read active_cfg_q. They must see one complete configuration or the previous one, never a mixture.

Failure. §37.

DV. §38's properties; cover a commit attempted with a non-empty TX queue and with outstanding replay entries.

37. Wrong RTL — Live Configuration Change

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — configuration fields are written directly, while traffic is live.
always_ff @(posedge adapter_clk)
  if (cfg_write) begin
    active_cfg_q.rate  <= cfg_wdata[RATE_FIELD];
    active_cfg_q.width <= cfg_wdata[WIDTH_FIELD];    // ← different cycle, live traffic
  end
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. An object is in the TX pipeline, framed under width = 16.
2. Software writes a new width of 8. active_cfg_q.width changes.
3. The object's remaining stages are processed under width = 8.
4. -> the object is framed inconsistently: its early stages assumed one
     configuration and its later stages another.
5. The far end reconstructs something that is not what was sent, and the
   CRC — computed over what WAS sent — fails, or worse, passes over a
   coincidentally-consistent result.

Four properties.

Different pipeline stages interpret the same object under different configurations, which is a class of corruption that no single stage can detect — each stage behaved correctly under the configuration it saw.

And a replayed object is framed under the new configuration while the peer expects the old — which is why the commit guard includes the replay-outstanding term (§36).

The window is small and the traffic is high, so the probability per write is low and the number of writes over a product's life is not.

The correct form is requested-then-committed (§36), and the commit guard is not optional: committing with a drained pipeline but outstanding replay entries produces exactly the same failure on the first retransmission.

38. SVA — Configuration Lifetime

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The active configuration changes only at a guarded commit.
property p_cfg_changes_only_at_commit;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    $changed(active_cfg_q) |-> $past(cfg_commit_fire);
endproperty
a_cfg_changes_only_at_commit: assert property (p_cfg_changes_only_at_commit);
 
// The commit requires a drained pipeline and no outstanding replay.
property p_commit_requires_quiesce;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    cfg_commit_fire |-> ((tx_q_occupancy == '0) && (replay_outstanding == '0));
endproperty
a_commit_requires_quiesce: assert property (p_commit_requires_quiesce);
 
// The epoch advances with the configuration — they can never disagree.
property p_epoch_tracks_cfg;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    cfg_commit_fire |=> (active_cfg_epoch_q == $past(active_cfg_epoch_q) + 1);
endproperty
a_epoch_tracks_cfg: assert property (p_epoch_tracks_cfg);
 
// An object's configuration epoch is stable for its whole life in the link.
property p_object_cfg_epoch_stable;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    tb_object_live(REF_UT) |-> $stable(tb_object_cfg_epoch(REF_UT));
endproperty
a_object_cfg_epoch_stable: assert property (p_object_cfg_epoch_stable);

Architecture. Four properties: commit-only changes, the guard, epoch coupling, and per-object stability.

Why the fourth exists given the first three. They protect the configuration; the fourth protects the object — it says no live object ever spans two epochs, which is §37's failure stated from the object's point of view rather than the register's.

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

BoundaryTypical reasonWhat crosses
protocol ↔ Adapterthe protocol client's clock differspayload streams, backpressure
Adapter ↔ PHYthe PHY's clock is derived from the link ratepayload streams, status, credit returns
What crossesCorrect bridgeWrong bridge
a payload streaman asynchronous FIFO (18.4 §18)a plain register
a level or statea two-flop synchronisera plain register
a one-cycle pulsea toggle or a handshakea synchroniser — the pulse can be missed
a multi-bit valueGray-coded, or a handshakeper-bit synchronisers
a credit returna Gray-coded count (§40)a pulse — §40
resetasynchronous assert, synchronous deasserta plain register

40. Wrong CDC — a Credit Return as a Pulse

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a one-cycle credit-return pulse crossed with a synchroniser.
always_ff @(posedge adapter_clk)
  cr_sync_q <= {cr_sync_q[0], phy_credit_return_pulse};
assign credit_return_fire = cr_sync_q[1];

If the Adapter clock is slower than the PHY clock, a pulse can fall entirely between two Adapter edges and is never sampled.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. Light traffic: returns are sparse, nearly all are sampled. Works perfectly.
2. Sustained traffic: returns are frequent, a fraction fall between edges
   and are LOST.
3. Every lost pulse is a credit that will never be returned.
4. tx_credit_q decreases monotonically.
5. -> after a long run it reaches zero and the link stops PERMANENTLY.
     No error, no assertion, no CRC failure. It simply stops.

Four properties, and this is the best bug in the chapter.

It is a leak, not a corruption. Nothing is ever wrong; there is progressively less capacity. The symptom is "the link worked for six hours and then stopped".

It is load-dependent in the direction that hides it — light traffic loses few pulses, so it survives every short test.

And §20's conservation assertion catches it on the very first lost pulse, while every bounds check stays silent to the end. That single property is the difference between finding it in simulation and finding it in a customer's rack.

The fix is to cross a state, not an event:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. A monotonic Gray-coded COUNT. A missed sample is recovered by
// the next one, because the destination takes the DIFFERENCE.
logic [CNT_W-1:0] cr_count_q;                 // PHY domain, monotonic
logic [CNT_W-1:0] cr_gray_q;
logic [CNT_W-1:0] cr_gray_sync_q [2];         // Adapter domain
logic [CNT_W-1:0] cr_seen_q;
 
always_ff @(posedge phy_clk)
  if (phy_credit_return) begin
    cr_count_q <= cr_count_q + 1'b1;
    cr_gray_q  <= bin2gray(cr_count_q + 1'b1);
  end
 
wire [CNT_W-1:0] cr_bin   = gray2bin(cr_gray_sync_q[1]);
wire [CNT_W-1:0] cr_delta = cr_bin - cr_seen_q;      // how many to add

41. TX and RX Are Independent

Full duplex means the two directions progress independently — and a design that couples them halves its throughput or deadlocks.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Separate readiness for each direction, with no shared term.
assign tx_ready = !tx_full && credit_available[tx_class] && replay_space_available
               && link_admits_new_traffic && cfg_stable;
 
assign rx_ready_to_phy = rx_q_space;     // depends ONLY on the receive path

Contract. The peer relies on the receive path accepting regardless of the transmit path's congestion — and vice versa. 12.2 §12 makes the general argument; at top level it is a wiring question.

42. Wrong RTL — a Shared Ready

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — one readiness signal for both directions.
assign link_ready = !tx_full && rx_q_space;
assign tx_ready        = link_ready;
assign rx_ready_to_phy = link_ready;      // ← RX now depends on TX congestion
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The RX queue fills because the local consumer is slow.
2. link_ready deasserts.
3. TX also stops — even though the transmit path has credits, replay
   space, and an operational link.
4. The peer, waiting for our transmissions, cannot make progress.
5. It therefore cannot consume what it has received, so it stops
   returning credits or acknowledgements.
6. -> our TX blocks harder, our RX stays full, and the link deadlocks
     with both directions idle.

Four properties.

At best it halves throughput, because either direction's congestion stops both.

At worst it deadlocks, and the cycle in step 6 is real: the peer's progress depends on our transmissions, and our transmissions depend on our receive path draining, which depends on the peer.

The signal name makes it look correct. link_ready reads as a property of the link. It is actually a conjunction of two unrelated conditions, and nothing about the name suggests that.

And it is a top-level integration bug. Both blocks are correct; the error is one shared wire, which is exactly the class of failure that block-level verification cannot see and this chapter exists to catch.

43. SVA — Direction Independence

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. RX readiness does not depend on TX state.
property p_rx_ready_independent_of_tx;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (rx_q_space && !link_recovery_active) |-> rx_ready_to_phy;
endproperty
a_rx_ready_independent_of_tx:
  assert property (p_rx_ready_independent_of_tx);
 
// TX readiness does not depend on RX occupancy.
property p_tx_ready_independent_of_rx;
  @(posedge adapter_clk) disable iff (!adapter_rst_n)
    (!tx_full && credit_available[tx_class] && replay_space_available
     && link_admits_new_traffic && cfg_stable) |-> tx_ready;
endproperty
a_tx_ready_independent_of_rx:
  assert property (p_tx_ready_independent_of_rx);

Architecture. Two properties, one per direction, each written as a complete implication: given the conditions that should enable readiness, readiness is enabled.

Why written this way rather than as a stability check. A stability property would pass on a design that never asserts readiness at all. These say "if the legitimate conditions hold, you must be ready" — which is what forbids an extra term from another direction sneaking in.

DV. Fill the RX queue and confirm tx_ready still asserts; fill the TX queue and confirm rx_ready_to_phy still asserts.

44. Performance Counters and the Stall Classifier

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Diagnostic only, and in the POR reset domain (Section 7).
logic [63:0] tx_payload_bytes_q;      // useful bytes transmitted
logic [63:0] tx_replay_bytes_q;       // bytes retransmitted (not useful)
logic [63:0] tx_credit_stall_q;
logic [63:0] rx_payload_bytes_q;
logic [63:0] rx_duplicate_q;          // suppressed duplicates (Section 23)
logic [63:0] recovery_cycles_q;
logic [63:0] retrain_count_q;
logic [63:0] degraded_cycles_q;
logic [63:0] cfg_commit_count_q;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. 15.5 Section 10's causal-priority classifier at TX ingress.
typedef enum logic [3:0] {
  TXS_TRANSFER    = 4'd0,
  TXS_SOURCE_EMPTY= 4'd1,   // nothing offered            <- must be early
  TXS_LINK_DOWN   = 4'd2,
  TXS_RECOVERY    = 4'd3,
  TXS_CFG_COMMIT  = 4'd4,   // a deliberate quiesce
  TXS_NO_QUEUE    = 4'd5,
  TXS_NO_CREDIT   = 4'd6,
  TXS_REPLAY_FULL = 4'd7,
  TXS_PHY_BLOCKED = 4'd8,
  TXS_UNATTRIB    = 4'd9    // must stay at zero
} tx_stall_e;
 
tx_stall_e tx_reason_d;
 
always_comb begin
  unique case (1'b1)
    tx_push_fire                          : tx_reason_d = TXS_TRANSFER;
    !tx_valid                             : tx_reason_d = TXS_SOURCE_EMPTY;
    (link_state_q == LNK_FAILED)          : tx_reason_d = TXS_LINK_DOWN;
    (link_state_q == LNK_RECOVERY)        : tx_reason_d = TXS_RECOVERY;
    !cfg_stable                           : tx_reason_d = TXS_CFG_COMMIT;
    tx_full                               : tx_reason_d = TXS_NO_QUEUE;
    !credit_available[tx_class]           : tx_reason_d = TXS_NO_CREDIT;
    !replay_space_available               : tx_reason_d = TXS_REPLAY_FULL;
    !phy_tx_ready                         : tx_reason_d = TXS_PHY_BLOCKED;
    default                               : tx_reason_d = TXS_UNATTRIB;
  endcase
end

Architecture. Ten mutually exclusive reasons in causal priority, one counter each, summing to elapsed cycles.

TXS_SOURCE_EMPTY is second so idle cycles never inflate a resource bin — the most common way a classifier lies (17.2 §32).

And TXS_UNATTRIB must stay at zero. A non-zero value means a stall cause exists that the design does not model — which tells you the instrument is incomplete, and that is the most valuable thing an instrument can tell you.

45. The Conservation Model

Four invariants that tie the layers together, and each is checkable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
SEMANTIC
  accepted semantic objects = completed + outstanding + explicitly failed
 
TRANSPORT
  physical attempts >= semantic deliveries          (retries add attempts, not deliveries)
  semantic deliveries per object <= 1               (Section 24)
 
BUFFERS
  queue occupancy = pushes - pops                   (Section 14)
 
CREDITS
  initial + returned - consumed = current           (Section 20)
 
OWNERSHIP
  every live object is in the TX queue, in replay, or in both   (Section 18)

Each invariant is owned by one layer and checkable by one scoreboard model (§46). A violation identifies the layer, which is what makes the four-layer structure worth its cost.

46. The Top-Level Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Verification-only. FOUR models, one per layer of Section 45's invariants.
class ucie_link_scoreboard;
 
  // ---- Layer 1: SEMANTIC model — what the client handed over and expects.
  typedef struct {
    int  sem_id;
    bit  accepted;
    int  semantic_deliveries;     // MUST be <= 1
    bit  completed;
    bit  explicitly_failed;
    bit  crossed_recovery;
  } semantic_model_t;
  semantic_model_t sem [int];
 
  // ---- Layer 2: ADAPTER / REPLAY model — objects and physical attempts.
  typedef struct {
    int  sem_id;
    int  attempts;                // >= 1
    bit  retained;                // replay holds it
    bit  in_tx_queue;             // the queue holds it
    bit  acknowledged;
  } transport_model_t;
  transport_model_t tr [int];
 
  // ---- Layer 3: RESOURCE model — credits and occupancies.
  typedef struct {
    int credits_available;
    int credits_outstanding;
    int tx_occupancy, rx_occupancy, replay_occupancy;
  } resource_model_t;
  resource_model_t res [int];     // per class where applicable
 
  // ---- Layer 4: LINK-MANAGEMENT model — state, config epoch, faults.
  typedef struct {
    int  link_state;
    int  cfg_epoch;
    bit  first_fault_valid;
    int  first_fault_type;
    int  retrain_count;
  } link_mgmt_model_t;
  link_mgmt_model_t mgmt;
 
  // ---- Catches Section 17 — an object owned by nothing.
  function void check_ownership(int id);
    if (!tr[id].in_tx_queue && !tr[id].retained && !tr[id].acknowledged)
      $error("OBJECT %0d owned by NOTHING (Section 17)", id);
  endfunction
 
  // ---- Catches Section 22 — duplicate or premature semantic delivery.
  function void check_delivery(int id);
    if (sem[id].semantic_deliveries > 1)
      $error("OBJECT %0d delivered %0d times (must be <= 1)", id, sem[id].semantic_deliveries);
    if (tr[id].attempts < sem[id].semantic_deliveries)
      $error("OBJECT %0d: %0d deliveries from %0d attempts", id,
             sem[id].semantic_deliveries, tr[id].attempts);
  endfunction
 
  // ---- Catches Sections 9 and 40 — state lost, or credits leaked.
  function void check_recovery_survival(int id);
    if (sem[id].crossed_recovery && !sem[id].accepted)
      $error("SEMANTIC OBJECT %0d lost across a recovery (Section 9)", id);
  endfunction
 
  function void check_credit_conservation(int cls);
    if (res[cls].credits_available + res[cls].credits_outstanding != CREDIT_INIT)
      $error("CREDITS NOT CONSERVED class %0d: %0d + %0d != %0d (Section 40)",
             cls, res[cls].credits_available, res[cls].credits_outstanding, CREDIT_INIT);
  endfunction
 
  // ---- Catches Section 34 — the fault record destroyed by the retrain.
  function void check_fault_preserved();
    if (mgmt.retrain_count > 0 && !mgmt.first_fault_valid)
      $error("A RETRAIN OCCURRED WITH NO FAULT RECORD (Section 34)");
  endfunction
 
endclass

Architecture. Four models: what the client expects, what physically happened, what resources are held, and what the link's management state is.

Which divergence identifies which subsystem is the payoff:

| Layer 1 diverges | a semantic failure — an obligation lost or completed wrongly | | Layer 2 diverges | an Adapter or replay failure — ownership, attempts, acknowledgement | | Layer 3 diverges | a flow-control or buffering failure — credits, occupancy | | Layer 4 diverges | a link-management failure — state, configuration, fault capture |

And Layer 2's in_tx_queue and retained are tracked separately rather than as one "somewhere in the link" flag — because §17's bug is precisely the cycle in which both are false.

47. Flagship Trace 1 — a Clean Transmission and Response

Illustrative. Cycle numbers illustrative.

CycTX queueCreditReplayPHYRX (peer side)Semantic
0080idle
1accepted80idleclient hands over
2180idlequeue owns it
318framingidle
418committedidledual-ownership window
5081idlequeue releases (§16)
6071attempt 1credit consumed on transfer
9071in flight
11071deliveredintegrity ok
12071gate: not duplicate
13071pushed — delivery ×1semantic delivery
1807retiredack receivedreplay releases
20080credit returned
30080response returns
34080semantic completion

Six readings.

Cycle 4 is the dual-ownership window — the queue and replay both hold it. §17's bug is the queue releasing at cycle 3, before replay committed.

Cycle 6 consumes the credit on the actual transfer, not on acceptance at cycle 1. Consuming at acceptance would credit an object still sitting in the queue.

Cycles 11 to 13 are the delivery gate — integrity, then duplicate, then push. §22's design pushes at cycle 11 and checks afterwards.

Cycle 18 retires the replay entry on the acknowledgement, not at cycle 6 when the PHY sent it (14.3 §14, and §18's fourth property).

Cycle 34 is the semantic completion — twenty-eight cycles after the replay entry was created and sixteen after it retired. Four candidate events, and the last one is correct.

And the object was owned at every cycle from 1 to 18. §46's ownership check holds throughout.

48. Flagship Trace 2 — a Credit Stall

CycTX headtx_credit_qtx_occ_qtx_reason_dHead stable?
40object A13TRANSFER
41object B03NO_CREDIT
45object B03NO_CREDIT
60object B03NO_CREDIT
61object B1 — returned3TRANSFER
62object C02NO_CREDIT

Four readings.

The head is stable for twenty cycles — §14's third property, and the reason the Adapter can sample it whenever it is ready.

The occupancy does not drift. No push, no pop, no simultaneous cycle — and §13's bug would not show here, which is why the simultaneous cycle needs its own coverage bin.

TXS_NO_CREDIT is charged for every stalled cycle, and TXS_PHY_BLOCKED for none. Two counters, two causes (§44): this was flow control, not the PHY.

And nothing about the receive path changed during the stall — §43's independence property, observed.

49. Flagship Trace 3 — a CRC Failure and a Replay

CycReplay entryPHYPeer RXSemantic deliveriesAttempts
6heldattempt 101
11helddeliveredintegrity FAIL01
12helddiscarded — gate blocks01
14heldNAK sent01
20heldattempt 2 — same object02
25helddeliveredintegrity ok2
26heldnot a duplicate — pushed12
32retiredack received12

Four readings.

Two attempts, one semantic delivery. §45's transport invariant, holding.

The replay entry is held from cycle 6 to cycle 32 — through the failure, the NAK and the retransmission. §18's fourth property is that it is not released at cycle 6 when the PHY sent it.

Cycle 12: the gate discards the corrupt object before it reaches the RX queue. §22's design pushes it and flags it afterwards, by which time the consumer may have taken it.

And cycle 26 pushes because the history says this is not a duplicate — the first attempt was never delivered, so the second is the first delivery. The history must distinguish "transmitted twice" from "delivered twice", which is exactly what 14.1 §26's history provides.

50. Flagship Trace 4 — Recovery With Live Transactions

The strongest trace in the chapter.

CycLink stateAdmitTX queueReplayFirst faultConfig epochSemantic
100ACTIVE4376 outstanding
104ACTIVE437error detected
105RECOVERY✗ stops4376 — unchanged
106RECOVERY43CAPTURED76
107RECOVERY43held7capture BEFORE retrain (§33)
110RECOVERY43held7quiesce — nothing cleared
115RECOVERY43held7retraining
150RECOVERY43held7trained, validating
158RECOVERY0 — drained0 — resolvedheld7commit conditions met
159RECOVERY00held8config committed atomically
160DEGRADED✓ resumes00held8x16 → x8
165DEGRADED22held8traffic resumes
210DEGRADED00held8all 6 completed

Seven readings, and this is what the whole chapter has been building toward.

Cycle 105: admission stops and nothing else changes. Six semantic obligations, four queue entries, three replay entries — all preserved. §9's design clears every one of them here.

Cycle 106: the fault is captured before the retrain begins at 115. §34's design retrains first and captures at 150, by which time the syndrome is gone.

Cycles 110 to 158: the queue and replay drain naturally, because admission stopped and the outstanding work resolved. The commit guard at 158 requires exactly that (§36).

Cycle 159: the configuration commits atomically, and the epoch advances with it. §37's design writes fields live during any of these cycles.

Cycle 160: the link returns as DEGRADED at half width. It admits traffic — a degraded link works (§29) — and every semantic obligation is unchanged.

Cycle 210: all six original obligations completed, on a link that recovered at reduced capability. Slower, not different.

And the first-fault record is still held at cycle 210, available to whoever asks. §9's design cleared it at cycle 105.

51. Flagship Trace 5 — Lane Degradation

Cycactive_cfg_q.widthEpochObjects in flightSemantic identityThroughput
3001685unchanged1.0×
3401685unchanged1.0× — lane fault detected
3451685unchangedrecovery entered, admission stops
3801680 — drainedunchanged
381890unchangedcommitted
385893unchanged~0.5×
450890unchanged~0.5×

Four readings.

No object spanned the configuration change — the pipeline drained at 380 and the commit was at 381. §38's fourth property, observed.

Not one semantic identity changed, and every obligation completed. The link is slower and not different (16.5 §26).

Throughput halves and degraded_cycles_q accumulates (§44) — so the performance change is reported as a performance change, not as a fault.

And any bound derived from the width must be rescaled at cycle 381 (16.5 §27), or the very next object is declared failed on a link that just recovered successfully.

52. The Assertion Inventory

#PropertySectionClass
1no TX queue overflow or underflow§14safety
2the head is stable under stall§14safety
3occupancy matches the pointer difference§14safety
4a simultaneous push and pop holds occupancy§14safety
5every live object is owned by the queue, replay, or both§18safety
6the queue releases only after replay commits§18safety
7replay is not released at PHY send§18safety
8credits are conserved and two-sided bounded§20safety
9no transfer without a credit§20safety
10semantic delivery at most once§24safety
11a duplicate pushes nothing but is still acknowledged§24safety + liveness
12a corrupt object pushes nothing§24safety
13the bundle is stable under stall§27safety
14identity survives the link§27safety
15only legal FSM transitions occur§32safety
16no admission outside the admitting states§32safety
17no admission during recovery§32safety
18recovery terminates, under assumptions§32liveness
19semantic and diagnostic state survive a recovery§10safety
20a recovery is not a reset§10safety
21the first fault survives everything but power-on§10observability
22the active configuration changes only at a guarded commit§38safety
23the epoch advances with the configuration§38safety
24an object's configuration epoch is stable for its life§38safety
25RX readiness is independent of TX state§43liveness
26TX readiness is independent of RX occupancy§43liveness
27the stall classifier is one-hot and conserves cycles§44observability

Read the class column. Four are liveness, two are observability, and the rest are safety — and the two observability properties are the ones most often omitted, because they protect the ability to diagnose rather than the ability to function.

53. Coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_ucie_link @(posedge adapter_clk);
  option.per_instance = 1;
 
  // --- Datapath (Sections 12-14, 19).
  cp_tx_occ : coverpoint tx_occ_q {
    bins empty = {0}; bins mid = {[1:TX_Q_DEPTH-1]}; bins full = {TX_Q_DEPTH};
  }
  cp_rx_occ : coverpoint rx_occ_q {
    bins empty = {0}; bins mid = {[1:RX_Q_DEPTH-1]}; bins full = {RX_Q_DEPTH};
  }
  cp_simul_push_pop : coverpoint tx_push_and_pop_same_cycle;      // Section 13
  cp_credit : coverpoint tx_credit_q_ut {
    bins zero = {0}; bins low = {[1:2]}; bins full = {CREDIT_INIT};
  }
  cp_credit_simul : coverpoint consume_and_return_same_cycle;
  cp_replay_occ : coverpoint replay_occupancy {
    bins empty = {0}; bins mid = {[1:REPLAY_DEPTH-1]}; bins full = {REPLAY_DEPTH};
  }
  cp_replay_wrap : coverpoint replay_pointer_wrapped;
 
  // --- Ownership handoff (Sections 16-18).
  cp_dual_window : coverpoint dual_ownership_window_active;
  cp_error_in_handoff : coverpoint error_during_handoff_window;   // Section 17
 
  // --- Receive path (Sections 21-24).
  cp_rx_outcome : coverpoint rx_object_outcome {
    bins delivered = {0};
    bins corrupt   = {1};
    bins duplicate = {2};
    bins stale_epoch = {3};
    bins no_space  = {4};                     // all five gate terms
  }
  cp_duplicate_acked : coverpoint duplicate_still_acknowledged;   // Section 24
 
  // --- Pipeline (Sections 25-27).
  cp_back_to_back : coverpoint consecutive_objects_differing_metadata;  // Section 26
  cp_stall_during_pipe : coverpoint stall_with_objects_in_pipeline;
 
  // --- Link management (Sections 29-32).
  cp_link_state : coverpoint link_state_q { bins each[] = {[0:6]}; }
  cp_transition : coverpoint link_transition { bins each[] = {[0:10]}; }
  cp_attempts : coverpoint retrain_attempts_q {
    bins none = {0}; bins some = {[1:ATTEMPT_BUDGET-1]}; bins budget = {ATTEMPT_BUDGET};
  }
 
  // --- Recovery and faults (Sections 33-35).
  cp_recovery_context : coverpoint recovery_with_state {
    bins idle = {0};
    bins tx_queue_nonempty = {1};
    bins replay_outstanding = {2};
    bins both = {3};                          // THE case — Section 50
  }
  cp_fault_order : coverpoint fault_captured_before_retrain;     // Section 34
  cp_multiple_faults : coverpoint faults_before_clear {
    bins one = {1}; bins several = {[2:$]};   // first, not latest
  }
 
  // --- Configuration (Sections 36-38).
  cp_cfg_context : coverpoint cfg_commit_context {
    bins idle = {0}; bins blocked_by_queue = {1};
    bins blocked_by_replay = {2}; bins forced = {3};
  }
  cp_width_change : coverpoint width_after_commit {
    bins same = {0}; bins narrower = {1};     // Section 51
  }
 
  // --- CDC (Sections 39-40).
  cp_clock_ratio : coverpoint adapter_phy_clock_ratio {
    bins same = {0}; bins adapter_slower = {1}; bins adapter_faster = {2};
  }
  cp_credit_pulse_lost : coverpoint credit_return_sample_missed;  // Section 40
 
  // --- Direction independence (Sections 41-43).
  cp_direction : coverpoint traffic_direction {
    bins tx_only = {0}; bins rx_only = {1}; bins full_duplex = {2};
  }
  cp_rx_full_tx_active : coverpoint rx_full_while_tx_progresses;  // Section 42
 
  // --- Attribution (Section 44).
  cp_stall_reason : coverpoint tx_reason_d { bins each[] = {[0:9]}; }
 
  // --- Crosses that carry the information.
  x_recovery_state : cross cp_recovery_context, cp_link_state;    // Section 50
  x_clock_credit   : cross cp_clock_ratio, cp_credit;             // Section 40
  x_direction_occ  : cross cp_direction, cp_rx_occ;               // Section 42
  x_cfg_inflight   : cross cp_cfg_context, cp_tx_occ;             // Section 37
endcovergroup

Nine bins worth calling out:

cp_recovery_context.both crossed with the link state. §50's trace — a recovery with a non-empty TX queue and outstanding replay entries. Every property in §10 depends on reaching it, and it never occurs spontaneously.

cp_error_in_handoff. §17's one-cycle window, which requires an error injected in exactly that cycle.

cp_rx_outcome — all five. The five gate terms exercised individually (§23), which is the "counted but not gated" test.

cp_back_to_back. §26's off-by-one, detectable only with two consecutive objects carrying different metadata.

cp_clock_ratio.adapter_slower crossed with cp_credit.zero. §40's leak — unreachable at a 1:1 ratio.

cp_rx_full_tx_active. §42 — the RX queue full while TX still progresses, which is the state a shared-ready design cannot produce.

cp_fault_order. §34 — the capture happening before the retrain, proven rather than assumed.

cp_multiple_faults.several. §35's first-versus-latest guard.

And cp_stall_reason.TXS_UNATTRIB must stay at zero, while every other reason should be reachable (§44).

54. Debug Taxonomy

SignatureMost likely causeFirst instrument
The link never reaches an admitting state§30 — a training or link-manager handshake never completeslink_state_q and where it stalls; retrain_attempts_q
Works at low load, stalls forever later§13 or §40 — a counter drift or a credit leakoccupancy against pointers; credit conservation
A duplicate semantic object after a retry§22 — delivery before the history checkis the gate combinational into the push?
A response is lost after a recovery§9 — semantic or queue state cleared by the recoverywhat changed at the recovery cycle
The first failure is never available§34 or §9 — capture after retrain, or the wrong reset domainis first_fault_q in the POR domain?
Data and metadata mismatched§26 — pipelines of different depthmetadata at the far end against acceptance
Fails only when the clocks differ§40 — a pulse crossed as a pulsewhich events cross domains, and how
Throughput unexpectedly halved§42 — a shared ready coupling TX and RXdoes rx_ready depend on any TX term?
The link works until a configuration change§37 — a live configuration writedoes active_cfg_q change outside a commit?
An object disappears with no error§17 — the one-cycle unowned windowownership model; is the pop gated on replay commit?
Retries never stop on one object§24 — a duplicate suppressed without an acknowledgementis the reply sent for a recognised duplicate?
A recoverable error becomes a permanent failure§30 — the attempt budget too small, or no DEGRADED stateretrain_attempts_q; is DEGRADED reachable?

Row 10 is the hardest. An object disappears with no error anywhere is the one-cycle handoff window, and the only detector is a model that checks at every cycle that every live object is held by something.

55. Debug Checklist

  1. Which semantic object, by the client's identity?
  2. Who owns it right now — the TX queue, replay, both, or neither? (§18)
  3. Which TX queue entry, and what is the occupancy? (§12)
  4. Does occupancy match the pointer difference? (§14)
  5. Was a replay entry allocated, and when relative to the queue pop? (§16, §17)
  6. What are the replay pointers, and has the buffer wrapped? (14.3)
  7. How many physical attempts has this object had? (§45)
  8. How many semantic deliveries? Must be ≤ 1. (§24)
  9. Which class, and how many credits does it have? (§19)
  10. Do the credits conserve — available plus outstanding equals advertised? (§20)
  11. Was a credit consumed on an actual transfer, or at acceptance? (§19)
  12. Which configuration epoch did the object carry? (§36)
  13. Did the active configuration change during its life? (§38)
  14. What is the link-manager state, and how did it get there? (§30)
  15. Is training active, and how many attempts have been made? (§30)
  16. What is the lane map and the active width? (§36)
  17. Is a recovery active, and is admission disabled? (§32)
  18. Was the first fault captured, and before the retrain? (§34, §35)
  19. Which reset scopes have been asserted? (§8)
  20. What survived each, against §7's table?
  21. Which clock domain is the signal in question in? (§39)
  22. Does any event cross a domain as a pulse? (§40)
  23. Was the RX gate's verdict evaluated before the push? (§23)
  24. Which of the five gate terms refused? (§23)
  25. Does RX readiness depend on any TX term? (§43)
  26. What does the stall histogram say, and is TXS_UNATTRIB non-zero? (§44)
  27. Which of the four scoreboard layers diverged first? (§46)

56. Common Misconceptions

"A UCIe link is one FSM." It is at least three cooperating machines — link management, training and lane management — plus a datapath with its own per-object state, and six or more distinct state lifetimes. A single merged machine cannot express a policy decision that outlives a training attempt (§28, §30).

"Recovery is just reset." A recovery re-establishes a link. It must not clear semantic obligations, queue contents, the fault record or the performance counters — and a design with one reset_all driven by link_recovery loses accepted objects, makes them unretransmittable, and erases the evidence of what caused the event (§9, §10).

"The replay buffer owns the semantic transaction." Replay owns a transport object until it is acknowledged. The semantic obligation belongs to the protocol client and outlives the replay entry — in the worked trace, by sixteen cycles (§7, §47).

"PHY send means the TX object can be forgotten." The TX queue may release only after replay confirms it holds the object, and replay may release only on acknowledgement. A pop gated on PHY send opens a window in which nothing owns it (§16, §17).

"A clean CRC means semantic delivery is safe." Integrity is one of five gate terms. An object can be intact and be a duplicate, be from a dead epoch, or have nowhere to go — and pushing before the verdict handles three of four validity-and-novelty combinations wrongly (§22, §23).

"Credits are just performance counters." They are the flow-control guarantee, and a leak — from a mis-index or a lost CDC pulse — drifts the count downward inside its legal range until the link stops permanently with no error anywhere. Conservation catches it on the first lost return; bounds never do (§19, §20, §40).

"All state can use one reset." There are at least five reset scopes with different triggers, and §7's table shows sixteen kinds of state with four different survival behaviours. Diagnostics in particular must survive the events that clear everything else (§8, §35).

"Training and link management should be one FSM." They have different lifetimes: a link manager's state lives for the link's operational life and a training controller's for one attempt. Merging them means a policy decision is destroyed every time training restarts (§28).

"Configuration can be changed live if each field is valid." Different pipeline stages then interpret one object under different configurations, and a replayed object is framed under a configuration the peer does not expect. The commit needs a drained pipeline and no outstanding replay entries (§36, §37).

"TX and RX should share one ready signal." A shared link_ready halves throughput at best and deadlocks at worst, because the peer's progress depends on our transmissions and our transmissions then depend on our receive path draining (§42).

"CDC is a physical implementation detail." A one-cycle credit-return pulse crossed with a synchroniser is lost at some clock ratios, and every lost pulse is a permanent credit leak. The link works for hours and then stops (§40).

"A top-level block diagram is enough to define the architecture." The diagram shows blocks; the architecture is §7's table — who owns each piece of state, when it is allocated and released, and what it survives. Two designs with identical block diagrams and different ownership tables behave completely differently under recovery.

"The fastest way to implement UCIe is one large module." It removes every boundary at which ownership, reset scope and clock domain could have been reviewed — which are precisely the three things this chapter's failures are made of.

"Assertions belong after the RTL is complete." Five of §52's twenty-seven properties are connectivity claims — that a recovery does not reach a reset, that a pop is gated on replay, that RX readiness has no TX term. Those are decided when the top level is wired, and writing them afterwards means discovering them afterwards.

"A recovery that returns to an active state was successful." In §9's design the link returns active, reports success, and has lost every accepted object and the record of why. Reaching an admitting state is a link-management outcome, not a semantic one (§9, §50).

57. Understanding Check

58. Summary and What Comes Next

A UCIe link is a hierarchy of state machines and queues whose lifetimes overlap, and the architecture is the discipline of never letting one of them reset, release or decide on behalf of another.

The state-ownership table is the design. Sixteen kinds of state, twelve owners, and four different survival behaviours — and two designs with identical block diagrams and different tables behave completely differently under recovery.

Five reset scopes, and a recovery is not one of them. One reset_all driven by link_recovery loses accepted objects, makes them unretransmittable, and erases the record of why — while the link comes back and reports success.

The object is never unowned. The TX queue releases only after replay confirms, which opens a deliberate one-cycle dual-ownership window — and the alternative opens a one-cycle window in which nothing owns it and an error loses it silently.

Check, then act. The receive gate is five terms evaluated combinationally before the push; pushing first mishandles three of four validity-and-novelty combinations while the transport behaves exactly as specified.

Three machines, not one. Link management, training and lane management have three lifetimes, and their outputs come from state rather than from raw asynchronous events.

Capture before you retrain, or the retrain overwrites the evidence and the same failure recurs indefinitely with nothing to diagnose it.

Commit configuration atomically, with a drained pipeline and no outstanding replay — because a replayed object framed under the old configuration is transmitted under the new one.

Cross state, not events. A one-cycle credit-return pulse across a clock boundary is a permanent leak that stops the link after hours, and only conservation catches it.

And TX and RX are independent. One shared ready signal halves throughput at best and closes a deadlock cycle through the peer at worst.

This chapter partitioned the link into blocks and assigned every piece of state an owner. The next chapter opens the first of those blocks. The Protocol Layer is where the mapped protocols actually live — PCIe, CXL and the streaming path — and its RTL is where a protocol's transactions become objects the Adapter can carry.

Browse the full path on the UCIe tutorials index.