Skip to content

UCIe · Module 28

“UCIe Is Only For CPUs”

A role is not a boundary — the smallest counterexample has no CPU on the link at all. Why CPU-shaped assumptions get compiled into transport RTL, the mixed-traffic deadlock they produce, and the closing synthesis of the whole UCIe curriculum.

The last of the six beliefs, and the only one that is a claim about applicability rather than about architecture. It is also the one that most often goes unstated — it shows up as a scoping decision, a reuse assumption, or an interview answer that stops one sentence early.

1. The Belief

"UCIe is a CPU chiplet technology. It's not really relevant to GPUs, accelerators, memory-side logic or I/O."

The belief is rarely defended, which is what makes it durable. Nobody argues for it; it simply shapes what gets considered — which link IP is evaluated for reuse, which traffic patterns a transport is verified against, and whether a design team believes a boundary they are building is the same kind of boundary as the one in the reference material.

The replacement model (§6):

UCIe standardises a die-to-die boundary. The function behind that boundary is an architectural choice. A CPU is an endpoint role; the boundary is a position in a system — and a role can be substituted without the position changing.

And the cost is concrete (§11–§13): CPU-shaped assumptions get compiled into transport RTL, where they are invisible until someone tries to reuse the link for an accelerator and discovers the transport has opinions about cache opcodes and request sizes.

2. Why a Competent Engineer Believes It

Five reasons, all of them legitimate history rather than carelessness.

The public examples are overwhelmingly CPUs. Multi-die server processors were the first chiplet architectures with detailed public documentation, and they remain the most cited. 26.1 is a CPU; the most-reproduced chiplet diagrams in the industry are CPU diagrams. An engineer learning from available material learns from CPU material.

Protocol mapping is host-centric in its framing. The protocols most discussed in a UCIe context are ones whose canonical topology has a host at the root (25.5). The vocabulary — host, device, root, endpoint — imports a CPU into every sentence, even when the actual system has none.

Teaching sequences start with CPU plus I/O die. It is the clearest first example: two dies, obviously different functions, obviously different process needs. Every curriculum's first example becomes some readers' definition — including this one's, which is why this chapter exists.

The first successful use case gets mistaken for the scope. This is a general failure of technology reasoning and not specific to chiplets. A standard's early adopters shape its perceived boundary long after its actual boundary is wider.

And organisationally, the CPU team often owns the link IP. If the die-to-die interface was built by the CPU group for a CPU product, its assumptions, its verification plan and its performance model are CPU-shaped — and the next team to reuse it inherits all three without being told.

3. The Hidden Assumption

That a standard's scope is defined by the function of the products that adopted it first, rather than by the architectural boundary it standardises.

Two sub-assumptions.

A — that a role and a position are the same kind of thing. CPU answers "what does this die compute?" Die-to-die boundary answers "where does this die stop?" They are orthogonal, and no amount of specifying one constrains the other.

B — that if the examples share a property, the property is required. Every published example having a CPU is evidence about who published, not about what is structurally necessary. §7 removes the CPU and shows every requirement surviving.

4. The Root-Cause Tree

LevelWhat happens
belief"UCIe is for CPU chiplets"
assumption Arole and boundary are the same kind of thing
assumption Bshared properties of examples are requirements
architecture decisionthe transport is specified against CPU traffic — short request/response, bounded outstanding, predictable sizes
second decisionno traffic-class separation, because CPU traffic is fairly homogeneous
RTL decisionCPU semantics are read directly by the transport controller (§11), and one shared queue serves everything (§13)
DV decisionthe environment models CPU-shaped stimulus only (27.3 §16)
observed failurereuse for an accelerator deadlocks under mixed traffic (§13)
misleading symptom"the accelerator is overloading the link" — framed as a workload problem
first divergencethe cycle the last shared queue entry was taken by bulk traffic while a completion was pending
corrected model§6, §12, §14 — role-neutral transport, and resources reserved by class

The second decision is the one that survives every attempt to fix the first. A team can strip CPU opcodes out of the transport (§12) and still deadlock, because homogeneous-traffic assumptions live in the resource structure rather than in the field definitions.

5. Sourcing and Scope

6. Role Is Not Boundary

The correction is a type distinction, and once it is made the belief has nowhere to stand.

RoleBoundary
answerswhat does this die compute?where does this die stop?
examplesCPU, GPU, accelerator, memory-side, I/Odie-to-die, in package
chosen bythe product's purposethe partition (25.7)
varies independently?yesyes
what the specification's scope names"the interconnect between chiplets within a package" (§5)

Three readings.

The Consortium's own scope sentence names a boundary and no function. "Between chiplets within a package" is a position. Nothing in it says what the chiplets do, and a scope statement is the natural place to say so if it mattered.

The two axes are orthogonal, which is testable rather than rhetorical. Fix the role and vary the boundary: a CPU can be monolithic, two dies side by side, or stacked. Fix the boundary and vary the role: the same die-to-die position can join two compute dies, a compute die and a memory-side die, or an I/O die and an accelerator. Neither substitution changes the other, which is what "independent" means.

And the belief is a category error of the same family as 28.1's. That chapter fused semantics to physical scope; this one fuses application role to boundary position. In both cases two orthogonal axes were collapsed into one, and in both cases the repair is to name them separately before arguing.

7. The Minimal Counterexample

Remove the CPU entirely and check whether anything the boundary needed disappears.

The system (conceptual — not a product implementation): an AI accelerator compute die joined to a memory-side die carrying memory controllers and a large cache. Requests flow from compute to memory-side; data flows back. There is no CPU on this link. A host exists somewhere in the platform and is not a participant in this boundary.

Requirement the boundary hasDoes removing the CPU remove it?
die-to-die transport with a framing and integrity modelno
capability discovery and parameter negotiationno (28.2 §6)
a named owner for delivery guaranteesno (28.2 §4)
flow control with a conservation invariantno (25.6)
semantic identity stable across transport attemptsno (25.5 §11)
a decided retention policy across recoveryno (27.5 §8)
link state management and a readiness predicateno (26.5 §13)
configuration ownership and atomic commitno (26.5 §17)
an integration contract with a named owner per rowno (27.5 §6)

Nine requirements. The CPU's removal eliminates none of them.

Three readings.

Every problem this curriculum spent 155 chapters on is still present, which is the strongest possible form of the argument: the CPU was never load-bearing for any of it. If it had been, at least one row would have changed.

And one row gets harder without a CPU, not easier. Flow control and outstanding-window sizing (§17) are more demanding for sustained accelerator-to-memory traffic than for a CPU's request/response pattern, because the bandwidth-delay product is larger and the traffic is less self-limiting (26.4 §14). The non-CPU case is not a degenerate case of the CPU one.

The counterexample is conceptual and that is exactly the right form (§5). It establishes structural necessity — whether a CPU is required — and no product citation could establish that better. A product would show one case; the construction shows the CPU is not in the dependency set at all.

8. When the Belief Is Locally Useful

The simplificationWhere it holdsWhere it breaks
"start with the CPU example"teaching and first exposure — it genuinely is the clearest first partitionwhen the first example becomes the definition
"our link IP was built for CPU traffic"a true and useful statement about a specific IP blockwhen read as a statement about the standard
"host/device vocabulary"protocols whose topology really does have a host at the rooton a boundary with no host on it (§7)

Two readings.

Row 2 is the most operationally important, and it is the one worth saying out loud in a reuse discussion. "This transport was specified against CPU traffic" is accurate, valuable and actionable — it tells the next team exactly what to re-verify. The failure is silent inheritance: reusing the IP without anyone repeating the sentence.

And row 1 deserves a defence rather than an apology. CPU-plus-I/O-die is the right first example — two dies, obviously different functions, obviously different process needs (25.2 §5). The problem is not the example; it is that no chapter afterwards ever said "and the CPU was incidental." This one does.

9. Role Versus Transport — What Actually Changes

Same boundary, different semantics above it. The table is the architectural content of this chapter.

RoleDominant trafficLatency sensitivityBandwidth patternOrdering / coherence needFirst system concern
CPU computeshort request / responsehigh — tail latency dominatesmodest, burstyoften strong, often coherenttail latency
AI computebulk reads, collective phaseslow per transfer, high per phasesustained, very highusually weaker; phase barrierssustained bandwidth + sync (26.3 §17)
memory-siderequests in, data outmoderatedirectionally asymmetricordering within a domainoutstanding window + channel balance (26.4 §14)
I/Odescriptors, doorbells, DMAmixed — control is latency-criticalbursty, small + large mixedproducer/consumer orderingprogress for control traffic (§13)
networkingpacket streams, small transfershigh for controlsmall-transfer dominatedper-flow orderingper-packet overhead
security / managementsmall, infrequent, privilegedhighnegligiblestrictguaranteed progress under load

Three readings, and none of this is a claim about what UCIe requires (§5) — it is a claim about what different functions do.

The columns vary enormously and the boundary does not. Every one of these roles needs framing, flow control, identity and recovery. What differs is the resource profile they impose — and that is the part a CPU-shaped transport gets wrong (§13).

Rows 2 and 6 are the pair that produces the deadlock. AI compute saturates shared resources by design; management traffic is small, rare and must make progress. Put them on one queue and the second is starved by the first. Neither is misbehaving.

And row 3 is a reminder from 26.4 §16: a memory-side boundary is directionally asymmetric — small requests one way, full lines the other. A transport budgeted symmetrically because CPU traffic looked symmetric is wrong in one direction.

10. RTL — a Role-Neutral Die Boundary

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE ONLY. What crosses a die boundary, expressed so that the
// transport can route, account and retry WITHOUT knowing what the payload
// means. The traffic class is the one semantic hint the transport needs —
// and it is deliberately an abstract service class, not an application role.
typedef enum logic [2:0] {
  TC_CONTROL,      // small, rare, must make progress
  TC_MEMORY,       // request/response to a memory-side function
  TC_COHERENCE,    // ordered, completion-bearing
  TC_BULK,         // large, throughput-oriented, latency-tolerant
  TC_COMPLETION    // returns; must never be blocked by TC_BULK (§13)
} traffic_class_e;
 
typedef struct packed {
  // SEMANTIC — owned above the transport, stable for the operation's life
  logic [ID_W-1:0]    semantic_id;
  logic [GEN_W-1:0]   generation;
  traffic_class_e     traffic_class;   // a SERVICE class, not an application role
  logic [DST_W-1:0]   destination;     // a logical function, not a link index
  logic [EPOCH_W-1:0] config_epoch;
  logic [LEN_W-1:0]   bytes;
  // TRANSPORT — owned below; never written by semantic logic
  logic [ATT_W-1:0]   attempt;
} die_transaction_t;

Architecture. One record whose fields are grouped by owner, not by application. The transport reads traffic_class, destination, bytes and attempt — four fields, none of which encode what the die does.

State. One entry per outstanding operation in the transport's tracking table.

Event. Constructed at acceptance (valid && ready); attempt increments on retransmission. semantic_id and generation are never written by a transport event (28.1 §11).

Contract. The transport promises delivery-or-fault and promises not to interpret the payload. The layer above promises that traffic_class accurately describes the operation's service needs — which is the one thing the transport genuinely must know to schedule (§14).

Failure. If traffic_class were replaced by an application-role field — "CPU", "accelerator"the transport would be scheduling by identity rather than by need, and a CPU issuing bulk traffic would be mis-scheduled. The abstraction is service class precisely because roles and service needs are not 1:1.

DV/debug. destination being a logical function rather than a link index is what survives a route change (26.5 §19). And a trace carrying attempt alongside one semantic_id is the system working, not a duplicate — which is what stops an engineer filing a bug against a correct retry.

11. Wrong RTL — CPU Semantics Compiled Into Transport

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG. ILLUSTRATIVE. A transport controller that reads CPU-specific fields
// directly. Every line is reasonable if you know the product is a CPU: it
// prioritises snoops, it respects privilege for a management path, it sizes
// buffers from the known request size. The defect is that the TRANSPORT knows
// all of it.
always_comb begin
  // BUG 1: the transport decodes a CPU cache opcode to decide priority.
  //        A GPU or accelerator has no such opcode, and this field would be
  //        reinterpreted or hardwired — silently changing scheduling.
  tx_priority = (req_cache_opcode inside {OP_SNOOP, OP_SNOOP_RESP}) ? PRI_HIGH
              : (req_cache_opcode == OP_WRITEBACK)                  ? PRI_LOW
                                                                    : PRI_MED;
 
  // BUG 2: the transport gates admission on a CPU privilege level. A
  //        memory-side or accelerator die has no core privilege concept.
  tx_admit    = (req_priv_level >= PRIV_SUPERVISOR) || !mgmt_path_busy;
 
  // BUG 3: the transport derives its buffer accounting from a CPU core id,
  //        so per-source fairness is defined over cores rather than over
  //        logical sources. A single-source accelerator collapses to one bin.
  tx_bin      = req_core_id[BIN_W-1:0];
 
  // BUG 4: the transfer size is assumed to be a cache line. Bulk traffic is
  //        fragmented into line-sized pieces by a layer that should not know
  //        what a line is.
  tx_beats    = CACHE_LINE_BYTES / LANE_BYTES;
end

Architecture. A scheduler, an admission gate, a fairness binning function and a segmentation rule — all four reading CPU-specific fields.

State. None added; the damage is in the dependency set, not in storage. That is why it survives review: there is no suspicious register, only four assign-style expressions that each look sensible.

Event. Evaluated per request.

Contract. It silently requires that every die on this boundary has cache opcodes, privilege levels, core IDs and cache-line-sized transfers. None of that is written down, because in a CPU product all four are simply true.

Failure — what happens at reuse. The link IP is selected for an accelerator-to-memory-side boundary.

Attempt to reuseResult
tie req_cache_opcode to a constantall traffic gets one priority — §13's deadlock becomes reachable
map accelerator ops onto cache opcodesan arbitrary encoding nobody documents; scheduling becomes accidental
tie req_priv_level highthe admission gate stops gating; the management path loses its protection
req_core_id has no analogueall traffic lands in one fairness bin — per-source fairness ceases to exist
bulk transfers segmented to cache linesoverhead per useful byte rises; §17's efficiency argument

Root cause. The transport was specified against a role instead of against a service model. No individual line is a bug; the dependency direction is. And the fix is not to add accelerator fields alongside the CPU ones — that produces a transport that knows about two roles instead of one, and fails on the third.

DV/debug. The tell in review is 27.2 §5's traceability pass: name the architectural decision that says the transport may read req_cache_opcode. There is none — it was never a decision, it was an availability. A field that is in scope because it happened to be on the bus is the signature of this defect.

12. Corrected — a Role-Neutral Transport Contract

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECT. ILLUSTRATIVE. The transport reads FOUR fields and none of them is
// role-specific. Protocol adaptation — the layer that DOES understand cache
// opcodes — sits above and maps them onto service classes.
always_comb begin
  // Priority from an abstract service class (§10), assigned by the adapter.
  unique case (txn.traffic_class)
    TC_CONTROL,
    TC_COMPLETION : tx_priority = PRI_HIGH;   // progress-critical (§13)
    TC_COHERENCE  : tx_priority = PRI_MED;
    TC_MEMORY     : tx_priority = PRI_MED;
    TC_BULK       : tx_priority = PRI_LOW;
    default       : tx_priority = PRI_MED;
  endcase
 
  // Admission by class reservation, not by an application privilege field.
  tx_admit = class_has_credit(txn.traffic_class);
 
  // Fairness over LOGICAL sources, a concept every role has.
  tx_bin   = txn.destination[BIN_W-1:0];
 
  // Segmentation from the transfer's own length and the link's geometry —
  // the transport does not need to know what a cache line is.
  tx_beats = (txn.bytes + LANE_BYTES - 1) / LANE_BYTES;
end

Architecture. The same four decisions, taken from service-model inputs. The adapter above translates role semantics into traffic_class; the transport never learns the role.

State. Unchanged. The fix is a dependency change, which is why it is cheap to make early and expensive to retrofit — every consumer of the removed fields must be re-verified.

Event. Per request, as before.

Contract. The adapter owes an accurate traffic_class. The transport owes scheduling and accounting that depend on nothing else — and that is the property that makes the IP reusable at all.

Failure. The realistic residual mistake is a traffic_class assignment that is role-derived rather than need-derived"accelerator traffic is TC_BULK". An accelerator's completion traffic is not bulk, and classifying by role rather than by service need reintroduces §13 one layer higher.

DV/debug. The reusability test is mechanical and worth running before claiming role neutrality: grep the transport for every field it reads and ask whether a die with no cores could supply it. If any field has no analogue in a non-CPU die, the transport is not role-neutral, whatever the block diagram says.

13. Failure Case — the CPU-Shaped Progress Assumption

The failure that survives §12's fix, because it lives in resource structure rather than in field definitions.

The setup. One shared queue of depth 16 at the transport boundary, drained in order. This is entirely adequate for CPU traffic: short request/response, self-limiting because a core stalls waiting for its own completions, and no single source can fill the queue for long.

Now put an accelerator on it. Bulk reads are issued deeply and are not self-limiting — the accelerator has hundreds of independent requests outstanding by design (26.3 §8).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG. ILLUSTRATIVE. One queue for every class. Correct, fair and adequate
// for homogeneous traffic — and it has no way to guarantee progress for a
// class that is rare.
logic [3:0] q_count_q;                       // depth 16, shared
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) q_count_q <= '0;
  else        q_count_q <= q_count_q + 4'(enq_fire) - 4'(deq_fire);
end
 
// BUG: admission is class-blind. Any class may consume the last entry.
assign enq_ready = (q_count_q != 4'd15);

Failure — the timeline.

CycleQueueWhat entersCompletion pathObservable
00/16accelerator issues bulk readsnormal, high throughput
4016/16bulk fills the queuequeue full — still looks like healthy load
4116/16bulk blocked, waitingbackpressure to the accelerator
6016/16a completion for an earlier read arrives and needs an entryit cannot be enqueued
6116/16completion blockedthe completion that would free a bulk entry is behind the bulk
62–∞16/16bulk waiting for spacecompletion waiting for spacecircular wait — deadlock
link ACTIVE, no error, no CRC event, throughput zero

First divergence: cycle 60 — the moment a progress-critical class needed a resource that a latency-tolerant class had already consumed. Everything before it is normal operation.

Root cause. A resource that must be available for completions was made consumable by bulk traffic. With CPU traffic this never manifests, because a core cannot issue enough to fill the queue while its own completions are pending — the traffic pattern was providing the safety property, and nobody knew.

And this is the sharpest form of the chapter's thesis. The transport is functionally correct and was functionally reusable. It was not architecturally role-neutral, and the difference only appears under a traffic pattern the original role could not produce.

DV/debug. The symptom is "the accelerator overloads the link", which sounds like a workload problem and invites rate-limiting. The discriminator is per-class occupancy (§14): one class at the queue limit and another with zero entries and a pending request is a structural starvation, not a load problem. Rate-limiting the accelerator makes the deadlock rarer and does not remove it (25.8 credit deadlocks is the same shape at the credit layer).

14. RTL — Traffic-Class Resource Separation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECT. ILLUSTRATIVE. Per-class occupancy with a RESERVE for
// progress-critical classes. The reserve is the whole mechanism: bulk may use
// the shared pool, and it may never touch the last N entries.
localparam int Q_DEPTH        = 16;
localparam int N_CLASS        = 5;
localparam int PROGRESS_RSV   = 4;   // entries reserved for CONTROL/COMPLETION
 
logic [4:0] q_total_q;
logic [4:0] q_class_q      [N_CLASS];
logic [4:0] q_class_high_q [N_CLASS];      // high-water, per class
logic [31:0] blocked_cyc_q [N_CLASS];      // cycles a class wanted space and had none
 
function automatic logic is_progress_class(traffic_class_e tc);
  is_progress_class = (tc == TC_CONTROL) || (tc == TC_COMPLETION);
endfunction
 
// Admission: a non-progress class may not consume the reserve.
assign enq_ready = is_progress_class(enq_tc)
                 ? (q_total_q != 5'(Q_DEPTH))
                 : (q_total_q <  5'(Q_DEPTH - PROGRESS_RSV));
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    q_total_q <= '0;
    for (int c = 0; c < N_CLASS; c++) begin
      q_class_q[c] <= '0; q_class_high_q[c] <= '0; blocked_cyc_q[c] <= '0;
    end
  end else begin
    // One signed next-state expression per counter (27.2 §8), so an enqueue
    // and a dequeue in the same cycle net correctly instead of losing one.
    q_total_q <= q_total_q + 5'(enq_fire) - 5'(deq_fire);
    for (int c = 0; c < N_CLASS; c++) begin
      q_class_q[c] <= q_class_q[c]
                    + 5'(enq_fire && (enq_tc == traffic_class_e'(c)))
                    - 5'(deq_fire && (deq_tc == traffic_class_e'(c)));
      if (q_class_q[c] > q_class_high_q[c]) q_class_high_q[c] <= q_class_q[c];
      // The diagnostic that names structural starvation (§13 DV lens).
      if (class_req_pending[c] && !enq_ready_for_class[c])
        blocked_cyc_q[c] <= blocked_cyc_q[c] + 32'd1;
    end
  end
end

Architecture. A shared pool with a reserve that only progress-critical classes may enter. Not full per-class queues — that would be more expensive and is not required to break the circular wait; it is sufficient that the class which frees resources can always be admitted.

State. Total occupancy, per-class occupancy, per-class high-water, and per-class blocked cycles. Twenty-ish counters for a five-class boundary — cheap, and the difference between a one-read diagnosis and a week.

Event. Admission is decided by class before enqueue. blocked_cyc counts while a class has a request pending and is refused, which distinguishes "this class is idle" from "this class is being starved" (27.4 §8).

Contract. PROGRESS_RSV must be at least the maximum number of in-flight operations whose completion is required to free a non-progress entry. A reserve of one is not obviously sufficient and the number needs deriving, not guessing — and that derivation belongs at the architecture gate (27.1 §9 D3).

Failure. Two realistic residual errors. A reserve that is too small narrows the deadlock window without closing it. And classifying by role rather than by need (§12 Failure) puts an accelerator's completions in TC_BULK, at which point the reserve protects nothing.

DV/debug. q_class_high_q[TC_BULK] at the pool limit with blocked_cyc_q[TC_COMPLETION] rising is the exact signature of §13 — and it is visible before the deadlock, because the pressure builds first.

15. Assertions Worth Writing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. English: a non-progress class never consumes the reserved
// entries. This is §13's deadlock expressed as a property — it fires the first
// time bulk traffic reaches the reserve, long before a circular wait forms.
a_reserve_respected: assert property (
  @(posedge clk) disable iff (!rst_n)
    (enq_fire && !is_progress_class(enq_tc))
      |-> (q_total_q <= 5'(Q_DEPTH - PROGRESS_RSV))
);
 
// MANDATORY. English: a progress-critical class with a pending request is
// admitted within a bounded number of cycles. This is the liveness property
// the shared-queue design cannot satisfy. The bound must come from the
// architecture's own progress contract, not from measurement (21.6 §29).
a_progress_class_admitted: assert property (
  @(posedge clk) disable iff (!rst_n)
    (class_req_pending[TC_COMPLETION])
      |-> ##[1:MAX_PROGRESS_WAIT] enq_ready_for_class[TC_COMPLETION]
);
 
// MANDATORY. English: the transport's scheduling decision depends only on the
// service class, never on a role-specific field. Structural rather than
// temporal — checked by construction and by review (§12 DV lens), stated here
// because the property is what §11 violates.
a_priority_is_class_derived: assert property (
  @(posedge clk) disable iff (!rst_n)
    $stable(txn.traffic_class) |-> $stable(tx_priority)
);

Reading the three properties.

a_reserve_respected uses |-> because the violation is same-cycle — the admission decision and the occupancy are evaluated together. It is a good formal target: small state, bounded, and it is exactly the bug.

a_progress_class_admitted is the liveness half and needs a derived bound. A bound raised each time it fires cannot detect the failure it exists for (21.6 §29). It also needs a coverage companion — if class_req_pending[TC_COMPLETION] never occurs in the regression, the property passes vacuously (27.3 §18 I1).

And a_priority_is_class_derived is the weakest of the three, deliberately. It catches priority changing while the class is stable, which is a symptom of role-dependence rather than the thing itself. The real check for §11 is structural — enumerate the transport's input fields and ask which have no analogue in a coreless die. Some properties are review items, not assertions, and saying so is better than writing a property that cannot express it.

16. Verification Consequence

A CPU-shaped testbench verifies a CPU-shaped transport, and both pass.

CPU-shaped environment testsA role-neutral transport must also test
short request / responselong bulk flows with deep outstanding
bounded, self-limiting outstandinga source that does not self-limit (26.3 §8)
one traffic classmixed classes concurrently — §13's exact condition
symmetric directionasymmetric direction (26.4 §16)
predictable latencyburstiness and latency variance
recovery at idlerecovery under sustained load (27.3 §14 E1)
per-class starvation coverage

Three readings.

Row 3 is the coverage cross that would have caught §13 before silicon, and it is a single bin: bulk at the pool limit × a completion request pending. 27.3 §16's argument in its most specific form — each class works, the mix does not.

Row 2 is subtler and is the real generalisation. A CPU is self-limiting: a core stalls on its own completions, so it cannot saturate indefinitely. An accelerator is not, and a transport verified only against self-limiting sources has never been driven to its structural limits.

And the environment must be able to express a non-CPU source at all. If the stimulus generator emits cache opcodes and core IDs because that is what §11's transport reads, it cannot generate accelerator traffic — the testbench inherited the same role-coupling as the design, so it agrees with it (27.3 §7).

17. Performance Consequence

"The link met its target for the CPU use case" does not establish that the architecture is role-neutral, because different roles bind on different resources.

One boundary, two roles, same link. Per-transfer overhead 8 bytes; round-trip latency 500 ns; outstanding table 64 entries.

CPU-styleAccelerator-style
typical transfer64 B512 B
framing efficiency64/(64+8) = 0.89512/(512+8) = 0.985
outstanding needed for 100 GB/s100e9/64 × 500e-9781100e9/512 × 500e-998
with a 64-entry table64/500ns × 64 B8.2 GB/s64/500ns × 512 B65.5 GB/s
what bindsthe outstanding window, badlycloser to the link
what the designer would conclude"we need far more IDs""the table is roughly right"

Three readings.

The same 64-entry table delivers 8.2 GB/s or 65.5 GB/s depending purely on transfer size — a factor of 8, with no change to the link, the protocol or the RTL. A resource sized for one role is mis-sized for the other in a way that is invisible until the role changes.

And the direction of the error is counter-intuitive. The CPU case — smaller transfers — needs more outstanding entries for the same bandwidth, not fewer. A transport sized generously for an accelerator can be badly under-provisioned for small-transfer traffic, which is the opposite of the intuition that accelerators are the demanding case.

Which is why §16 row 2 matters and why "it met the target" is not transferable evidence. 27.4 §7 requires a stated workload with every performance claim; a claim carried across a role change has silently changed its workload and is no longer supported.

18. Integration and Debug Consequence

Role-neutral transport does not imply role-neutral integration, and the distinction matters because the first is often achieved and then over-claimed.

Contract that must be re-established per roleWhy the transport's neutrality does not cover it
semantic mappingwhat the payloads mean differs entirely
configuration ownershipa different die may be the config owner (26.5 §17)
reset and recovery retentionthe retention policy must be agreed with this peer (27.5 §8)
power and readinessa different readiness predicate (26.5 §13)
security and privilegean accelerator's model is not a CPU's
software modelhow the function is discovered and driven
performance budget§17 — the workload changed
fault domainswhat must reset together (26.5 §20)

And the debug consequence is an observability gap that follows the same pattern. A CPU-centric instrument set measures request latency and its distribution — appropriate, because tail latency is the CPU's first concern (§9). Point it at an accelerator failure and it shows nothing: §13's deadlock has no latency signature until it is total, and channel or class imbalance is invisible in an aggregate latency histogram.

The instruments a non-CPU role needs are different in kind (27.6 §13): per-class occupancy and blocked cycles (§14), per-direction accounting (26.4 §21), and useful-versus-retry byte separation (27.4 §11). None of them is exotic; all of them are absent from an instrument set designed for tail latency — and none can be added after tapeout.

19. Interview Ladder

15 seconds. "UCIe standardises a die-to-die boundary, not the function of the die behind it. CPU chiplets are the most visible use case, but the same boundary can join accelerators, memory-side logic or I/O when the protocol and system contracts fit."

60–90 seconds. Add the type distinction: "a CPU is a role — what the die computes. The boundary is a position — where the die stops. They vary independently." Add the counterexample: "take an accelerator die talking to a memory-side die. No CPU on the link at all, and every problem is still there — framing, capability negotiation, flow control, semantic identity across retries, retention across recovery, configuration ownership. Removing the CPU removes none of them, so it was never load-bearing." Add the cost: "and the belief has a hardware consequence — CPU assumptions get compiled into transport RTL, and you find out when you try to reuse the link."

Senior follow-up to expect: "So what actually changes between roles?"

Strong answer. "The service profile, not the boundary. CPU traffic is short request/response and tail-latency-sensitive; accelerator traffic is bulk and sustained; memory-side is directionally asymmetric; I/O mixes latency-critical control with bulk. The transport should schedule on an abstract service class, never on an application role — and the classic failure is one shared queue that bulk traffic can fill, which starves completions and deadlocks. It never happens with CPU traffic because a core is self-limiting, so the traffic pattern was providing the safety property."

And the evidence discipline, unprompted (§5): "I'd be careful about naming products. Across everything I've read, shipping accelerator packages document proprietary die-to-die links — AMD's Infinity Fabric, NVIDIA calls NV-HBI 'custom'. Consortium membership isn't adoption and a chiplet product isn't a UCIe product. The architectural argument doesn't need a product anyway — it's a construction."

20. Module 28 Synthesis — Six Beliefs, One Pattern

Each chapter mis-scoped a different kind of object. The pattern is the module's real content.

ChapterObject mis-scopedLocally true simplificationScope where it failsArchitecture consequence
28.1a technology"PCIe means one thing" — exact at a board linkwhen semantics separate from transportprotocol engine coupled to transport; retry creates a second operation
28.2a specification"PHY plus my controller" — right for most interfaceswhen the standard's scope exceeds the PHYreliability owned twice, or not at all
28.3an equationthe yield curve — correct about siliconwhen packaging and assembly terms dominatesplitting a small die costs 3.3×; no harvest path
28.4a property"the fabric handles coherence" — true on one SoCwhen both sides are yours and neither is a hostno home agent; two exclusive grants; lost write
28.5an input"packaging is downstream" — true monolithicallywhen an internal boundary becomes physicala partition that cannot be built
28.6 (this chapter)a scope"start with the CPU example" — the clearest first casewhen the first use case becomes the definitionrole-coupled transport; mixed-traffic deadlock

Three readings.

Every row's third column is genuinely true somewhere. That is the module's method: a misconception worth a chapter is never simply false — it is a working simplification that was carried across a scope boundary. Dismissing it teaches nothing; locating the boundary teaches everything.

And every failure in the last column is invisible in the layer where the belief lives. The RTL compiles, the link counters are clean, the cost model balances, the block diagram is correct. A wrong mental model does not produce a visible error — it produces a system that is wrong in a way its own instruments cannot see, which is why the correction has to happen in the model rather than in the code review.

The general defence is one habit: name the object before reasoning about it. Is this a technology, a specification's scope, an equation, a system property, an architectural input, or an applicability claim? Each has a different scope, and the error is always a valid statement about one applied to another.

21. What a Senior UCIe Engineer Can Now Do

This chapter completes the UCIe curriculum. The useful closing statement is a capability list, not a celebration.

CapabilityWhere it was built
partition responsibility by layer and refuse malformed comparisons23.3 · 25.3 · 28.1
keep three identities distinct — semantic, transport object, physical attempt25.5 §11 · 28.1 §11
preserve semantic state across transport events, and know retention is a decision25.3 §14 · 27.5 §8
model credits as ownership with a conservation invariant25.6 §10
derive required concurrency from rate × latency, and recognise its ceiling26.4 §14 · 27.4 §17
attribute every lost cycle to a named cause27.4 §8
design a verification architecture whose expected value is independent25.8 · 27.3 §7
debug to a first divergence rather than to a first error25.9 · 27.6 §6
review integration contracts and find the row with two different answers25.10 · 27.5 §6
read a real architecture and say what its documentation does not establish26.126.5
distinguish evidence from inference, and say so out loud22.1 §5 · §5
recognise a scope error before it reaches RTL28.128.6

And the single habit that ties them together — visible in every module from 21 onward — is asking what a mechanism owns rather than what it does. Who owns identity, who owns reliability, who owns configuration, who owns coherence, who owns the point of serialisation, who owns the packaging constraint. Nearly every failure in this curriculum was an ownership question that nobody asked.

22. Final Whiteboard Exercise

"A heterogeneous package contains a compute die, an accelerator die, an I/O die and a memory-side die. Under heavy traffic the link stays ACTIVE, useful throughput drops, a recovery occurs, and afterwards one completion is duplicated. Work it."

Step 1 — separate the failures before theorising. Two independent problems until proven otherwise: a performance failure (throughput drops) and a correctness failure (a duplicated completion). A single explanation covering both is a hypothesis, not a starting point (27.7 §16).

Step 2 — the duplicate first, because correctness precedes performance. A duplicate after a recovery has three candidate causes, and they are distinguishable. A transport event created a semantic operation (28.1 §10) — check whether anything writes semantic identity on retry_fire. Two reliability layers raced (28.2 §10) — check whether an upper timeout is shorter than the transport's stated worst-case delivery. Or a retention disagreement (27.5 §8) — one side retained, the other replayed. The discriminator is obligations_outstanding_at_recovery on both dies plus reissued_after_recovery; without those counters this is a cross-team debug.

Step 3 — the throughput drop, with a staged counter walk (27.4 §8). Offered → accepted → transmitted → delivered → retired. The first stage whose rate falls below target is the boundary. Then split by symptom: nothing full anywhere → a concurrency window (§17's arithmetic, and note the window requirement differs per role); upstream full and no-credit high → flow control, check conservation (25.6 §10); one class at its limit with another blocked → §13's structural starvation; utilisation high but useful throughput low → retry bytes counted as useful (27.4 §11).

Step 4 — traffic classes, because this package is heterogeneous. Four dies with four different service profiles (§9) share this fabric. Ask for per-class occupancy and blocked cycles (§14). A bulk class at its pool limit with a completion class blocked is §13, and it explains the throughput collapse without explaining the duplicate — which is evidence they are two problems, and that is a finding.

Step 5 — check whether they are related after all. One mechanism connects them: if the recovery left an obligation permanently outstanding, that entry never releases its credit or its ID — so every recovery leaks one unit of concurrency and throughput degrades progressively. The confirming evidence is credits or free IDs trending down across recovery events, which is a snapshot comparison rather than a new experiment.

Step 6 — the verification and instrumentation asks. DV: a mixed-class, deep-outstanding, non-self-limiting stimulus with recovery injected while obligations are outstanding, from both sides (27.3 §14 E1), and an independent predictor fed from the input side (27.3 §7). SVA: a_retry_creates_no_operation, a_reserve_respected, exactly-one-completion-per-semantic-operation across recovery. Debug: first-fault capture that survives the recovery (27.6 §16), and per-class counters.

Step 7 — state what cannot be concluded, and what needs the specification. "I cannot say whether the retention behaviour is a specification gap or an implementation bug without both sides' written answer — that is a document question, not a data question. And any claim about what the standard requires here needs the actual specification revision; I would not assert it from memory" (§5).

23. Summary

Six things.

A role is not a boundary (§6). CPU answers what a die computes; the boundary answers where it stops. They vary independently, and the Consortium's own scope sentence names a position and no function.

The counterexample has no CPU on the link (§7). Accelerator die to memory-side die — nine boundary requirements, and removing the CPU eliminates none. It was never in the dependency set.

The belief compiles into RTL (§11): a transport reading cache opcodes, privilege levels, core IDs and cache-line sizes. No line is a bug; the dependency direction is — and adding accelerator fields alongside produces a transport that knows two roles and fails on the third.

The deeper failure survives that fix (§13). One shared queue is adequate for CPU traffic because a core is self-limiting — the traffic pattern was providing the safety property. An accelerator is not, bulk fills the pool, a completion cannot be enqueued, and the link deadlocks while ACTIVE with no error.

Roles bind on different resources (§17). The same 64-entry table yields 8.2 GB/s or 65.5 GB/s depending only on transfer size — and the small-transfer case is the demanding one, which is the opposite of the intuition.

And the module's pattern is one habit (§20): name the object before reasoning about it. Technology, specification scope, equation, system property, architectural input, applicability claim — every one of the six beliefs was a valid statement about one of these applied to another.