Skip to content

UCIe · Module 5

The Adapter Layer

The microarchitecture of the UCIe Die-to-Die Adapter — buffering and occupancy, flow control as resource accounting, integrity-pipeline alignment, retry retention, link-management and negotiation state machines, and arbitration between protocols.

Engineers meeting the UCIe stack for the first time usually classify the Adapter as glue — the thing between the interesting protocol logic and the interesting physical layer. That instinct is wrong, and this chapter is written to replace it with a microarchitectural picture.

The Adapter holds more state than either of its neighbours. It runs the link's control plane, it is the reliability boundary in the modes that use one, it accounts for storage that lives on another die, and it decides which protocol gets to use the link this cycle. Every one of those is a hardware structure with an ownership rule, a failure mode, and an assertion that catches it.

The Adapter's documented responsibilities are the ones Module 4 introduced: coordinating the higher-level link state machine and bring-up, negotiating parameters with the remote partner, providing CRC and link-level retry where configured, arbitrating and multiplexing when protocols share the link, and coordinating power states with the partner.

Listing them is not the lesson. The lesson is that each requires state, and asking what state? is what turns a specification bullet into a design:

ResponsibilityState it requires
Accept traffic from aboveBuffer storage and occupancy
Deliver to the PHYEgress state, ordering position
Flow controlAn account of capacity — local and remote
IntegrityA pipeline whose stages stay aligned with the data
RetryRetained copies of unacknowledged transport units
Link managementA state machine with prerequisites
NegotiationStored remote capability and the chosen operating point
ArbitrationSelection state, and enough history for fairness
Power coordinationAgreed state with the partner

The Adapter is the link's control plane and — in the modes that use one — its reliability boundary. It separates semantic behaviour from physical behaviour by owning all the state that sits between them.

Adapter microarchitecture. Protocol-side ingress feeds an input buffer, which feeds arbitration and transport, which feeds integrity and retry storage, which feeds the PHY-side egress. A link management and negotiation control block gates the datapath and exchanges status with the physical layer.Protocol-sideingressaccept when space existsInput bufferoccupancy, head, tailArbitrationwhich protocol this cycleIntegrity and retrycheck state, replaycopiesPHY-side egresssend when link permitsLink managementstate, negotiation,gatinggatesgates12
Figure 1 — Adapter microarchitecture. Traffic from the Protocol Layer enters an ingress buffer, passes arbitration where several protocols share the link, receives integrity treatment and is retained for possible replay, then leaves toward the PHY. A separate control block owns link management and parameter negotiation and gates the whole datapath through link state — the datapath cannot run until the control plane says the link is ready.

2. The Ingress Buffer

Module 4 used one-entry stages to teach ownership. The Adapter needs a real queue, because it must absorb bursts from above while the link below is busy.

Illustrative architecture RTL — not UCIe normative signal naming.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Adapter ingress buffer: a small synchronous FIFO with explicit occupancy.
parameter int unsigned DEPTH  = 4;
parameter int unsigned DATA_W = 256;
 
localparam int unsigned PTR_W = $clog2(DEPTH);
localparam int unsigned CNT_W = $clog2(DEPTH + 1);
 
logic [DATA_W-1:0] fifo_mem [DEPTH];
logic [PTR_W-1:0]  rd_ptr_q, wr_ptr_q;
logic [CNT_W-1:0]  count_q;
 
logic full, empty, do_enq, do_deq;
 
assign full   = (count_q == CNT_W'(DEPTH));
assign empty  = (count_q == '0);
 
assign up_ready = !full;
assign do_enq   = up_valid && up_ready;
 
assign dn_valid = !empty;
assign do_deq   = dn_valid && dn_ready;
assign dn_data  = fifo_mem[rd_ptr_q];
 
always_ff @(posedge clk) begin
  if (!rst_n) begin
    rd_ptr_q <= '0;
    wr_ptr_q <= '0;
    count_q  <= '0;
  end else begin
    if (do_enq) begin
      fifo_mem[wr_ptr_q] <= up_data;
      wr_ptr_q           <= (wr_ptr_q == PTR_W'(DEPTH-1)) ? '0 : wr_ptr_q + 1'b1;
    end
    if (do_deq) begin
      rd_ptr_q <= (rd_ptr_q == PTR_W'(DEPTH-1)) ? '0 : rd_ptr_q + 1'b1;
    end
    unique case ({do_enq, do_deq})
      2'b10:   count_q <= count_q + 1'b1;
      2'b01:   count_q <= count_q - 1'b1;
      default: count_q <= count_q;   // both, or neither
    endcase
  end
end

Architecture. Storage that decouples the rate the Protocol Layer produces from the rate the PHY consumes, so a short stall below does not immediately stall above.

State. fifo_mem holds payloads; wr_ptr_q/rd_ptr_q are head and tail; count_q is occupancy. Occupancy is deliberately a separate counter rather than derived from pointer difference — it makes full and empty unambiguous without a wrap bit, and it is the single most useful signal in debug.

Cycle behaviour. Cycle N: up_valid and !full, so do_enq — the payload is written and wr_ptr_q advances. Cycle N+1: the PHY is busy, dn_ready low, so no dequeue and count_q holds. Cycle N+2: dn_ready rises while up_valid is still high — both fire, count_q is unchanged, and the buffer sustains full throughput rather than alternating.

Contract. Upstream: if up_ready was high, the payload is retained. Downstream: dn_data is the oldest entry and is stable while dn_valid is high.

Failure/DV. The default arm matters more than it looks — treating simultaneous enqueue and dequeue as either an increment or a decrement corrupts occupancy immediately, which then corrupts full and empty. That is a silent structural bug, and it is exactly what the next section asserts against.

3. Occupancy Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative buffer invariants — conceptual, not UCIe normative properties.
 
// SAFETY: occupancy can never exceed capacity.
property p_no_overflow;
  @(posedge clk) disable iff (!rst_n) count_q <= CNT_W'(DEPTH);
endproperty
assert property (p_no_overflow) else $error("occupancy exceeded DEPTH");
 
// SAFETY: never dequeue from an empty buffer.
property p_no_underflow;
  @(posedge clk) disable iff (!rst_n) do_deq |-> (count_q != '0);
endproperty
assert property (p_no_underflow) else $error("dequeued while empty");
 
// SAFETY: the full/empty flags must agree with occupancy.
property p_full_iff_depth;
  @(posedge clk) disable iff (!rst_n) full == (count_q == CNT_W'(DEPTH));
endproperty
assert property (p_full_iff_depth) else $error("full flag disagrees with occupancy");
 
property p_empty_iff_zero;
  @(posedge clk) disable iff (!rst_n) empty == (count_q == '0);
endproperty
assert property (p_empty_iff_zero) else $error("empty flag disagrees with occupancy");

Each catches a distinct bug. Overflow means a payload was written over an unconsumed one — silent loss. Underflow means a payload was fabricated from stale memory — silent corruption. The flag-consistency pair catches the class where occupancy is right but the derived control is wrong, which produces either lost throughput (never asserting ready) or overflow (asserting ready when full) depending on direction.

4. Flow Control Is Resource Accounting

A FIFO handles local capacity. But the Adapter also has to avoid overrunning storage on the other die, which it cannot observe.

Flow control is accounting for storage ownership across a boundary you cannot see.

The general mechanism is a counter representing how much remote capacity this transmitter is entitled to consume: decrement when consuming it, increment when the remote side signals capacity has been freed. The exact mechanism UCIe defines belongs to the flow-control chapter later in the curriculum; the accounting shape is what matters now.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Conceptual transmit-side capacity accounting -- generic, not UCIe's
// specific flow-control mechanism.
parameter int unsigned CREDIT_W = 6;
 
logic [CREDIT_W-1:0] tx_credit_q;
logic                credit_return;   // remote signalled capacity freed
logic                tx_consume;      // we are launching a transport unit
 
assign may_transmit = (tx_credit_q != '0);
assign tx_consume   = dn_valid && dn_ready && may_transmit;
 
always_ff @(posedge clk) begin
  if (!rst_n) begin
    tx_credit_q <= INITIAL_CREDITS;
  end else begin
    unique case ({tx_consume, credit_return})
      2'b10:   tx_credit_q <= tx_credit_q - 1'b1;
      2'b01:   tx_credit_q <= tx_credit_q + 1'b1;
      default: tx_credit_q <= tx_credit_q;
    endcase
  end
end

Architecture. A local model of remote storage. The transmitter is permitted to send only while it holds entitlement.

State. tx_credit_q — initialised during bring-up to whatever the partner advertised, then maintained for the life of the link.

Cycle behaviour. Consuming and returning on the same cycle leaves the count unchanged — the same simultaneity handling as the FIFO, and wrong in the same way if collapsed.

Contract. The receiver relies absolutely on this: it sized its buffers assuming the transmitter respects the accounting. If the transmitter sends without entitlement, the receiver has no defence — it has nowhere to put the arriving unit.

Failure/DV. Transmit at zero credit and the remote buffer overflows, dropping or corrupting a transport unit that the local side believes was delivered. Worse, an underflowing counter that wraps looks like a large positive credit, so the transmitter floods.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SAFETY: never launch a transport unit without entitlement.
property p_no_send_without_credit;
  @(posedge clk) disable iff (!rst_n)
    tx_consume |-> (tx_credit_q != '0);
endproperty
assert property (p_no_send_without_credit)
  else $error("transmitted with zero remote capacity");
 
// SAFETY: entitlement must never exceed what was advertised.
property p_credit_bounded;
  @(posedge clk) disable iff (!rst_n) tx_credit_q <= MAX_CREDITS;
endproperty
assert property (p_credit_bounded)
  else $error("credit count exceeded the advertised maximum");

The second property catches duplicate returns — a return counted twice inflates entitlement, and the transmitter later overruns a receiver that never had that capacity.

5. Integrity, and the Alignment Bug

In the modes where the Adapter provides reliability, transmit computes integrity information and receive verifies it. The full mechanism is a later chapter; what belongs here is a single line and the trap hiding inside it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The receive-side decision, in one line.
assign deliver_up = rx_valid && crc_ok;

That looks trivial and conceals a genuine pipeline problem: crc_ok and the payload it describes must refer to the same transport unit. Checking is not instantaneous — it takes at least one pipeline stage — so if the data path and the check result are not delayed identically, they drift apart.

Here is the bug, which is common enough to be worth showing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// INCORRECT: the check result is registered but the payload is not, so
// crc_ok lags the data it describes by one cycle.
always_ff @(posedge clk) begin
  crc_ok_q <= crc_check(rx_data);     // result for THIS cycle's data...
end
 
assign deliver_up = rx_valid && crc_ok_q;   // ...applied to NEXT cycle's data
assign up_data    = rx_data;                // undelayed

Why it fails. Cycle N: unit A arrives; crc_check(A) is computed and registered. Cycle N+1: crc_ok_q now holds A's result, but rx_data is unit B. A good A followed by a corrupt B is accepted, because B is gated by A's verdict. A corrupt A followed by a good B is rejected, discarding a valid unit.

The signature is diagnostically distinctive: errors appear to be off by one transport unit, and rejection and acceptance are both wrong in a correlated way. It also looks intermittent — it only shows when corruption actually occurs — which sends people hunting for a marginal channel.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECTED: delay the payload alongside its check result so both stages
// describe the same transport unit.
always_ff @(posedge clk) begin
  rx_data_q  <= rx_data;
  rx_valid_q <= rx_valid;
  crc_ok_q   <= crc_check(rx_data);
end
 
assign deliver_up = rx_valid_q && crc_ok_q;
assign up_data    = rx_data_q;

DV. The catch is a scoreboard property rather than a single-cycle assertion: for each transport unit injected with known-bad integrity, exactly that unit must be rejected and no other. An error-injection test that corrupts alternating units exposes the misalignment immediately, whereas corrupting a single unit in isolation may not.

6. Retry Requires Retention

If the link can replay a transport unit, then transmitting it is not the end of the transmitter's responsibility.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Conceptual replay retention -- illustrative, not UCIe's retry protocol.
logic              retry_valid_q;
logic [DATA_W-1:0] retry_data_q;
logic [SEQ_W-1:0]  retry_seq_q;
 
always_ff @(posedge clk) begin
  if (!rst_n) begin
    retry_valid_q <= 1'b0;
  end else if (tx_fire) begin
    retry_valid_q <= 1'b1;          // launched: keep a copy
    retry_data_q  <= tx_data;
    retry_seq_q   <= tx_seq;
  end else if (ack_fire && (ack_seq == retry_seq_q)) begin
    retry_valid_q <= 1'b0;          // confirmed: now safe to discard
  end
end

Architecture. A local copy retained until the remote side confirms receipt. This is the longest-lived state in the Adapter — it outlives the transmission by a full round trip.

Cycle behaviour. Set on launch; cleared only on a matching confirmation. Between those two events the Adapter holds the only recoverable copy in the system.

Failure/DV. The classic bug:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG: discarding the replay copy at transmission.
if (tx_fire)
  retry_valid_q <= 1'b0;

Why it fails. Transmission is exactly when the copy becomes necessary. If the unit is corrupted in the channel, the receiver rejects it and requests replay — and there is nothing to replay. The failure only manifests when an error actually occurs, so it survives clean-link testing completely and appears in the field as an unrecoverable link error under marginal conditions.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SAFETY: a launched unit must remain retained until confirmed.
property p_retry_retained_until_ack;
  @(posedge clk) disable iff (!rst_n)
    tx_fire |=> retry_valid_q throughout (!(ack_fire && ack_seq == retry_seq_q))[->1];
endproperty

This one deserves a caveat: as written it assumes a confirmation eventually arrives, which makes it a bounded-liveness property rather than pure safety, and in a formal flow it needs an accompanying assumption that the environment does acknowledge. The simpler safety companion — tx_fire |=> retry_valid_q — is weaker but unconditional, and is the one to start with.

The Adapter owns bring-up, and bring-up is a sequence with prerequisites.

Illustrative architecture states — not UCIe normative state names.

A conceptual adapter link-management state machine with states reset, wait for PHY, negotiate, ready, recovery and error. Reset advances to wait for PHY, which advances to negotiate when the physical link reports up, which advances to ready when a compatible operating point is agreed. A link fault moves ready to recovery, which returns to wait for PHY, and a negotiation mismatch moves to error.RESETWAIT_PHYNEGOTIATEREADYRECOVERYERRORout of resetout of resetphy upphy upagreedagreedno matchno matchfaultfaultretrainretrain
Figure 2 — a conceptual Adapter link-management state machine. Each transition has a prerequisite: the physical link must report usable before negotiation begins, and a compatible operating point must be selected before the datapath is enabled. Recovery re-enters the sequence rather than jumping straight back to ready, which is what prevents traffic resuming on a link whose parameters have not been re-established.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum logic [2:0] {
  ADP_RESET,
  ADP_WAIT_PHY,   // waiting for the physical link to report usable
  ADP_NEGOTIATE,  // exchanging and selecting parameters with the partner
  ADP_READY,      // datapath enabled
  ADP_RECOVERY,   // transient fault; re-establishing
  ADP_ERROR       // unrecoverable (e.g. no compatible operating point)
} adapter_state_t;
 
adapter_state_t adp_state_q;
 
always_ff @(posedge clk) begin
  if (!rst_n) begin
    adp_state_q <= ADP_RESET;
  end else begin
    unique case (adp_state_q)
      ADP_RESET     : adp_state_q <= ADP_WAIT_PHY;
      ADP_WAIT_PHY  : if (phy_link_up)     adp_state_q <= ADP_NEGOTIATE;
      ADP_NEGOTIATE : if (negotiate_done)  adp_state_q <= (common_ok ? ADP_READY : ADP_ERROR);
      ADP_READY     : if (link_fault)      adp_state_q <= ADP_RECOVERY;
      ADP_RECOVERY  : if (recovered)       adp_state_q <= ADP_WAIT_PHY;
      ADP_ERROR     : /* held until software or reset intervenes */ ;
      default       : adp_state_q <= ADP_ERROR;
    endcase
  end
end
 
// The single abstraction consumed by the Protocol Layer.
assign link_operational = (adp_state_q == ADP_READY);

Architecture. The control plane that gates everything else. Note ADP_RECOVERY returns to ADP_WAIT_PHY, not directly to ADP_READY — after a fault the physical link and the negotiated parameters must both be re-established, and shortcutting that is a real bug.

Contract. The Protocol Layer consumes link_operational and nothing finer, per Chapter 5.1 §8.

Failure/DV. Reaching ADP_READY without completing negotiation means running with an operating point the partner never agreed to.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SAFETY: ready requires a successfully negotiated operating point.
property p_ready_requires_negotiated;
  @(posedge clk) disable iff (!rst_n)
    (adp_state_q == ADP_READY) |-> negotiated_valid_q;
endproperty
assert property (p_ready_requires_negotiated)
  else $error("entered READY without a negotiated operating point");
 
// SAFETY: no protocol traffic accepted outside READY.
property p_no_traffic_outside_ready;
  @(posedge clk) disable iff (!rst_n)
    (adp_state_q != ADP_READY) |-> !(up_valid && up_ready);
endproperty
assert property (p_no_traffic_outside_ready)
  else $error("accepted protocol traffic while not READY");

8. Negotiation Needs Stored Remote State

Chapter 3.2 introduced capability intersection as a concept. Here it is a hardware structure owned by this layer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Conceptual parameter negotiation state -- not UCIe's encoding.
logic [CAP_W-1:0] local_caps;        // fixed by this design
logic [CAP_W-1:0] remote_caps_q;     // captured during NEGOTIATE
logic [CAP_W-1:0] common_caps;
logic [CAP_W-1:0] selected_q;        // the chosen operating point
logic             negotiated_valid_q;
 
assign common_caps = local_caps & remote_caps_q;
assign common_ok   = (common_caps != '0);
 
always_ff @(posedge clk) begin
  if (!rst_n) begin
    negotiated_valid_q <= 1'b0;
  end else if (adp_state_q == ADP_NEGOTIATE && negotiate_done && common_ok) begin
    selected_q         <= select_preferred(common_caps);
    negotiated_valid_q <= 1'b1;
  end else if (adp_state_q == ADP_RECOVERY) begin
    negotiated_valid_q <= 1'b0;      // must be re-established after a fault
  end
end

Architecture. Negotiation is inherently sequential — it requires exchanging information with a partner across a link, which takes many cycles. It cannot be combinational, and treating it as such is a design error rather than an optimisation.

State. remote_caps_q retains what the partner advertised, for the life of the link. selected_q is the agreed operating point everything else depends on.

Cycle behaviour. Captured once during ADP_NEGOTIATE; invalidated on entering recovery, which is what forces re-negotiation rather than resuming on stale parameters.

Failure/DV. Failing to invalidate on recovery means resuming with an operating point that may no longer hold — for instance after the physical link retrains at a different width.

9. Arbitration and Starvation

Where several protocols share one link, something must choose. Fixed priority is the obvious implementation and has a well-known flaw:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// INCORRECT for shared links: strict priority starves the lower stream.
always_comb begin
  if      (a_valid) grant = GRANT_A;
  else if (b_valid) grant = GRANT_B;
  else              grant = GRANT_NONE;
end

Why it fails. If stream A has work every cycle, B is never granted. Cycle N through N+1000: a_valid high, b_valid high, grant = A every cycle. B makes no progress at all. This is not a slowdown — it is indefinite starvation, and if B carries responses that A's progress ultimately depends on, it becomes a deadlock.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECTED: round-robin -- the last granted source has lowest priority next.
logic last_grant_a_q;
 
always_comb begin
  unique case (1'b1)
    (a_valid && b_valid): grant = last_grant_a_q ? GRANT_B : GRANT_A;
    a_valid:              grant = GRANT_A;
    b_valid:              grant = GRANT_B;
    default:              grant = GRANT_NONE;
  endcase
end
 
always_ff @(posedge clk) begin
  if (!rst_n)                 last_grant_a_q <= 1'b0;
  else if (grant == GRANT_A)  last_grant_a_q <= 1'b1;
  else if (grant == GRANT_B)  last_grant_a_q <= 1'b0;
end

State. last_grant_a_q is the entire fairness memory — one bit, and without it fairness is impossible.

DV. Fairness is a liveness property, and writing it carelessly produces an assertion that is either vacuous or unprovable. A bounded formulation is far more useful:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// BOUNDED LIVENESS: a continuously requesting source must be granted within
// a bounded window while the arbiter is making progress. The bound is a
// design parameter -- an unbounded eventually() is usually not checkable
// in simulation and needs fairness assumptions in formal.
property p_b_not_starved;
  @(posedge clk) disable iff (!rst_n)
    (b_valid && arb_progressing)[*STARVE_LIMIT] |-> ##[0:STARVE_LIMIT] (grant == GRANT_B);
endproperty

Pair it with coverage that both sources are granted while both are continuously requesting — coverage often finds starvation faster than assertions do.

10. Turning Lower-Layer Conditions Into Status

The Adapter is where physical conditions become something the Protocol Layer can act on. It must distinguish conditions that look similar and mean different things:

  • A detected bad payload — integrity check failed. Recoverable in the modes with retry; the transport unit is rejected and not delivered upward.
  • A resource condition — buffers full, no credit. Not an error at all; normal backpressure.
  • Link unavailable — the PHY is not reporting the link usable. Traffic must stop; not a data error.
  • Negotiation mismatch — no compatible operating point exists. Terminal for this configuration; not retryable.
  • Persistent physical fault — repeated failures that recovery does not clear.

These require different responses, and collapsing them is a common design error. Backpressure treated as an error causes spurious recovery; a persistent fault treated as backpressure causes a hang with no diagnostic.

What goes upward is an abstraction: operational or not, and whatever degradation status the architecture defines. Raw physical detail must not leak, per Chapter 4.2.

11. Debugging a Stalled Adapter

When traffic stops, walk this in order. The first no is the answer:

  1. Is the link state ADP_READY? If not, the control plane is the problem — go to §7's prerequisites.
  2. Is the Protocol side actually presenting up_valid?
  3. Does the buffer have space — is count_q < DEPTH?
  4. Did an enqueue fire, and did count_q increment?
  5. Is arbitration granting this source, or is another stream monopolising it?
  6. Is retry or recovery active and suppressing new traffic?
  7. Is the PHY side accepting — is dn_ready ever high?
  8. Is there transmit entitlement — is tx_credit_q non-zero?
  9. Is an error state suppressing the datapath?
  10. Is link_operational correctly reflecting state upward?

Two signals answer most cases. count_q distinguishes "nothing is arriving" (stays zero) from "nothing is leaving" (pegged at DEPTH). And tx_credit_q at zero with a full buffer is the signature of the far side not returning capacity — which points at the remote Adapter or the return path, not this one.

12. Common Misconceptions

13. Understanding Check

14. Summary and What Comes Next

The Adapter is the link's control plane and, in the modes that use one, its reliability boundary — and every responsibility it holds is backed by state. Ingress buffering with explicit occupancy decouples production from consumption, and its simultaneous enqueue-and-dequeue case is where naive implementations corrupt the count. Flow control is resource accounting for storage on a die you cannot observe, with underflow and duplicated returns as the two dangerous directions. Integrity requires pipeline alignment, whose failure looks like errors shifted by one unit and masquerades as a marginal channel. Retry requires retention until delivery is confirmed, and freeing the copy at transmission is invisible to clean-link testing and fatal on a marginal one.

Above the datapath sits a link-management state machine whose transitions have prerequisites — PHY up before negotiate, agreed operating point before ready, recovery back to waiting rather than straight to ready — and negotiation that is inherently sequential and must be invalidated after a fault. Where protocols share a link, arbitration needs fairness state, because strict priority starves indefinitely and can become deadlock.

For debug, two counters do most of the work: occupancy distinguishes "nothing arriving" from "nothing leaving", and zero credit with a full buffer means this Adapter is correctly waiting on the far side.

The Adapter can only manage a link if something beneath it makes that link real — discovering usable lanes, establishing timing, and reporting physical readiness honestly:

  • 5.3 — The Physical Layer — the digital state inside a PHY: bring-up state machines, lane health versus lane enable, clock-domain crossing done safely, timeouts, and why a link can train perfectly and still corrupt every transfer.

Browse the full path on the UCIe tutorials index.