UCIe · Module 7
The Role of the UCIe PHY
What the UCIe PHY owns and what it hides from the Adapter — logical PHY versus analogue front end, the RDI boundary, an illustrative bring-up FSM, lane health versus lane enable, lane mapping, configuration snapshots, CDC hazards, accepted-data responsibility, and PHY debug method.
Module 6 spent six chapters on the physical structures a die-to-die link runs through, and every one of them treated "the link" as a given. Conductors exist. They carry lanes. Lanes carry data. Something declares the arrangement operational and the Adapter starts sending.
Something has to make that true, and it is not a wire. Between the Adapter's clean world of transport units and the package's world of imperfect conductors sits a substantial piece of hardware whose entire job is to convert one into the other — with state machines, configuration registers, per-lane status, mapping tables, clock-domain crossings, timeouts, and a defined story for what happens when a conductor stops working. That hardware is the PHY, and it is where this module lives.
Chapter 5.3 asked what does the Physical Layer own? — a layering question. This chapter asks a harder one: how is that layer built as real hardware, and what exactly does the Adapter get to assume about it? Every later chapter in Module 7 refines a piece of the answer. This one establishes the ownership model they all use.
1. The One-Sentence Model
The PHY turns a collection of imperfect physical wires into an abstract digital link resource that the Adapter can trust.
Take "trust" literally, because it is the engineering content of the sentence. The Adapter is entitled to reason as though it has a link with a defined width, a defined status, and defined error reporting. It is not entitled to know — and must not be written to depend on — which individual conductor is healthy, what sampling phase the receiver settled on, what termination the driver configured, or how many training attempts it took.
That is not politeness. It is the property that lets the same Adapter work across package classes, process nodes, and PHY implementations, which Chapter 6.4 §6 identified as the practical payoff of UCIe's two package tracks sharing everything above the physical design point. Break the abstraction and you have coupled your transport logic to a specific piece of analogue engineering.
2. Three Different Jobs
"The PHY" is a single word for three distinct responsibilities that fail in different ways and are verified differently.
1 — Electrical movement. Drive signals onto the package channel and sample them at the far end. Transmitters, receivers, termination, and the sampling machinery. This is where the analogue engineering lives.
2 — Physical organisation. Map the abstract stream the Adapter hands down onto the physical resources that exist: which lanes, in which order, grouped into which modules, timed against which clock. This is bookkeeping, and it is entirely digital.
3 — Physical readiness. Bring enough of the physical resources into a working state to declare the link usable — initialisation, training, calibration — and keep monitoring whether that remains true.
Job 1 fails as corruption or as a link that will not come up. Job 2 fails as deterministic corruption with a perfectly healthy channel — §14 makes this a signature worth memorising. Job 3 fails as a link that never becomes operational, or that stops being operational later. Confusing which job failed is the single largest source of wasted PHY debug time, which is why §21 and §22 are structured the way they are.
3. Logical PHY and Analogue Front End
The most common misconception about PHYs, in one sentence: PHY does not mean analogue.
A UCIe PHY has two halves. The analogue front end (AFE) contains the transmitters, receivers, termination, and the clocking and sampling circuitry — genuinely analogue and mixed-signal design. The logical PHY is ordinary synchronous digital logic, and it is substantial:
| Logical PHY (digital) | Analogue front end |
|---|---|
| bring-up and training state machines | drivers and receivers |
| configuration registers and snapshots | termination |
| lane enable and lane health tracking | clock generation and forwarding |
| lane mapping and lane reversal handling | sampling and capture circuitry |
| scrambling and de-scrambling | equalisation, where present |
| sideband control and parameter exchange | calibration hardware |
| status generation and error monitoring | |
| clock-domain crossing to the Adapter |
UCIe's own description of the physical layer covers both: it includes the electrical AFE — transmitter and receiver — as well as the logic PHY that implements the link initialisation, training, and calibration algorithms, plus test and repair functionality. It also owns a sideband channel used for parameter exchange and negotiation between the two dies.
For an RTL or DV engineer, the practical consequence is that most of the PHY is your problem. Training sequencing, configuration legality, lane masks, mapping, status abstraction, timeouts, and clock-domain crossings are all synchronous digital design with all the usual hazards — and they are where the majority of PHY bring-up bugs actually live.
4. The Boundary With the Adapter
Module 5 established the layering. Here is the concrete division, stated in terms of what each side is accountable for.
The PHY owns, per UCIe's description of the physical layer: mainband link training and initialisation; sideband initialisation and training; lane repair; lane reversal; scrambling and de-scrambling; recalibration; clock forwarding; and test and repair functionality — together with the electrical front end that does the driving and sampling.
The D2D Adapter owns the transport-level responsibilities above that: flit handling and formatting, the reliability mechanisms where the negotiated mode requires them, arbitration and multiplexing between protocols, parameter negotiation with its peer, and coordination of link state at the transport level.
The clean way to hold the split:
The PHY is accountable for the link being physically usable. The Adapter is accountable for what is carried over it being correct.
Two implications people get wrong in interviews and in code review:
- The Adapter does not fix a broken conductor. Retry, where it exists, exists to survive transient corruption — not to compensate for a lane that has stopped working. Recovering a failed lane is the PHY's job, through repair and retraining.
- The PHY does not know what it is carrying. Ordering, transaction semantics, and protocol rules are above it. A PHY that inspects payload to decide behaviour has taken on a responsibility that will break the next time the protocol above it changes.
5. RDI, at the Right Level
RDI — the Raw Die-to-Die Interface — is the architectural boundary between the D2D Adapter and the Physical Layer. UCIe defines it so that an Adapter and a PHY from different sources can be composed, which is exactly what a chiplet ecosystem needs.
At this stage, three properties matter and the signal list does not.
Directionality. Traffic descends: the Adapter hands data down for transmission, and the PHY delivers received data up. Status ascends: the PHY reports what the Adapter is allowed to assume. Control flows both ways, since the Adapter can request state changes and the PHY reports what state it is actually in.
Classes of information. Data — the raw payload the PHY moves without interpreting. Control — requests and acknowledgements about link state. Status — the abstract report on whether the link is usable and whether something has gone wrong.
Ownership, which is the point. RDI is where responsibility transfers. When the Adapter's data crosses it and the PHY accepts it, the PHY becomes accountable for it — §12 examines exactly what that obligation means, because it is the most misunderstood part of the interface.
6. A PHY Needs a State Machine
Why does the PHY need explicit state at all? Because becoming usable is a process with prerequisites, and the Adapter needs a single trustworthy answer to "may I send?" at every instant of that process.
// Illustrative architecture FSM — NOT normative UCIe state names.
typedef enum logic [2:0] {
PHY_RESET = 3'd0, // internal state known, nothing assumed about the link
PHY_WAIT_CFG = 3'd1, // out of reset, waiting for a valid configuration
PHY_TRAIN = 3'd2, // establishing a usable physical channel
PHY_ACTIVE = 3'd3, // the Adapter may exchange traffic
PHY_RECOVER = 3'd4, // was active, something degraded, attempting restoration
PHY_FAILED = 3'd5 // cannot establish or restore supported operation
} phy_state_t;What each state means — and note that every one of them is a statement about what may be assumed:
- RESET — internal state is known and nothing about the outside world is assumed. Not "ready"; merely "not confused".
- WAIT_CFG — reset is released but the PHY does not yet know what link it is supposed to build. Width, rate, and mode are configuration, and configuration arrives from somewhere.
- TRAIN — actively establishing a usable channel. Prerequisites are met; the outcome is not yet known.
- ACTIVE — the only state in which the Adapter may exchange traffic. Everything else in this chapter exists to make this state mean something.
- RECOVER — the link was active and something degraded. Traffic is suspended while restoration is attempted, and the exit is either back to ACTIVE or onward to FAILED.
- FAILED — supported operation cannot be established. A terminal state until something external intervenes.
7. The State Machine as Real RTL
// Illustrative architecture RTL — not UCIe normative signal naming.
module phy_bringup_fsm #(
parameter int TIMEOUT_W = 16
) (
input logic clk,
input logic rst_n,
input logic cfg_valid, // configuration presented
input logic cfg_legal, // and it is self-consistent
input logic training_done, // from the training engine
input logic training_error, // unrecoverable training result
input logic link_fault, // observed after ACTIVE
input logic recovery_done,
input logic recovery_exhausted,
output phy_state_t phy_state_q,
output logic start_training,
output logic phy_operational
);
phy_state_t phy_state_d;
logic [TIMEOUT_W-1:0] train_timer_q;
logic train_timeout;
assign train_timeout = (train_timer_q == '1);
always_comb begin
phy_state_d = phy_state_q; // explicit default: hold
unique case (phy_state_q)
PHY_RESET : phy_state_d = PHY_WAIT_CFG;
PHY_WAIT_CFG : if (cfg_valid && cfg_legal) phy_state_d = PHY_TRAIN;
PHY_TRAIN : if (training_error || train_timeout) phy_state_d = PHY_FAILED;
else if (training_done) phy_state_d = PHY_ACTIVE;
PHY_ACTIVE : if (link_fault) phy_state_d = PHY_RECOVER;
PHY_RECOVER : if (recovery_exhausted) phy_state_d = PHY_FAILED;
else if (recovery_done) phy_state_d = PHY_ACTIVE;
PHY_FAILED : phy_state_d = PHY_FAILED; // terminal until reset
default : phy_state_d = PHY_RESET; // illegal encoding recovers
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phy_state_q <= PHY_RESET;
train_timer_q <= '0;
end else begin
phy_state_q <= phy_state_d;
// The timer runs only while training, and clears on every entry to TRAIN.
if (phy_state_q != PHY_TRAIN) train_timer_q <= '0;
else if (!train_timeout) train_timer_q <= train_timer_q + 1'b1;
end
end
assign start_training = (phy_state_q == PHY_WAIT_CFG) && (phy_state_d == PHY_TRAIN);
assign phy_operational = (phy_state_q == PHY_ACTIVE);
endmoduleArchitecture. The PHY must give one unambiguous answer to "may the Adapter send?", and that answer must depend on prerequisites actually having been met. Encoding readiness as a state reachable only through training makes the prerequisite structural rather than a condition someone remembers to check.
State. A six-value state register and a training timer. Nothing else — the training engine, the lane logic, and the configuration capture live in their own blocks and report in.
Cycle behaviour. phy_state_q updates once per clock from phy_state_d. The combinational block has an explicit default of hold, so an unhandled condition parks the machine rather than producing an unintended transition. The timer is cleared in every state other than TRAIN, which means it is automatically reinitialised on each entry — no separate clear signal to forget. default: PHY_RESET recovers from an illegal encoding, which matters in a design exposed to single-event upsets.
Contract. Everything downstream keys off phy_operational. The training engine starts on the single-cycle start_training pulse, and — this is the point of §17 — it may rely on the configuration being frozen for the duration.
Failure. The classic errors this structure prevents: a transition into ACTIVE from anywhere but TRAIN; a timer that keeps running after leaving TRAIN and expires spuriously on a later entry; and a latch inferred from an incomplete combinational assignment.
DV. Assert the prerequisite directly rather than trusting the case statement to stay correct:
// Illustrative — ACTIVE is only reachable through completed training.
property p_active_requires_training;
@(posedge clk) disable iff (!rst_n)
$rose(phy_state_q == PHY_ACTIVE) |->
$past(phy_state_q) inside {PHY_TRAIN, PHY_RECOVER};
endproperty
a_active_requires_training :
assert property (p_active_requires_training)
else $error("Entered PHY_ACTIVE from %0s.", $past(phy_state_q).name());What it encodes: the physical prerequisite that a channel must be established before it can be used. What it catches: any future edit — a new state, a debug override, a fast-bring-up mode — that creates a shortcut into ACTIVE. This is a safety property, and its value is that it survives refactoring by people who did not read this chapter.
8. Bring-Up, Cycle by Cycle
| Cycle | Event | State after edge | phy_operational | Adapter may send |
|---|---|---|---|---|
| 0 | rst_n low | RESET | 0 | no |
| 1 | rst_n released | WAIT_CFG | 0 | no |
| 2 | configuration not yet valid | WAIT_CFG | 0 | no |
| 3 | cfg_valid && cfg_legal | TRAIN | 0 | no |
| 4 … n | training in progress, timer counting | TRAIN | 0 | no |
| n+1 | training_done | ACTIVE | 1 | not yet |
| n+2 … | status crosses into the Adapter domain | ACTIVE | 1 | yes, once observed |
Two rows deserve attention.
Cycles 4 to n have no fixed length. Training duration depends on the channel, the configuration, whether recalibration is needed, and what the peer is doing. Any design that assumes a constant is wrong; that is why the bound is a timeout (§18) rather than a fixed wait.
The gap between n+1 and n+2 is real. phy_operational becomes true in the PHY's domain at n+1. The Adapter may act on it only after it has been safely synchronised into the Adapter's domain (§15). Those are different instants, and a testbench that treats them as one will pass while the silicon fails.
9. Reset Release Is Not Readiness
The single most common PHY-boundary bug, in one line:
// WRONG — reset release does not mean the link is usable.
assign phy_ready = rst_n;Reset establishes that internal state is known. It says nothing about:
- whether the peer die is powered, out of reset, or even present;
- whether clocking is running and usable;
- whether any lane can carry data;
- whether training has run, let alone succeeded;
- whether the two ends agree on width, rate, and mode.
Every one of those is a fact about the outside world, and reset is entirely local. The correct form derives readiness from the state that encodes those facts:
// Illustrative — readiness is a conclusion, not an input.
assign phy_operational = (phy_state_q == PHY_ACTIVE);The failure signature is brutal and worth recognising. With phy_ready = rst_n, everything looks perfect in a simulation where the peer is a cooperative model that responds immediately. In silicon, the Adapter starts transmitting the moment reset lifts, into a PHY whose training has not begun. The data is driven onto a channel with no established sampling, so it is simply gone. Nothing reports an error, because from the Adapter's point of view the handshake completed. The symptom is a link that appears to initialise and then produces no correct traffic — and the initial suspicion always falls on the channel.
10. Gating the Adapter
Readiness must actually gate acceptance, and readiness alone is not sufficient:
// Illustrative — accept only when the link is usable AND storage exists.
assign rdi_ready = phy_operational_sync && tx_credit_available;Architecture. Two independent conditions must both hold. The link must be usable — otherwise there is nowhere for the data to go. And the PHY must have somewhere to put the data, because between accepting an item and putting it on the wire there is a pipeline, a clock crossing, and a scheduler, all of which can stall.
State. Whatever tracks transmit-side occupancy — a credit counter or a FIFO occupancy, as in Chapter 5.5. Note that phy_operational_sync is the synchronised status, not the raw one (§15).
Cycle behaviour. Combinational over registered inputs. A transfer occurs on a cycle where both the Adapter's valid and this ready are high; the Adapter is entitled to retire that item.
Contract. This is the transfer of responsibility described in §5. The moment rdi_ready and the Adapter's valid coincide, the item is the PHY's.
Failure. Omitting either term breaks it in a different way. Without phy_operational_sync, data is accepted into a link that cannot carry it. Without tx_credit_available, data is accepted with nowhere to store it and is overwritten by the next item — a silent single-item loss whose only symptom is a missing transport unit somewhere far downstream.
The wrong version is short enough to be tempting, especially during bring-up:
// WRONG — unconditional acceptance.
assign rdi_ready = 1'b1;Cycle-level failure walkthrough:
- Cycle k: the Adapter asserts valid with a transport unit.
rdi_readyis high, so the transfer completes. - Cycle k: the Adapter, correctly, treats the item as delivered and releases the buffer holding it. The only copy now lives in the PHY.
- Cycle k+1: the PHY's transmit path is stalled — training incomplete, a clock crossing full, a scheduler busy — so it cannot forward the item.
- Cycle k+1: with no storage reserved, the item is overwritten by the next accepted one, or simply dropped.
- Later: the peer never receives it. If the negotiated mode includes retry, the mechanism may eventually recover it, at the cost of a stall nobody can explain. If it does not, the transport unit is lost with no error reported anywhere, because every participant behaved correctly according to its own local view.
This is the archetypal layer-boundary bug: two blocks that are individually correct, disagreeing about who owns an item.
11. Why the PHY Buffers Internally
The tx_credit_available term implies storage, and it is worth being explicit about why the PHY needs its own rather than leaning on the Adapter's.
- Clock domains differ. The Adapter-facing interface and the PHY's transmit machinery need not run on the same clock, and a crossing needs elasticity on at least one side.
- The transmit path is scheduled. Data is mapped onto lanes and modules, possibly with framing or periodic events interleaved. The scheduler is not always ready in the same cycle the Adapter is.
- The path is pipelined. Mapping, scrambling, and serialisation take cycles, and a pipeline that can stall needs a place for the items already in it.
Chapter 5.5 built a two-entry elastic buffer for exactly this shape of problem and explained why two entries are the minimum that lets a stalled interface resume without a bubble. The same reasoning applies here; the argument is not repeated. What is specific to this boundary is the conclusion:
The PHY's acceptance condition must be derived from its own storage, not from an assumption about how fast the transmit path drains. Rate assumptions are the thing that breaks when someone later adds a pipeline stage.
12. What the PHY Owes for Accepted Data
Now the interface's most misunderstood obligation, and the reason §10's wrong version is more than a style problem.
When the PHY accepts a transport unit, it takes on responsibility for it. What that responsibility is depends on the contract in force, and the honest statement of it has three parts:
- It may not silently discard the item. If the link fails after acceptance, the item's fate must be knowable — either it is retained and sent after recovery, or its loss is reported through a mechanism the Adapter can act on. Vanishing is not an option.
- What "reported" means is defined by the mode. UCIe supports operating modes with different reliability arrangements, and where a retry mechanism exists it is the Adapter's, not the PHY's. The PHY's obligation is to make the situation visible so that mechanism can act.
- The two ends must agree. An Adapter that assumes retention and a PHY that assumes reporting will lose data at exactly the moment a link degrades — the worst possible time to have an ambiguity.
The anti-pattern is the one Chapter 5.5 §7 diagnosed at the Adapter boundary, appearing here at the PHY boundary:
// WRONG — a link fault destroys data the PHY already accepted.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) tx_item_valid_q <= 1'b0;
else if (link_fault) tx_item_valid_q <= 1'b0; // silently discards
else if (accept) tx_item_valid_q <= 1'b1;
else if (sent) tx_item_valid_q <= 1'b0;
endThe item was accepted on cycle k. On cycle k+1 a fault clears the valid bit. The Adapter has already retired it. Nobody has it, and nobody knows.
The correct shape keeps the accepted item accounted for across the fault:
// Illustrative — an accepted item stays accounted for through a fault.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_item_valid_q <= 1'b0;
item_lost_q <= 1'b0;
end else begin
if (accept) tx_item_valid_q <= 1'b1;
else if (sent) tx_item_valid_q <= 1'b0;
// A fault does not erase the item; it either survives recovery or is
// reported as lost so the Adapter's mechanism can act on it.
if (link_fault && tx_item_valid_q && !retain_across_fault)
item_lost_q <= 1'b1;
else if (fault_report_taken)
item_lost_q <= 1'b0;
end
endDV. The invariant is that acceptance implies capacity to honour it:
// Illustrative — never accept an item there is no room to retain.
property p_accept_implies_storage_available;
@(posedge clk) disable iff (!rst_n)
rdi_fire |-> tx_credit_available;
endproperty
a_accept_implies_storage :
assert property (p_accept_implies_storage_available)
else $error("Accepted an item with no storage reserved for it.");Pair it with a scoreboard check that every accepted item is either transmitted or reported lost — never neither. That scoreboard, not the assertion, is what catches the silent-discard case, because the discard happens after the accept and the assertion above has already passed.
13. Lane Health Is Not Lane Configuration
Two per-lane masks, two different meanings — the same present-versus-enabled distinction Module 6 established for dies, layers, and bond segments, now at lane granularity.
// Illustrative architecture RTL — not UCIe normative signal naming.
localparam int NUM_LANES = 64;
logic [NUM_LANES-1:0] lane_good_q; // physical health, from training/monitoring
logic [NUM_LANES-1:0] lane_enable_q; // configuration decision
logic [NUM_LANES-1:0] lane_usable;
assign lane_usable = lane_good_q & lane_enable_q;Architecture. A lane can be electrically fine and deliberately unused — because the configured width is narrower than the physical width, because the far end disabled its counterpart, because a lane is held out as a spare, or because a debug mode reduced the link. And a lane can be enabled by configuration and physically dead. These are independent facts and one mask cannot carry both.
State. Two vectors, one bit per lane. lane_good_q is a measurement, written by training and updated by monitoring. lane_enable_q is a decision, written by configuration and negotiated with the peer.
Cycle behaviour. lane_enable_q is stable during operation and changes only in a quiesced state — changing the enabled set mid-transfer would alter the width underneath in-flight data. lane_good_q can change at any time, because a lane can fail while the link is up, and that change is what drives the transition to RECOVER.
Contract. The transmit mapper drives only lane_usable. Both ends must converge on the same usable set, which is what the sideband parameter exchange is for.
Failure. Using lane_good_q alone transmits on lanes the far end has disabled — data leaves and is never collected. Using lane_enable_q alone transmits on dead lanes — those bit positions are corrupt, deterministically and in the same place every time. That second signature is diagnostic: the same bits wrong every time points at configuration, never at analogue margin.
DV.
// Illustrative — a lane may not be enabled unless it is physically good.
property p_enabled_lane_is_usable;
@(posedge clk) disable iff (!rst_n)
(phy_state_q == PHY_ACTIVE) |-> ((lane_enable_q & ~lane_good_q) == '0);
endproperty
a_enabled_lane_is_usable :
assert property (p_enabled_lane_is_usable)
else $error("Lane enabled but not good: en=%h good=%h", lane_enable_q, lane_good_q);Note the qualification on ACTIVE. During training the two masks are legitimately inconsistent — that is what training is resolving — so asserting the invariant unconditionally produces failures that are not bugs. Getting the qualification right is most of the skill in writing useful assertions; an assertion that fires during normal operation gets waived, and a waived assertion protects nothing.
14. Lane Mapping, and the Signature Worth Memorising
Logical lane n is not necessarily physical lane n. Package routing may reverse the order, repair may substitute a spare, and the two dies may be oriented differently. So the PHY carries a mapping:
// Illustrative architecture RTL — not UCIe normative signal naming.
localparam int LANE_IDX_W = $clog2(NUM_LANES);
logic [LANE_IDX_W-1:0] logical_to_physical_q [NUM_LANES];Architecture. The physical arrangement is a package and repair outcome; the logical arrangement is what the data stream assumes. Something must translate, and that something is state — real flops, not a wiring assumption.
State. One index per logical lane, established during training and after any repair.
Cycle behaviour. Written during training or repair, read every cycle data is mapped. It must be stable while data is in flight, since a mapping change mid-transfer scrambles the payload in a way nothing downstream can detect as a mapping problem.
Contract. The transmitter's mapping and the receiver's inverse must be consistent. This is negotiated, not assumed — and it is one of the things the sideband exists to establish before the mainband carries anything.
Failure — and this is the signature. Suppose training succeeds, every lane reports good, clocking is stable, error counters show a healthy channel — and every payload fails its integrity check, identically, every time.
Deterministic, repeatable, total corruption on a physically healthy link is a mapping, ordering, or alignment problem — not an analogue one.
The reasoning is that analogue margin problems are statistical. They corrupt occasionally, they get worse with temperature and voltage, they cluster on the worst lanes, and they change between runs. A mapping error is arithmetic: the bits arrive perfectly and are reassembled in the wrong order, so the result is wrong the same way every single time. Recognising this distinction converts a multi-week signal-integrity investigation into an afternoon spent comparing two configuration tables.
DV. Check mapping legality structurally — it must be a permutation over the usable set:
// Illustrative — every usable logical lane maps to a distinct usable physical lane.
property p_mapping_is_injective;
@(posedge clk) disable iff (!rst_n)
(phy_state_q == PHY_ACTIVE) |->
mapping_is_permutation(logical_to_physical_q, lane_usable);
endpropertywith mapping_is_permutation a checker function over the mask. Two logical lanes mapping to the same physical lane is a bug that no amount of traffic testing reliably exposes, because it corrupts in a way that looks like data.
15. Clock Domains Are Not Optional Here
The Adapter-facing interface and the PHY's internal machinery are frequently in different clock domains, and the receive side is often source-synchronous — captured against a clock that arrived with the data. Do not assume one global clock.
The wrong crossing, which looks entirely reasonable:
// WRONG — a raw asynchronous signal sampled directly.
always_ff @(posedge adapter_clk)
phy_operational_q <= phy_operational_async;If phy_operational_async changes near the sampling edge, the flop can enter a metastable state — neither 0 nor 1, resolving after an unpredictable delay. Downstream logic may sample it during resolution, and different downstream paths may resolve it differently, so parts of the design can disagree about whether the link is up. Simulation will not show this, since a two-valued simulator resolves the race deterministically.
The correct crossing for a single stable status bit:
// Illustrative — two-flop synchroniser for a slow, stable, single-bit status.
logic phy_op_meta_q, phy_op_sync_q;
always_ff @(posedge adapter_clk or negedge adapter_rst_n) begin
if (!adapter_rst_n) begin
phy_op_meta_q <= 1'b0;
phy_op_sync_q <= 1'b0;
end else begin
phy_op_meta_q <= phy_operational_async;
phy_op_sync_q <= phy_op_meta_q;
end
endThe first flop absorbs the metastability and is given a full destination clock period to resolve; the second presents a clean value. This is correct only because the signal is a single bit that changes rarely and whose destination logic tolerates a cycle or two of latency.
And the nuance that separates a good verification engineer from a competent one:
SVA cannot prove metastability safety.
An assertion can prove that the Adapter uses the synchronised version rather than the raw one, that the protocol timing after synchronisation is correct, and that a handshake's request and acknowledge sequence properly. It cannot prove that a flop resolves in time, because metastability is an analogue phenomenon and the simulator does not model it.
What proves it is structural CDC analysis — a tool that identifies every crossing, checks each has an appropriate synchroniser, checks that multi-bit crossings use a coherent mechanism, and checks that the source is stable for long enough. Add metastability injection in simulation to confirm the design tolerates the delay a real synchroniser introduces. Assertions are a complement to that, never a substitute.
// Illustrative — the Adapter must consume the synchronised status only.
property p_adapter_uses_synced_status;
@(posedge adapter_clk) disable iff (!adapter_rst_n)
rdi_fire |-> phy_op_sync_q;
endproperty16. Status Abstraction Protects Both Sides
The PHY observes a great deal internally: which lanes failed, how many training attempts ran, calibration results, clock loss, error counts per lane. Almost none of that belongs to the Adapter.
// WRONG — transport logic reaching into physical implementation detail.
assign adapter_retry_request = !lane_good_q[17];Three things are broken here. The Adapter now depends on a lane count and numbering, so it breaks on any width change. It depends on repair not having happened, since after repair lane 17 may not be the relevant lane. And it has taken a policy decision that is not its own — deciding that one bad lane warrants a transport-level response, when the PHY may be able to repair around it with no transport impact at all.
// Illustrative — the PHY draws the conclusion; the Adapter consumes it.
assign phy_link_fault = (phy_state_q == PHY_RECOVER) ||
(phy_state_q == PHY_FAILED) ||
degraded_beyond_configured_width;The PHY, which knows how many lanes it has, whether spares exist, whether repair succeeded, and what width was negotiated, decides whether the physical situation amounts to something the Adapter must act on. The Adapter receives a conclusion.
The Adapter should receive conclusions, not evidence. Evidence changes with every implementation; conclusions are the interface.
This does not mean the detail is hidden from everyone. Diagnostics, telemetry, and debug interfaces absolutely need the per-lane view — that is exactly the distinction Chapter 5.4 drew between a narrow functional contract and a wide observability path. What must not happen is functional logic taking a dependency on the wide path.
17. Configuration Must Be Snapshotted
An underrated source of irreproducible bring-up failures.
// Illustrative architecture RTL — not UCIe normative signal naming.
typedef struct packed {
logic [7:0] lane_count; // configured width
logic [3:0] rate_sel; // per-lane rate selection
logic enable_training;
logic loopback_mode;
} phy_cfg_t;
phy_cfg_t active_cfg_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) active_cfg_q <= '0;
else if (start_training) active_cfg_q <= cfg_in; // snapshot, once
endArchitecture. Training is a multi-cycle process whose stages all assume the same configuration. Configuration arrives from somewhere else — firmware, fuses, a register interface, a debug tool — and that somewhere else has its own timing. If the training engine reads cfg_in continuously, a write at any point during training changes the assumptions of the stages that have not run yet.
State. One registered copy, captured on the single-cycle start_training pulse from §7.
Cycle behaviour. Written on exactly one cycle per training attempt. Read continuously by every stage. cfg_in may change freely afterwards without any effect until the next attempt.
Contract. Every consumer of configuration reads active_cfg_q, never cfg_in. That rule is the whole mechanism, and it is worth enforcing in review, because a single stage reading the live input reintroduces the bug for everyone.
Failure — and the reason this is worth a section. Software writes a new lane count midway through training. The stages that already ran used the old width; the stages still to run use the new one. Training may complete and report success, with the two ends configured differently. Or it may fail in a way that depends entirely on when the write landed relative to the training stages — which means it reproduces on some boots and not others, with no correlation to anything the debug engineer can see. Irreproducible bring-up failures are the most expensive class of bug in this entire subject, and this is one of their most common causes.
DV.
// Illustrative — the active configuration may not change during training.
property p_cfg_stable_during_training;
@(posedge clk) disable iff (!rst_n)
(phy_state_q == PHY_TRAIN) |=> $stable(active_cfg_q);
endproperty
a_cfg_stable_during_training :
assert property (p_cfg_stable_during_training)
else $error("Active PHY configuration changed during training.");Then, in the testbench, write configuration deliberately during training — at several different points — and confirm the assertion holds and the outcome is unchanged. A snapshot mechanism that is never attacked is a snapshot mechanism nobody has verified.
18. Bounded Waiting
Training must not wait forever. The timer in §7 gives it a bound; the reasoning behind bounded waiting is worth stating explicitly.
Why it exists. Every prerequisite in bring-up depends on a peer that may not be there, a clock that may not be running, or a channel that may not work. Without a bound, any one of those turns into a silent hang — and a hang is the least diagnosable failure mode there is, because it produces no event to trace back from.
What the bound must not be. A fixed number of cycles derived from a simulation run. Training duration varies with the channel, the configuration, and the peer. The timeout must be a generous upper bound whose expiry means something is genuinely wrong, not this was slower than the model. UCIe defines its own timing requirements for its actual state machine; take those from the specification revision, not from a tutorial.
What expiry must do. Move to a state that is visibly abnormal, retain enough information to diagnose which prerequisite was missing, and stop — rather than retrying indefinitely, which converts a diagnosable failure into a livelock.
The counter invariants are simple and worth asserting, because a timer that is not cleared correctly produces spurious timeouts on a later attempt — a bug that only appears on the second bring-up:
// Illustrative — timer discipline.
property p_timer_only_runs_in_train;
@(posedge clk) disable iff (!rst_n)
(phy_state_q != PHY_TRAIN) |=> (train_timer_q == '0);
endproperty
property p_timeout_leaves_train;
@(posedge clk) disable iff (!rst_n)
((phy_state_q == PHY_TRAIN) && train_timeout) |=> (phy_state_q != PHY_TRAIN);
endpropertyThe second is a bounded liveness property expressed as safety — it does not promise training eventually succeeds, which would be unprovable, but it does promise the machine cannot sit in TRAIN after its bound expired. That is the strongest honest statement available, and it is the one that catches a stuck FSM.
19. Degradation After Success
A link that works is not a link that will keep working. Temperature drifts, voltage moves, a marginal lane crosses its limit, the peer enters an unexpected state. So the model needs a path from ACTIVE that is not FAILED.
RECOVER exists because degradation after operation is a genuinely different situation from never having worked:
- The configuration is known good, because it worked. That is diagnostic information a first bring-up does not have.
- Traffic is in flight, so §12's accepted-data obligation is live in a way it never is during initial bring-up.
- A less drastic remedy may suffice — retraining a subset, repairing a lane, recalibrating — rather than starting over.
- The Adapter must be told, because its own state depends on whether the link is carrying anything.
Exit is to ACTIVE if restoration succeeds, or FAILED if attempts are exhausted. Exhaustion must be bounded for the same reason training is: unbounded recovery attempts turn a hard failure into an intermittent one, which is strictly worse to diagnose.
UCIe's actual recovery and retraining flow is a Module 8 subject. What matters here is the structural point: the state model must have somewhere to go when a working link stops working, and that somewhere must be visible to the Adapter.
20. Sideband, Briefly
One structural fact belongs in the ownership model, and the rest belongs to later chapters.
Bringing up a link is a negotiation. The two dies must agree on width, on rate, on which lanes are usable, on mapping — and they must do that before the mainband is capable of carrying anything, because agreeing on how the mainband works is the point of the exercise. That is a bootstrapping problem, and it needs a communication path that works before the main data path does.
UCIe provides a sideband: a separate, simpler, lower-rate path used for parameter exchange and negotiation between the two dies, with its own initialisation and training. It is part of the PHY's responsibility, alongside mainband training.
Two consequences for the ownership model:
- The sideband is functional infrastructure, not a debug feature. The link does not come up without it. A sideband problem presents as a mainband that never trains, which is a diagnosis worth reaching quickly.
- It is why bring-up is a two-die process. Nothing in this chapter's FSM happens unilaterally; every transition out of WAIT_CFG depends on a peer that is also progressing.
The sideband protocol, its encoding, and its own training flow belong to the chapters that own them.
21. Debugging a PHY That Will Not Come Up
Work down the prerequisite chain. Each step is answerable from state you can read, and each eliminates everything below it.
- Is reset actually released? Check the PHY's reset at the PHY, not at its source — reset trees have their own bugs.
- Is configuration present and legal? If the FSM is parked in WAIT_CFG,
cfg_validorcfg_legalis low, and the reason is upstream of the PHY entirely. - Did the machine enter TRAIN? If not, stop here — the problem is a prerequisite, not training.
- Is the peer alive and is the control path working? No die trains alone. A sideband that has not initialised presents as a mainband that never trains (§20).
- Which prerequisite is the training engine waiting on? This is why training sub-state must be observable. A stalled bring-up with no visibility is an architectural failure as much as a functional one.
- Is the lane-enable mask legal? A configured width the physical link cannot support, or a mask the peer disagrees with, stalls negotiation with everything apparently healthy.
- Is the status crossing correct? The PHY may be in ACTIVE while the Adapter has never observed it, if the crossing is broken (§15).
- Has the timeout fired? If yes, the FSM is in FAILED and the captured state at expiry tells you which prerequisite was missing. If no, and it has been a long time, the timer itself is suspect.
- Is the FSM stuck in a state with no exit condition satisfiable? Check the inputs of the transition it is waiting on, individually.
- Did the Adapter observe operational status? If the PHY says ACTIVE and the Adapter disagrees, the problem is between them, not in either.
Steps 1 through 3 are configuration questions and take minutes. Steps 4 through 6 need PHY observability. Only after all of them does channel and analogue investigation make sense — and by then you know which link and which prerequisite, which is the difference between a targeted measurement and a fishing expedition.
22. Debugging a Trained Link With Bad Data
Different problem, different order, and the first question is the most informative one you can ask.
Is the corruption deterministic or statistical?
| Observation | Deterministic | Statistical |
|---|---|---|
| Same bits wrong every time | yes | no |
| Reproduces identically across runs | yes | no |
| Sensitive to temperature or voltage | no | yes |
| Rate changes with link rate | usually no | yes |
| Concentrated on specific lanes | possibly, but fixed | worst lanes first |
| Most likely cause | mapping, ordering, alignment, enable mask | analogue margin, channel, PDN |
Deterministic corruption is arithmetic — bits arrive intact and are reassembled wrongly. Check, in order: lane mapping consistency between the two ends; lane ordering and reversal; the enabled-lane mask agreement (§13); alignment of the received stream; and the clock-domain crossing, since a broken multi-bit crossing produces values that never existed and looks like corruption (§15).
Statistical corruption is physics. Check error counters per lane, look at whether the rate scales with link rate and with temperature, and hand it to signal-integrity and power-integrity analysis with the per-lane data that makes their job tractable.
The heuristic is not absolute — a marginal lane that fails almost always can look deterministic, and a mapping error confined to lanes that are rarely used can look sporadic. But it is right often enough to be the first question, and asking it costs nothing.
23. Verifying a PHY
The test plan for the digital PHY, organised by what it is proving:
State machine. Every legal transition taken. Every illegal transition proven impossible by assertion. Recovery from an illegal state encoding. Prerequisites enforced — ACTIVE unreachable without training, training unstartable without legal configuration.
Configuration. Minimum and maximum width. Every legal rate. Illegal configurations rejected rather than attempted. Configuration written during training, at several points, with the snapshot holding (§17).
Lanes. Individual lane disable. Lane failure during training. Lane failure after ACTIVE, driving the RECOVER path. Mapping permutations including reversal. The enable-versus-good invariant attacked by a configuration path that tries to violate it.
Clock domains. Structural CDC analysis over the whole boundary — not assertions alone. Metastability injection to confirm the design tolerates synchroniser delay. Verification that no functional path reads a raw asynchronous signal.
Error injection. Lane fault, clock loss, training timeout, transient error during ACTIVE, peer disappearing mid-training. Each should produce a defined, observable outcome — the test is not that it survives, but that it reports.
Adapter-facing behaviour. No acceptance while not operational. No data loss under stall, verified by scoreboard rather than by assertion. Correct status reporting through the crossing. Accepted items either transmitted or reported lost, never neither (§12).
The organising principle: most of these are digital design bugs in a block that people file mentally under "analogue". That mental filing is why they survive to silicon.
24. Coverage Beyond State
State coverage alone is weak — it tells you the machine visited each state, not that it visited them in the situations that matter.
// Illustrative configuration and state coverage — not UCIe-defined.
covergroup cg_phy @(posedge phy_clk);
cp_state : coverpoint phy_state_q {
bins reset = {PHY_RESET};
bins waitcfg = {PHY_WAIT_CFG};
bins train = {PHY_TRAIN};
bins active = {PHY_ACTIVE};
bins recover = {PHY_RECOVER};
bins failed = {PHY_FAILED};
}
cp_width : coverpoint active_cfg_q.lane_count {
bins minimum = {MIN_LANES};
bins mid[] = {[MIN_LANES+1 : NUM_LANES-1]};
bins full = {NUM_LANES};
}
cp_degraded : coverpoint (lane_good_q != '1);
// Was RECOVER ever entered from a degraded, narrow configuration?
x_recover_by_width : cross cp_state, cp_width;
x_degraded_by_state : cross cp_state, cp_degraded;
endgroupWhy the crosses carry the value. Reaching RECOVER at full width with every lane healthy is easy and proves little. Reaching it at minimum width with lanes already degraded is where recovery logic actually breaks, because that is where the remaining margin is smallest and the code paths least travelled. Hitting each axis separately does not imply hitting the corner, and the corner is what ships in a marginal part.
Keep it proportionate. This is configuration and state coverage, not a class-based verification environment — that belongs to a methodology track, not to a chapter establishing an ownership model.
25. Why Interviewers Probe This Chapter
The distinctions here separate an engineer who has integrated a PHY from one who has read about one. A strong answer distinguishes:
- Adapter transport responsibility from PHY physical responsibility — and can say why retry does not fix a dead lane.
- Logical PHY from analogue front end — and knows that most PHY bring-up bugs are in the digital half.
- Reset released from operational — and can describe the failure mode of confusing them.
- Lane physically good from lane logically enabled — and knows the deterministic-corruption signature of getting it wrong.
- Evidence from conclusions at the interface — and can explain why an Adapter reading lane health is a design defect rather than a shortcut.
Interviewers probe these because each one is a boundary, and boundaries are where integration fails. Anyone can describe a block. Describing precisely what one block is entitled to assume about another is the skill that determines whether two teams' correct designs work together.
26. Common Misconceptions
"PHY means analogue." A UCIe PHY has a substantial logical half: state machines, configuration, lane masks, mapping, status, and CDC — all ordinary synchronous design, and where most bring-up bugs live (§3).
"The PHY just serialises data." It also organises data onto physical resources, establishes and monitors physical readiness, negotiates with the peer over a sideband, and abstracts physical state into status (§2).
"Reset deasserted means the link is ready." Reset is local and says nothing about the peer, clocking, lanes, training, or agreement on configuration (§9).
"Training complete and Adapter-ready are the same instant." They are separated by a clock-domain crossing. The Adapter may act only on the synchronised status (§8, §15).
"The Adapter should inspect lane health." That couples transport logic to lane count, numbering, and repair state, and takes a policy decision the PHY is better placed to make (§16).
"Every good lane must be enabled." Width may be configured narrower, the peer may have disabled a counterpart, or a lane may be held as a spare. Good and enabled are independent (§13).
"If training passes, the mapping must be right." Training can establish a perfectly healthy channel while the two ends disagree about lane order — producing deterministic total corruption (§14).
"A two-flop synchroniser solves any CDC." It handles a single stable bit. Applied per bit to a multi-bit value it constructs values that never existed at the source (§15).
"SVA proves metastability safety." It proves synchronised values are used and protocol timing holds. Metastability resolution needs structural CDC analysis (§15).
"Configuration can change freely during training." Different stages then use different assumptions, producing bring-up failures that reproduce on some boots and not others (§17).
"The PHY can drop accepted traffic when the link fails." Once accepted, the item's fate must be knowable — retained through recovery, or reported so the Adapter's mechanism can act (§12).
"The PHY owns protocol ordering." Ordering and transaction semantics are above it. A PHY that inspects payload has taken on a responsibility that breaks on the next protocol change (§4).
"Sideband is only for debug." It carries the parameter exchange without which the mainband never comes up. A sideband failure presents as a mainband that never trains (§20).
27. Understanding Check
28. Summary and What Comes Next
The PHY turns imperfect physical conductors into an abstract digital link resource the Adapter can trust. It does three separable jobs: move signals electrically, organise data onto physical resources, and establish and maintain physical readiness. It is not an analogue block — it has an analogue front end and a substantial logical PHY of ordinary synchronous digital design, and the logical half is where most bring-up bugs live.
The boundary is RDI, and the way to hold it is that the PHY is accountable for the link being physically usable while the Adapter is accountable for what is carried being correct. Data descends, status ascends, and responsibility transfers on acceptance.
The digital core is state. Readiness must be a state reachable only through training, never an input like reset. Acceptance must be gated on both operational status and available storage, and once an item is accepted its fate must remain knowable — retained through recovery or reported as lost, never silently discarded. Lane health and lane enable are independent facts needing separate masks. Mapping is real state whose failure signature — deterministic, total, repeatable corruption on a healthy link — is the most useful diagnostic pattern in this chapter. Configuration must be snapshotted at training start, because live configuration produces irreproducible bring-up failures. Waiting must be bounded, because a hang is the least diagnosable failure there is. And status crossing into the Adapter's domain must be synchronised properly, with the multi-bit trap understood and structural CDC analysis — not assertions — doing the proving.
Above everything, the abstraction rule: the Adapter receives conclusions, not evidence. Per-lane detail belongs on the wide observability path for diagnostics and debug; it must never appear in a functional dependency, because evidence changes with every implementation while conclusions are the interface.
This chapter has treated the electrical layer as a black box that either works or does not. The rest of Module 7 opens it:
- 7.2 — Electrical Signalling — voltage, signalling, termination, and how a bit actually survives the channel.
Browse the full path on the UCIe tutorials index.