UCIe · Module 23
UCIe vs PCIe
Why two interconnects that share protocol semantics are not interchangeable links — normalising both stacks before comparing them, what PCIe-over-UCIe actually means and why it is not PCIe signalling between dies, where latency really comes from, and the RTL layering mistake that makes a protocol engine impossible to move to a different transport.
Module 23 compares UCIe with the rest of the high-bandwidth interconnect family. Every chapter starts the same way: by making sure the two things being compared occupy the same architectural layer.
1. The One-Sentence Model
PCIe connects components across a platform. UCIe connects dies inside a package. They can carry related protocol semantics and they solve different physical problems at different scopes — which is why "UCIe is faster PCIe" is not a simplification but a category error.
The interesting part is the duality. The two are most similar at the top of the stack, where a transaction has a meaning, and most different at the bottom, where a channel has a length. Comparisons go wrong when a reader takes a fact from one end and applies it to the other.
2. The Module 23 Principle
A fair interconnect comparison starts by making sure the two things occupy the same architectural layer.
Four comparisons that are invalid on their face, and you will meet all of them:
| Invalid comparison | Why |
|---|---|
| a UCIe PHY figure vs a complete PCIe system capability | PHY vs whole stack |
| PCIe transaction semantics vs UCIe physical scope | semantics vs physics |
| a raw line rate vs a delivered payload rate | different quantities |
| one generation of A vs a different generation of B | no common baseline |
So every chapter in this module does layer normalisation before any table — §5 is this chapter's.
3. What This Chapter Owns
| Question | Where |
|---|---|
| The UCIe stack — Protocol Layer, D2D Adapter, PHY, FDI/RDI | 19.1 — Link Architecture |
| Protocol mapping — native, Streaming, Raw Mode | 19.2 — Protocol Engines |
| Latency anatomy · bandwidth · package performance | 15.2 · 15.1 · 15.4 |
| Error detection, recovery, epochs | 14.1 · 14.2 |
| Verification environments and compliance | 20.2 · 20.7 |
| Open standard vs proprietary D2D | 23.2 (next) |
Three things are new here:
Layer normalisation as a method (§5–§6), reusable for every remaining comparison.
PCIe-over-UCIe (§8–§9) — the fact that dissolves most of the confusion, and the distinction it forces between PCIe as a protocol and PCIe as an electrical link.
And the layering failure in RTL (§14–§16): a protocol engine that reaches into transport state cannot be moved to a different transport, and the bug is invisible until you try.
4. Sourcing and Evidence Asymmetry
5. Normalising the Two Stacks
Three things to read.
The rows are aligned by responsibility, not by name. PCIe's Data Link Layer and UCIe's D2D Adapter are not the same thing; they occupy the same row because both answer "who guarantees delivery?" — and that is the only basis on which they can be discussed together.
Comparison is valid across a row and invalid down a column. Every bad comparison in §2 is a diagonal.
And the bottom row is where the architectures genuinely diverge — one reaches across a platform, the other across a package. Every physical difference in §10 follows from that row, and no protocol difference does.
6. What Each Layer Owns
| Row | PCIe | UCIe |
|---|---|---|
| semantics | transaction meaning, ordering, completion | carries a protocol — PCIe and CXL natively mapped |
| reliability | Data Link Layer: correct, in-order delivery; flow control; acknowledgement | D2D Adapter: CRC + link-level retry, described as optional |
| physical | serial signalling over a platform channel | die-to-die signalling over a package channel |
| scope | component to component | die to die |
Two properties worth pausing on.
Row 2's asymmetry is real and is stated in the sources. PCIe's Data Link Layer is described as responsible for correct in-order delivery; UCIe's Adapter guarantee is described as optional (19.3). That is an architectural difference at an equivalent layer — the most legitimate kind of comparison this chapter can make.
And row 1 is where they touch rather than compete. UCIe does not define transaction semantics of its own for a natively mapped protocol; it carries PCIe's. §8.
7. What Each Deliberately Does Not Define
Half of an architecture is what it leaves to someone else — and this table is where most "A vs B" arguments quietly dissolve.
| Not defined by PCIe | Not defined by UCIe |
|---|---|
| what is inside a device | the semantics of the protocol it carries |
| how a die is partitioned internally | CPU cache coherence (22.2 §15) |
| package construction | the software/device model above it (§12) |
| — | management, power sequencing, security (22.5 §5) |
And the right-hand column is why UCIe cannot "replace" PCIe in any general sense (§9): it does not define what a transaction means. For a natively mapped protocol, something else does — and for PCIe-over-UCIe, that something else is PCIe.
8. PCIe Over UCIe
The single fact that resolves most confusion in this chapter.
Official UCIe material describes PCIe as a natively mapped protocol. So a PCIe transaction can cross a UCIe die-to-die boundary — and when it does, it is not using a board-level PCIe PHY between the dies.
The distinction the whole chapter turns on:
| PCIe as a protocol | PCIe as an electrical link | |
|---|---|---|
| what it is | transaction semantics, ordering, completion | serial signalling over a platform channel |
| where it can live | above any transport that preserves its contract | on a PCIe PHY |
| in a package | yes — natively mapped over UCIe | not the point of a D2D link |
| software sees | the same device model | — |
Four readings.
"Carrying PCIe" and "being PCIe" are different claims. A UCIe link carrying PCIe transactions runs a UCIe PHY over a package channel — 22.2 §11's layering argument with a specific protocol named.
This is the reason the two are so often confused. They genuinely share the top row of §5. The confusion is not ignorance — it is a real architectural overlap being over-extended.
It also explains why the swap is attractive. Software above a natively mapped protocol need not change: the semantics are preserved and only the transport underneath moved (§14's whole point).
And it bounds the claim. PCIe-over-UCIe says nothing about PCIe outside the package, where a board-level link is doing a job a die-to-die link cannot do at all (§10).
9. The Wrong Mental Model
WRONG: "UCIe replaces PCIe."
BETTER: UCIe may replace a PROPRIETARY DIE-TO-DIE TRANSPORT beneath a
protocol, while PCIe semantics continue above it unchanged.
PCIe remains the thing that connects components across a platform.
A die-to-die link does not reach across a platform, and is not
trying to.Three properties.
The replaced thing is a transport, not a protocol — and frequently a proprietary one (23.2), which is the comparison that actually makes sense.
PCIe's platform role is untouched by any of this. Devices, slots, add-in cards, and the reach they need are outside a package by definition.
And the "replacement" framing hides the interesting question, which is 22.2 §11's: which layers move? A design can adopt a standard PHY and keep everything above it — and a press sentence saying "supports UCIe" does not say which.
10. Physical Scope Drives Everything Below the Protocol
| Consequence | Platform link | In-package link |
|---|---|---|
| channel | connectors, board traces, longer reach | short, controlled, co-designed |
| PHY complexity | equalisation and recovery for a hostile channel | less channel compensation needed |
| energy per bit | dominated by driving that channel | lower — that is the point of moving in-package |
| width | serial, few lanes, high rate | very wide, lower rate per wire |
| hot-plug / removability | a design requirement | not applicable |
| topology | trees, switches, a device ecosystem | a fixed graph, fixed at design time |
| failure model | a device can be absent, removed, or fail alone | dies fail with the package |
Two readings.
Every row follows from the bottom row of §5, not from any protocol decision — which is why they are architectural differences rather than quality differences.
And rows 5–7 are the ones that make "replacement" incoherent. A die-to-die link has no hot-plug story, no device-absence story and no switch topology, because it never needed one. Those are not deficiencies; they are scope.
11. Where Latency Actually Comes From
ILLUSTRATIVE symbolic decomposition. No numeric value for either technology
appears in this chapter (§4).
end_to_end = t_protocol_processing (build/parse the transaction)
+ t_queue (arbitration, buffering)
+ t_serialize (payload / link width)
+ t_phy (transmit + receive path)
+ t_flight (channel length)
+ t_reliability (CRC check, retry state)
+ t_bridge (any translation on the path)
+ ...the same terms on the return path
A SHORTER CHANNEL reduces t_flight and can reduce t_phy.
A WIDER LINK reduces t_serialize.
It does NOT reduce:
t_protocol_processing, t_queue, t_reliability, t_bridgeFour readings, and the third is the one people skip.
Moving in-package removes some terms and not others. 15.2's anatomy: protocol processing and queueing are the same work wherever the wire is.
A bridge is a term, and bridges are common. Translating between a protocol and a transport adds latency on both sides — so a design that moves a boundary in-package and inserts two bridges may not improve end-to-end latency at all.
Queueing frequently dominates under load, and it is a function of provisioning and flow control (13.1), not distance. A congested short link is slower than an idle long one.
And latency improvement does not imply system improvement (22.4 §8): a workload with enough independent work hides latency; one on a dependent chain cannot hide any of it. Which one you have decides whether the change is worth anything.
12. Physical Boundary Is Not Software Boundary
| Platform device | In-package die | |
|---|---|---|
| discovery | explicit, host-visible enumeration and configuration | may be invisible — the package presents one device |
| identity in software | a device with configuration state | possibly none |
| removability | modelled | not applicable |
| fault reporting | to the platform's error architecture | may be contained inside the package (22.4 §18) |
Three properties.
A die-to-die boundary can be completely invisible to software. Two dies can present a single device model, and nothing above needs to know there are two.
Which means the physical partitioning and the software model are independent choices — and a product can change its partitioning across generations without changing its software model at all, which is a large part of chiplets' commercial appeal.
And it inverts a common assumption. "There is a standard link there, so software must see something standard" is false: the link is a transport, and the software model is an entirely separate agreement (22.5 §5).
13. Why This Chapter Has No Bandwidth Table
A numeric comparison is only meaningful when every one of these is pinned:
| Condition | Why |
|---|---|
| both numbers measure comparable quantities | aggregate vs per-lane vs per-direction |
| generation / revision stated for both | otherwise it is two eras |
| physical topology and scope stated | one link vs a whole system |
| units match | — |
| raw line rate vs delivered payload made explicit | encoding and framing overhead differ |
| both from reliable sources at a comparable level | §4's asymmetry rule |
From Level B material I cannot pin the last four for both sides simultaneously, so this chapter states no figure for either (§4).
And the omission is the lesson. 21.5 §42's denominator discipline applies to comparison exactly as it applies to measurement: a number without its scope, generation and definition is not a measurement, and putting two of them in a table does not make them comparable.
14. Illustrative — Protocol Above Transport
// ILLUSTRATIVE ONLY. A protocol-layer request expressed WITHOUT reference to
// any transport. This is the object §8 says survives a transport change.
typedef enum logic [2:0] {
SEM_READ, SEM_WRITE, SEM_COMPLETION, SEM_MESSAGE
} sem_kind_e;
typedef struct packed {
logic [SEM_W-1:0] sem_id; // the SEMANTIC operation identity
logic [GEN_W-1:0] generation; // which USE of that id (22.4 §12)
sem_kind_e kind;
logic [ADDR_W-1:0] addr_token; // opaque; NOT an architectural address
logic [LEN_W-1:0] length;
logic [DOM_W-1:0] order_domain; // ordering scope (21.6 §16)
logic requires_completion;
} sem_req_t;
// The transport-agnostic contract. A transport implements THIS and nothing
// above it may know which transport is behind it.
//
// sem_valid / sem_ready -- acceptance, not offering (21.5 §20)
// sem_req -- the object above
// sem_delivered -- the transport confirms SEMANTIC delivery
// sem_delivered_id/gen -- which object, unambiguously
//
// Note what is ABSENT and must stay absent: lane counts, training state,
// retry counters, PHY status, link width. §15 is what happens when they leak.Architecture. One semantic object and one narrow contract, so the protocol layer can be paired with either transport of §16 without modification.
State. None here — the object is carried; the transports hold the state.
Cycle/event behaviour. sem_valid && sem_ready is the acceptance event; sem_delivered is a separate, later event carrying the identity. Two events, because acceptance and delivery are different facts and conflating them is 21.4 §5's arrow-2/arrow-9 distinction.
Contract. The transport must confirm semantic delivery, not physical transmission. A transport that signals delivery on transmit has told the protocol layer something false, and §17 is the failure.
Failure. Adding a single transport-specific field here — a lane index, a retry count — couples the layers permanently, and §15 shows what that costs.
DV/debug. This boundary is exactly where a monitor sits (20.2): observing it gives a transport-independent transaction stream, which is what makes one scoreboard work against two transports.
15. Wrong RTL — Transport Leaking Into the Protocol Layer
// WRONG — the protocol engine reads transport internals directly. Every line
// here is individually reasonable and the combination is a layering failure.
always_comb begin
// Reason 1: sizing a burst from the PHY's width.
max_burst = phy_lane_count * BYTES_PER_LANE;
// Reason 2: not issuing while the link is retraining.
can_issue = (phy_train_state == PHY_ACTIVE);
// Reason 3: treating transport retry as protocol back-pressure.
throttle = (transport_retry_count > RETRY_THRESHOLD);
// Reason 4: deriving a timeout from the transport's own recovery timer.
sem_timeout = transport_recovery_timer + FIXED_MARGIN;
endWhat each line costs, precisely:
| Line | The coupling | What breaks on a transport change |
|---|---|---|
max_burst | protocol sizing depends on PHY width | a different width silently changes transaction sizes |
can_issue | protocol gated by PHY training state | a transport with different states cannot drive it |
throttle | protocol responds to transport retry | a working retry mechanism becomes protocol back-pressure |
sem_timeout | protocol lifetime depends on a transport timer | timeouts become wrong, and §17's corruption follows |
Four properties.
Nothing here is a bug today. In a single-transport design every line works, and the design ships.
The cost appears only when the transport changes — which is precisely the scenario §8 makes attractive. The protocol engine that was supposed to be portable is not, and moving it means re-deriving four unrelated behaviours.
The third row is the most damaging. 21.6 §19: a transport retry is the reliability mechanism doing its job, and a protocol layer that throttles on it converts a healthy correction into a performance problem — and hides the real signal-integrity cause (21.7 §32).
And the fix is not to delete the couplings but to replace them with contract terms the transport publishes on its own boundary — §16.
16. Corrected RTL — Two Transports, One Protocol Layer
// CORRECTED. Everything the protocol layer needs is published BY the transport
// as part of its contract — no transport internals are read.
typedef struct packed {
logic [15:0] max_object_bytes; // replaces max_burst — a CONTRACT value
logic accepting; // replaces train-state peeking
logic [15:0] completion_bound; // replaces the borrowed timer
logic retains_across_recovery; // §17's field — the one that matters
} transport_caps_t;
// The protocol layer now reads ONLY this.
always_comb begin
max_burst = tcaps.max_object_bytes;
can_issue = tcaps.accepting;
sem_timeout = tcaps.completion_bound;
throttle = 1'b0; // transport retry is INVISIBLE here, by contract
end
// Transport A — a platform-style serial transport.
transport_caps_t caps_serial;
assign caps_serial = '{ max_object_bytes: SERIAL_MAX_BYTES,
accepting: serial_link_up,
completion_bound: SERIAL_COMPLETION_BOUND,
retains_across_recovery: 1'b1 };
// Transport B — an in-package die-to-die transport.
transport_caps_t caps_d2d;
assign caps_d2d = '{ max_object_bytes: D2D_MAX_BYTES,
accepting: d2d_link_up,
completion_bound: D2D_COMPLETION_BOUND,
retains_across_recovery: 1'b0 }; // <-- DIFFERENT (§17)
assign tcaps = USE_D2D ? caps_d2d : caps_serial;Architecture. A capability record published by the transport, so the protocol layer depends on stated values rather than on observed internals.
State. None in the protocol layer; each transport holds its own.
Cycle/event behaviour. accepting is a contract signal the transport drives; the protocol layer never sees a training state or a retry count.
Contract. retains_across_recovery is the field that carries the semantics of §17, and it exists because the two transports genuinely differ. A capability record that omits a difference does not remove the difference — it hides it, which is 22.5 §4's unstated-assumption failure.
Failure. If the protocol layer assumes one value of retains_across_recovery rather than reading it, swapping the transport produces §17 — the same protocol, the same semantics, and orphaned transactions.
DV/debug. One scoreboard and one monitor now work against both transports, because the observable boundary is identical (20.4 §17) — which is the practical payoff of the whole refactor.
17. Failure — Same Protocol, Different Recovery Assumption
The subtlest failure in the chapter, and it survives every unit test.
| Step | What happens |
|---|---|
| 1 | protocol layer accepts semantic object X, expects a completion |
| 2 | the transport takes a link event and recovers |
| 3 | transport A would have retained X's obligation across recovery |
| 3′ | transport B discards it — X's transport state is gone |
| 4 | the protocol layer is still waiting for X's completion |
| 5 | its timeout was derived from the old transport's bound (§15) and is too long |
| 6 | X is orphaned: never completed, never failed, holding its identity |
| 7 | eventually the identity is reused (22.4 §11) — and now a stale completion can corrupt a live request |
Four readings.
The protocol did not change and the semantics did not change. Only an unstated assumption about what a transport recovery means to a live obligation changed — 21.4 §22's epoch reconciliation, arriving through a portability change.
Step 6 is a silent hang: no error, no fault, no link event visible to the protocol layer. The link is up and healthy.
Step 7 turns a hang into corruption, which is why 22.4 §12's generation tagging is the second half of the fix — the first half is stating the assumption at all.
And the general rule is that a portable protocol layer must treat "what survives recovery" as an explicit contract term, never as something it learned from the transport it happened to be built against.
18. Assertions
// MANDATORY. Illustrative architectural properties (§4) — not normative for
// either technology.
// (1) ACCEPTED WORK SURVIVES A RECOVERY THAT CLAIMS TO RETAIN IT.
// English: if the transport advertises retention, a semantic object accepted
// before a recovery must still be live after it. Sampled at recovery exit.
// Catches §17 in the configuration where retention was PROMISED.
a_retained_survives_recovery: assert property (
@(posedge clk) disable iff (!por_n)
($fell(in_recovery) && tcaps.retains_across_recovery)
|-> (live_obj_count == $past(live_obj_count, RECOVERY_SAMPLE_DEPTH))
);
// (2) A PHYSICAL RETRY IS NOT A NEW SEMANTIC REQUEST.
// English: however many times the transport retransmits, the protocol layer
// observes one acceptance. Catches the §15 `throttle` coupling and 21.6 §19's
// duplicate-delivery misreading.
a_retry_invisible_to_protocol: assert property (
@(posedge clk) disable iff (!por_n)
transport_retry_fire |-> !sem_valid_rise
);
// (3) THE PROTOCOL LAYER NEVER READS TRANSPORT INTERNALS.
// English: protocol-layer control depends only on the capability record.
// This is a NON-INTERFERENCE property — best discharged formally, because
// simulation only shows it held for the stimulus you ran (21.7 §17).
a_no_transport_coupling: assert property (
@(posedge clk) disable iff (!por_n)
$stable({phy_lane_count, phy_train_state, transport_retry_count})
|| $stable({max_burst, can_issue, sem_timeout})
);
// (4) CONFIG EPOCH IS STABLE FOR A LIVE OBJECT.
// English: an object's configuration context cannot change underneath it.
// Catches 21.6 §27's mid-flight mutation, which a transport swap makes newly
// possible.
a_epoch_stable_for_live_object: assert property (
@(posedge clk) disable iff (!por_n)
(live_obj_valid && $changed(cfg_epoch_q)) |-> in_quiesce
);Architecture. Two behavioural contracts, one structural non-interference property, one lifetime property.
State. The live-object count and the epoch register.
Sampled timing. Property (1) samples on $fell(in_recovery) — the recovery exit edge, because that is when retention is observable; sampling during recovery would compare against a transient. Property (3) is written as a stability implication so it is formally dischargeable; as a simulation property it proves only what the stimulus explored.
Contract. RECOVERY_SAMPLE_DEPTH must be derived from the recovery's own duration — a guessed value makes (1) either vacuous or constantly false, which is 21.6 §29's bounded-window trap.
Failure if omitted. Without (1), §17 ships. Without (2), a healthy retry mechanism is throttled and the real cause is masked. Without (3), the layering can regress at any time and nothing notices until a transport swap is attempted years later.
DV/debug. Property (3) is the one to run in a formal flow. Non-interference is a proof obligation, and it is the only one of the four that protects the architecture rather than a behaviour.
19. Choosing the Boundary
| Question | Reading |
|---|---|
| is the boundary inside a package, between dies? | a die-to-die link is the candidate class |
| is it between components across a platform? | a platform serial link is the candidate class |
| do you need PCIe semantics at an in-package boundary? | PCIe-over-UCIe is architecturally relevant (§8) |
| do you need device discovery, hot-plug, removability? | that is a software and platform model, not a transport choice (§12) |
| is the boundary crossing an organisation? | the standard-versus-proprietary question — 23.2 |
And the framing that matters more than any row: the question is "what architectural boundary am I trying to standardise?" — not "which interconnect is fastest?"
20. Common Misconceptions
"UCIe is just faster PCIe." §5: they align at the top and diverge at the bottom. The differences are scope, not speed.
"UCIe replaces PCIe everywhere." §9: what gets replaced is a transport beneath a protocol — frequently a proprietary one. PCIe's platform role is untouched.
"PCIe over UCIe means PCIe electrical signalling between dies." §8: it means PCIe semantics carried over a UCIe PHY and package channel. Protocol ≠ electrical link.
"Shorter reach means near-zero latency." §11: a short channel removes t_flight and can reduce t_phy. Protocol processing, queueing, reliability and bridges are unchanged.
"A PCIe device and a UCIe-connected die have the same software model." §12: a die-to-die boundary can be entirely invisible to software.
"If both support PCIe semantics, the PHYs are interchangeable." §10: hot-plug, removability, topology and channel assumptions differ completely.
"Raw GT/s is enough to compare links." §13: without generation, topology, directionality and raw-versus-payload, two numbers in a table are not a comparison.
"Lower latency automatically means better system performance." §11, 22.4 §8: only if the workload cannot already hide it.
21. Understanding Check
22. Summary
Five things.
Normalise the stacks before comparing (§5). Align by responsibility, compare across a row, and treat every diagonal as invalid.
They align at the top and diverge at the bottom (§6, §10). Semantics can be shared; channel, reach, topology, removability and failure model cannot.
PCIe-over-UCIe is the fact that dissolves the confusion (§8) — and it forces the distinction between a protocol and an electrical link, which is the single most useful idea in the chapter.
Latency is a sum of terms and a shorter channel removes only some of them (§11). Protocol processing, queueing, reliability and bridges are unchanged by distance.
And a portable protocol layer depends on published contract values, never on transport internals (§15–§17) — with "what survives recovery" as the term that, left unstated, produces a silent orphan and then a corruption.
On evidence: neither base specification was inspected — UCIe requires registration, PCIe requires PCI-SIG membership. Level B on both sides, Level A on neither, which is why this chapter contains no numeric comparison at all (§13). That symmetry does not survive into the next chapters.