Skip to content

UCIe · Module 4

UCIe Layered Architecture

Where UCIe responsibilities belong and what contract holds between Protocol, Adapter, and PHY — state ownership, backpressure and decoupling, the combinational-path trap, reset versus operational, and the assertions that hold each boundary together.

Chapter 4.1 gave the picture: three layers, two specified boundaries, mirrored across a link. This chapter turns that picture into architecture — the decisions an engineer actually makes when building or verifying one of these layers.

The question changes from what is the stack? to where exactly should each responsibility live, and what must be true at each boundary? Those are different questions, and the second one has consequences you can see in waveforms: state in the wrong layer, backpressure that does not propagate, a combinational path that will not close timing, or a protocol implementation that has to be redesigned because a physical detail leaked upward.

1. Each Layer Owns One Kind of Uncertainty

The sharpest framing of the split, and the one to reason with when deciding where a new function belongs:

  • Protocol Layer owns uncertainty in transaction semantics — what a payload means, what ordering must hold, what a response signifies.
  • D2D Adapter owns uncertainty in reliable transport across the link — whether the payload arrived intact, in order, without overrunning the receiver, and whether the link is in a state that can carry it.
  • Physical Layer owns uncertainty in the physical channel — whether bits survive the package, whether lanes are working, whether the link has trained.

Use it as a placement test. Faced with "where does function X belong?", ask which uncertainty it resolves. Error detection over a transport unit resolves channel-induced uncertainty about data integrity — Adapter. Deciding whether two requests may complete out of order resolves semantic uncertainty — Protocol. Deciding whether a lane is usable resolves physical uncertainty — PHY.

2. Protocol Layer Responsibilities

The Protocol Layer implements a protocol: it deals in that protocol's transactions and messages, its ordering rules, and its semantics. UCIe carries established protocols — PCIe and CXL — along with a streaming/raw option for traffic that suits neither.

What it should not do is manage the physical link. Lane training, bump or lane repair, electrical signalling, clock recovery, and physical link state are not its concerns, and §8 shows concretely what breaks when they become so.

One responsibility genuinely does move depending on configuration, and getting this right early prevents a common misconception. In the mode where the Adapter handles flit framing, the Adapter inserts and checks CRC and can provide link-level retry. In a raw mode, the payload is populated entirely by the Protocol Layer, and error protection — CRC, retry, and any forward error correction — becomes the Protocol Layer's responsibility. So:

The layers are fixed; the assignment of some responsibilities depends on the configured mode.

That is worth internalising because it means "which layer does error protection?" has the answer it depends on the mode, and an engineer who memorised a single fixed table will be wrong half the time.

3. D2D Adapter Responsibilities

The Adapter is the layer that makes the link usable by something that does not want to know how the link works. Its documented responsibilities include:

  • Link state coordination and bring-up. Running the higher-level link state machine and getting the link from reset to operational, in cooperation with the PHY below.
  • Parameter negotiation with the remote partner. Establishing the operating point both sides support — the capability-intersection problem of Chapter 3.2, now a concrete responsibility with an owner.
  • Reliability, when configured. CRC insertion and checking, and link-level retry, so an upper layer does not have to treat the physical channel as unreliable.
  • Protocol arbitration and multiplexing. Where more than one protocol shares a link, deciding what goes when and keeping the streams separated.
  • Power-state coordination with the remote partner, since low-power states are only meaningful if both ends agree.

Notice the common property: every one of these requires knowing about the partner and the link, and none requires knowing what the payload means. That is precisely why they belong together in a layer between the two.

The details of each — retry mechanics, flit formats, the state machine's states, negotiation encoding — belong to later modules. What belongs here is the ownership claim and its consequence for where state lives (§5).

4. Physical Layer Responsibilities — and Why "PHY" Is Not "Analog"

The Physical Layer owns lanes and modules, electrical signalling, clocking, training the link into a usable state, and the sideband path used to coordinate that.

Now the correction that matters most to an RTL engineer, because the word misleads:

A PHY is a functional layer, not a synonym for analog circuitry.

A die-to-die PHY typically contains a substantial amount of digital logic: the training state machine that walks the link from reset to operational, lane mapping and repair state, serialisation and deserialisation, clocking control, status reporting upward, and the logic driving the sideband. The analog front end is one part of it, and often not the part with the most state.

This matters practically. If you assume "PHY = analog", you will assume there is nothing to verify at RTL level, no state machine to reason about, and nothing to model — all three wrong. The digital portion of a PHY is regular RTL with regular bugs, and it is where a large share of bring-up debugging happens.

5. Where State Lives

The most useful single table in this module. State placement follows from §1: the layer that owns an uncertainty owns the state that resolves it.

StateOwnerWhy
Transaction/message state, ordering contextProtocolSemantic — only Protocol knows what the payload means
Transport buffer occupancy at a boundaryAdapter (and each boundary)Whoever accepts a payload must retain it
Retry/reliability bookkeepingAdapter in the mode where it provides CRC and retry; Protocol in raw modeMode-dependent, per §2
Remote-partner negotiated parametersAdapterIt runs the negotiation
Higher-level link stateAdapter, coordinating with PHYIt owns bring-up and link-state coordination
Lane training statePHYPhysical uncertainty
Lane health / repair statePHYPhysical uncertainty
Serialisation and clocking statePHYPhysical uncertainty

Two honest notes. The exact boundary between "higher-level link state" in the Adapter and physical link state in the PHY is a specification detail with real nuance, and later modules treat it properly — the architectural point here is that both exist and they are not the same state. And the retry row is genuinely mode-dependent rather than a simplification.

The Protocol Layer owns transaction semantics and connects to the D2D Adapter across a boundary carrying payload downward and backpressure upward. The Adapter owns reliable transport and link state, and connects to the Physical Layer across a boundary carrying transport units downward and link status upward. The Physical Layer owns lanes, training and signalling.Protocol Layerowns semanticspayload +backpressureboundary contractD2D Adapterowns reliable transporttransport + statusboundary contractPhysical Layerowns the channelState followsownershipbuffers, link state,lanes12
Figure 1 — layer responsibilities and what crosses each boundary. Payload descends and backpressure ascends at both boundaries, while link status is abstracted upward so the Protocol Layer consumes a usable-or-not view rather than physical detail. Each layer owns one kind of uncertainty, and the state that resolves it sits in that layer.

6. The Boundary as an Interface

Both internal boundaries carry the same shape — payload down, backpressure up — so it is worth naming that shape once.

Illustrative transport abstraction — not UCIe normative interface contents. The real FDI and RDI carry considerably more than this; the reduction to payload plus backpressure isolates the behaviour this chapter is about.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
interface layer_stream_if #(parameter int unsigned W = 256) (input logic clk);
  logic [W-1:0] data;
  logic         valid;   // driven by the upstream (producing) side
  logic         ready;   // driven by the downstream (consuming) side
 
  modport up   (output data, output valid, input  ready);
  modport down (input  data, input  valid, output ready);
endinterface

Architecture. One abstraction reused at Protocol↔Adapter and Adapter↔PHY, which is why the same reasoning and the same assertions apply at both.

Contract. A transfer occurs on a cycle where valid && ready. While valid && !ready, data and valid must hold. Whoever asserts ready takes responsibility for retaining what it accepts.

Why modports matter here. They encode direction of ownership: the upstream side drives payload and qualification, the downstream side drives acceptance. Getting that backwards is a whole class of integration bug that the type system can prevent.

7. The Combinational-Path Trap

An architecture lesson that costs real schedule time when missed.

It is tempting to compute readiness combinationally through the whole stack — each layer's ready derived directly from the layer below, and each layer's valid derived from above:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// RISKY COMPOSITION: readiness threaded combinationally through layers,
// and valid derived from ready in the same cycle.
assign proto_ready = adapter_cond && phy_ready;      // ready flows up, combinationally
assign phy_valid   = proto_valid && proto_ready;     // valid derived from ready

Two distinct problems, and they are worth separating.

A long combinational path. phy_ready now feeds proto_ready, which the Protocol Layer's logic consumes to decide what it does this cycle. Across three layers — potentially three separately designed blocks, physically placed apart — this becomes a timing path spanning the endpoint. It will be among the first paths to fail closure, and it constrains where the blocks can be placed. Chapter 2.6's point that physical reality constrains architecture applies inside the die too.

A loop risk when combined carelessly. If the downstream ready is computed from the upstream valid — a plausible optimisation, since "I can accept if you are not sending" seems reasonable — while the upstream valid is computed from ready, the result is a combinational cycle. Each signal depends on the other within the same cycle, and the design either fails elaboration, fails timing, or simulates unpredictably.

To be precise about what is not being claimed: ready/valid does not have to be registered, and a combinational ready is entirely legitimate in many designs. The discipline is to be deliberate about how far a combinational path is allowed to run and which direction each signal is derived from — not to ban combinational readiness.

8. Decoupling With Storage at the Adapter

The standard remedy is to give the Adapter explicit state at its boundary. A skid buffer is the canonical structure: it accepts from above even when the layer below is stalling, so the upstream readiness need not depend combinationally on the downstream one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Adapter-side decoupling buffer. Breaks the combinational path between
// the PHY-facing ready and the protocol-facing ready, and gives the
// Adapter explicit ownership of an in-flight payload.
logic [W-1:0] out_data_q,  skid_data_q;
logic         out_valid_q, skid_valid_q;
 
always_ff @(posedge clk) begin
  if (!rst_n) begin
    out_valid_q  <= 1'b0;  out_data_q  <= '0;
    skid_valid_q <= 1'b0;  skid_data_q <= '0;
  end else begin
    // Output stage advances when it is empty or being drained.
    if (!out_valid_q || dn_ready) begin
      if (skid_valid_q) begin
        out_valid_q  <= 1'b1;
        out_data_q   <= skid_data_q;
        skid_valid_q <= 1'b0;
      end else begin
        out_valid_q  <= up_valid;
        out_data_q   <= up_data;
      end
    end
    // Downstream stalled while upstream is still offering: absorb one beat.
    if (out_valid_q && !dn_ready && up_valid && !skid_valid_q) begin
      skid_valid_q <= 1'b1;
      skid_data_q  <= up_data;
    end
  end
end
 
assign up_ready  = !out_valid_q || dn_ready || !skid_valid_q;
assign dn_valid  = out_valid_q;
assign dn_data   = out_data_q;

Architecture. The Adapter now holds up to two beats, decoupling the two boundaries so a PHY stall does not have to propagate combinationally to the Protocol Layer in the same cycle.

State. out_valid_q/out_data_q is the beat presented downstream; skid_valid_q/skid_data_q absorbs one more when the downstream stalls mid-flow. That is the entire state, and it is genuinely the Adapter's — not the Protocol Layer's and not the PHY's.

Cycle behaviour. When the output stage is empty or draining, it reloads from the skid stage if occupied, otherwise directly from upstream. When the output is stalled and upstream is still offering, the skid stage captures one beat so up_ready need not drop in the same cycle dn_ready did.

Contract. Both boundaries keep the §6 rule: a presented beat is held until accepted. The buffer is what lets the Adapter honour it toward both neighbours simultaneously.

Failure/DV. Get the up_ready expression wrong and you either accept when full (§9, overflow → corruption) or refuse when space exists (throughput loss, no correctness failure). Get the reload priority wrong — taking from upstream while the skid holds an older beat — and you reorder payloads, which is a correctness failure the assertions in §9 do not catch and which needs a scoreboard.

9. Two Assertions the Buffer Needs

Illustrative buffer invariants — conceptual, not UCIe normative properties.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Overflow: never claim readiness upstream when there is nowhere to put
// the beat. Violation means an accepted payload overwrites an
// unconsumed one -- silent corruption, no wire-level rule broken.
property p_no_accept_when_full;
  @(posedge clk) disable iff (!rst_n)
    (out_valid_q && skid_valid_q && !dn_ready) |-> !up_ready;
endproperty
assert property (p_no_accept_when_full)
  else $error("adapter accepted a beat with no storage available");
 
// Underflow: never present a beat downstream that the buffer does not
// hold. Violation means transmitting stale or undefined payload.
property p_no_send_when_empty;
  @(posedge clk) disable iff (!rst_n)
    dn_valid |-> out_valid_q;
endproperty
assert property (p_no_send_when_empty)
  else $error("adapter asserted valid downstream with an empty output stage");

The overflow property catches the dangerous case: the buffer says "send me more" while both stages are occupied and nothing is draining. The beat that arrives overwrites one that was never consumed — the payload count stays plausible and the contents are wrong, which is the hardest class of bug to trace back. The underflow property is cheaper insurance against a control bug presenting garbage as valid data.

A distinction that causes real bring-up bugs, and the layered form of Chapter 3.2's reset argument.

rst_n rising means each layer's logic has left reset. It does not mean the link works. Between those two states sit: the PHY training the physical link, the Adapter running bring-up and negotiating parameters with the remote partner, and both sides agreeing an operating point. Until that completes, there is nothing for a payload to travel over.

So the Protocol Layer must not send merely because reset has deasserted. It needs an abstracted operational indication:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative adapter-facing link state -- NOT UCIe normative states.
typedef enum logic [1:0] {
  ADP_RESET,      // out of reset, nothing established
  ADP_WAIT_PHY,   // waiting for the physical link to become usable
  ADP_READY,      // negotiated and usable for protocol traffic
  ADP_ERROR       // fault detected; traffic not permitted
} 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_READY;
      ADP_READY    : if (link_fault)    adp_state_q <= ADP_ERROR;
      ADP_ERROR    : if (recovered)     adp_state_q <= ADP_WAIT_PHY;
      default      : adp_state_q <= ADP_ERROR;
    endcase
  end
end
 
// The single abstracted fact the Protocol Layer is allowed to consume.
assign link_operational = (adp_state_q == ADP_READY);

Architecture. The Adapter converts "the PHY says the physical link is up" plus its own negotiation status into one boolean the layer above can act on. That conversion is the abstraction.

State. One enum register. Everything the Protocol Layer needs to know about a multi-step bring-up is compressed into whether it equals ADP_READY.

Cycle behaviour. Reset forces ADP_RESET; the machine advances only when the PHY reports the link usable; a fault moves to ADP_ERROR and blocks traffic until recovery returns it to waiting.

Contract. The Protocol Layer's readiness must be gated on this, not on reset:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign up_ready = link_operational && adapter_has_space;

Failure/DV. Without the gate, protocol traffic is accepted during bring-up and lost exactly as in Chapter 4.1 §5. This property catches it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// No protocol traffic may be accepted before the adapter is ready.
property p_no_accept_before_adapter_ready;
  @(posedge clk) disable iff (!rst_n)
    (adp_state_q != ADP_READY) |-> !(up_valid && up_ready);
endproperty
assert property (p_no_accept_before_adapter_ready)
  else $error("traffic accepted before the adapter reached ADP_READY");

Here is the architectural mistake this chapter most wants you to recognise, because it looks like a sensible optimisation.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// BAD ARCHITECTURE: protocol behaviour derived from raw PHY lane state.
always_ff @(posedge clk) begin
  if (!rst_n)             protocol_mode_q <= MODE_NORMAL;
  else if (!phy_lane3_ok) protocol_mode_q <= MODE_DEGRADED;
end

It is not wrong in the sense of producing an immediate bug — it may work in the design where it was written. It is wrong architecturally, in four compounding ways:

  • The Protocol Layer now knows the PHY's implementation. It knows there is a lane 3. A PHY with a different lane count, different numbering, or a repair mechanism that transparently remaps lanes breaks it.
  • Physical changes force protocol redesign. The independence that justified the layering is gone: re-implementing the PHY for a different package now requires touching protocol logic.
  • The verification boundary collapses. The Protocol Layer can no longer be verified against an abstract model of the layer below, because it depends on details only a real PHY exposes.
  • Reuse suffers. This Protocol Layer is now bound to this PHY family rather than to a specification-defined boundary.

The correct structure converts physical conditions into a defined abstract capability or status at the layer that owns them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// PHY/Adapter reduce physical conditions to something the layer above
// can act on without knowing why.
assign link_degraded    = (usable_lane_count < NOMINAL_LANES);
assign link_operational = (adp_state_q == ADP_READY);

The Protocol Layer consumes link_operational and, if the architecture defines one, link_degraded. It never learns which lane failed, and it should not — that is the PHY's uncertainty to own, and reducing it to an abstract status is exactly what a layer is for.

12. A Cycle Walkthrough With Stalls

Tie the pieces together. Ownership sequence, not a latency claim.

  • Cycle 0. Reset has deasserted; adp_state_q == ADP_WAIT_PHY, so link_operational is low and up_ready is low. The Protocol Layer asserts up_valid with a payload and holds it — this is the contract working: it cannot be accepted, so it is not lost.
  • Cycle 1. phy_link_up asserts. On this edge adp_state_q becomes ADP_READY, so link_operational rises. The buffer is empty, so up_ready rises.
  • Cycle 2. up_valid && up_ready — the transfer fires. out_valid_q sets and out_data_q captures the payload. The Adapter now owns it.
  • Cycle 3. The PHY applies backpressure: dn_ready low. out_valid_q holds, out_data_q is stable. The Protocol Layer offers another payload; the skid stage absorbs it, so up_ready need not fall this cycle.
  • Cycle 4. Both stages are occupied and dn_ready is still low, so up_ready falls — correctly refusing a third beat. This is the moment p_no_accept_when_full protects.
  • Cycle 5. dn_ready rises. The output stage drains, reloads from the skid stage, and up_ready rises again.

Every state change is on a clock edge, in a named register, owned by a named layer. That is what "the architecture is real" means in practice.

13. Verification Architecture and a Debug Checklist

The layering pays off twice: once in design, once in debug.

Environments. A protocol environment drives real protocol traffic and terminates it with an abstract Adapter model. An adapter environment sources traffic from above and terminates below with a PHY model, and is where stall, error-injection, and buffer-corner testing belongs. A PHY environment covers training, transport, and status reporting. An end-to-end environment connects two full endpoints. Each uses models in place of the layers it is not testing, which is what keeps them tractable.

A durable debug checklist. When a payload sent by one die's Protocol Layer never arrives at the other's, walk the stack rather than guessing:

  1. Did the local Protocol Layer actually present it — up_valid asserted with the expected payload?
  2. Did the Adapter accept it — did up_valid && up_ready fire, and did occupancy set?
  3. Did the Adapter present it downstream — dn_valid with the expected transport unit?
  4. Did the PHY accept and transmit it?
  5. Did the remote PHY receive it and pass it upward?
  6. Did the remote Adapter accept, check, and reconstruct it?
  7. Did the remote Protocol Layer accept it?

The first step that answers no localises the defect to one layer or one boundary. Each check is observable at a boundary the architecture already defines — which is the practical reason specified inter-layer interfaces are worth having even inside a single vendor's endpoint.

14. Layer-Boundary Invariants Worth Holding

Collecting the contract in one place:

Protocol ↔ Adapter. Payload held stable while stalled. No acceptance unless the Adapter can retain it — storage, not optimism. No acceptance before the link is operational. Payload semantics preserved unchanged: lower layers may reframe, protect, and retransmit, never reinterpret.

Adapter ↔ PHY. No transmission before the physical link is usable. No overflow — never accept without storage. No underflow — never assert valid without a beat. Preserve the ordering the contract requires. Propagate errors and status upward through the defined abstraction rather than by exposing raw physical state.

These are architectural invariants for a layered transport, expressed at the level this chapter teaches. The specification's own normative requirements at FDI and RDI are richer and belong to the modules that cover those interfaces in detail.

15. Common Misconceptions

16. Understanding Check

17. Summary and What Comes Next

Each layer owns one kind of uncertainty — Protocol owns semantics, the Adapter owns reliable transport across the link, the PHY owns the physical channel — and that is the test for where a new function belongs. State follows ownership: transaction context in Protocol, boundary buffers and negotiated parameters and higher-level link state in the Adapter, lane training and health and serialisation in the PHY. Reliability bookkeeping is mode-dependent — the Adapter's when it provides CRC and retry, the Protocol Layer's in raw mode — so the layers are fixed while some responsibility assignments are not.

Two corrections matter for implementers. A PHY is a functional layer, not analog circuitry — it holds training state machines, lane mapping and repair, serialisation, clocking control, and sideband logic, all of it regular RTL. And the Adapter is not glue: it is the most state-rich layer in the stack.

At the boundaries, the contract is payload down, backpressure up, status abstracted upward. Threading readiness combinationally through every layer creates an endpoint-spanning timing path and, combined carelessly with valid derived from ready, a combinational loop — so storage at the Adapter both makes readiness honest and decouples the boundaries. Guard it with p_no_accept_when_full and p_no_send_when_empty, and remember that local invariants prove the boundary honest without proving ordering.

Finally, reset deasserted is not link operational. PHY training, adapter bring-up, and partner negotiation sit between them, so readiness must be gated on an abstracted status — and the Protocol Layer should consume link_operational, never phy_lane3_ok, because reducing physical conditions to abstract status is the entire job of a layer.

The boundaries are now sharp. Next the curriculum walks the stack once more as a complete protocol path, layer by layer, with the interfaces between them treated properly rather than abstracted:

Browse the full path on the UCIe tutorials index.