Skip to content

UCIe · Module 8

Link States

The UCIe link state machine as a set of contracts — what each state guarantees, state retention across transitions, quiescence before low power, recovery ownership, transition priority, legal-transition assertions, and the ACTIVE-to-recovery transfer race.

Chapter 8.5 followed a link to the moment it reports itself operational. That moment is not the end of the link's state machine — it is roughly the beginning of the part that runs for the lifetime of the system.

A link that is up will, over the hours and years that follow, be asked to enter low power and come back, will lose trust in its physical layer and have to recover it, will be retrained, and may be reset out from under whatever was using it. Each of those is a state transition, and each one raises the same three questions: what is guaranteed now, what may be retained across the change, and who is allowed to make it happen.

Getting those answers wrong does not produce a link that fails to come up. It produces a link that comes up perfectly and then loses data three hours later during a power transition — which is a much worse bug to have.

1. The One-Sentence Model

A link state is a contract, not a label.

Each state is a set of promises. Which physical resources are valid. Whether traffic may move. Which clocks are running. What configuration is retained. Which transitions out are legal. What it costs to leave.

Treating a state as a label — a value software reads to know "where we are" — produces designs where the state register and the actual behaviour disagree, because nothing enforced the promises. Treating it as a contract produces the assertions in §11 and the retention table in §6, both of which are enforceable.

2. What the Specification Names

UCIe's link training state machine is described with these top-level states:

StateRole as describedTraffic?
RESETentered from a primary physical-layer reset, from TRAINERROR, or from the deeper low-power stateno
SBINITsideband initialisation (Chapter 8.2)no
MBINITmainband initialisation — calibration, repair, reversal (Chapter 8.3)no
MBTRAINmainband training; the link moves to the highest negotiated rateno
LINKINITlink initialisation work following trainingno
ACTIVEphysical-layer initialisation complete; the link is usableyes
L1a standby that disables much of the connection, especially across the mainbandno
L2a deeper standby, including clock shutdown, saving more powerno
PHYRETRAINretraining the linkno
TRAINERRORhandling an error during trainingno

Grouping them pedagogically — and this grouping is mine, not the specification's:

  • Initialisation — RESET, SBINIT, MBINIT, MBTRAIN, LINKINIT. Chapters 8.1 to 8.4 own these.
  • Operational — ACTIVE. The only traffic-bearing state in the list above.
  • Low power — L1, L2.
  • Recovery and error — PHYRETRAIN, TRAINERROR.

Two structural observations worth extracting immediately.

RESET has three named entry paths, and one of them is from the deeper low-power state. That is not an error path — it says exiting L2 goes back through initialisation rather than straight to ACTIVE, which is §9's whole point about wake cost.

There are two distinct non-ACTIVE ways to be unhealthy. PHYRETRAIN is for retraining a link that was working; TRAINERROR is for an error during training. Those are different situations with different amounts of surviving context, and §10 develops why that distinction matters.

A simplified UCIe link state machine. RESET leads through SBINIT, MBINIT, MBTRAIN and LINKINIT to ACTIVE. ACTIVE can enter L1 standby and return, L1 can go deeper to L2, and ACTIVE can enter PHY retrain which returns to mainband training. Mainband training can enter the training error state, which returns to RESET.RESETSBINITMBINITMBTRAINLINKINITACTIVETRAINERRORPHYRETRAINL1L2prereqs metprereqs metsideband upsideband upmainband initmainband inittrainedtrainedinit doneinit doneL1 entryL1 entrywakewakedeeperdeeperretrainretrainre-trainre-trainerrorerrorrestartrestart
Figure 1 — a simplified view of the link state machine, drawn to show structure rather than to be a complete or normative transition map. Several real transitions are omitted for legibility — notably the exit from the deeper low-power state, which published descriptions place back through RESET, and which section 2's table records instead. Read the shape: one initialisation chain, a single traffic-bearing state, low-power states hanging off it, and two distinct unhealthy states depending on whether the link was previously working.

3. Representing State in RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative RTL representation. A compliant implementation uses the
// specification's own states and encodings — this is a teaching abstraction
// grouped by CONTRACT rather than by name.
typedef enum logic [2:0] {
  LINK_RESET     = 3'd0,   // nothing assumed
  LINK_TRAINING  = 3'd1,   // initialisation and training in progress
  LINK_ACTIVE    = 3'd2,   // traffic permitted
  LINK_LP_LIGHT  = 3'd3,   // standby, context largely retained
  LINK_LP_DEEP   = 3'd4,   // deeper standby, more torn down
  LINK_RECOVERY  = 3'd5,   // was working; restoring physical trust
  LINK_FAILED    = 3'd6    // cannot establish or restore
} link_state_t;
 
link_state_t link_state_q, link_state_d, prev_state_q;

Architecture. The specification's ten states collapse, for most upper-layer purposes, into a much smaller set of contracts. Logic that only needs to know "may I send?" should not be coupled to the full training sub-state structure — that is Chapter 7.1 §16's evidence-versus-conclusions rule at state granularity.

State. Current state, next state, and previous state. prev_state_q exists for §16 and costs three flops.

Cycle behaviour. One transition per clock.

Contract. Upper layers consume a derived traffic_allowed (§12), never the raw state. Diagnostics consume the full state.

Failure. Exposing the detailed state upward couples the Adapter to the PHY's state machine, so a specification revision that adds a sub-state becomes an Adapter change.

A boundary worth stating. This enum is a view, not a replacement. A real PHY implements the specification's state machine; this abstraction sits above it. Do not treat the two as interchangeable — the mapping is one-way.

4. Next-State Discipline

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative link-state control — not UCIe's normative transition logic.
always_comb begin
  link_state_d = link_state_q;                    // explicit default: hold
 
  unique case (link_state_q)
    LINK_RESET    : if (bringup_start)        link_state_d = LINK_TRAINING;
    LINK_TRAINING : if      (training_failed) link_state_d = LINK_FAILED;
                    else if (training_done)   link_state_d = LINK_ACTIVE;
    LINK_ACTIVE   : if      (lp_entry_agreed) link_state_d = LINK_LP_LIGHT;
                    else if (link_fault)      link_state_d = LINK_RECOVERY;
    LINK_LP_LIGHT : if      (lp_deeper_agreed)link_state_d = LINK_LP_DEEP;
                    else if (wake_complete)   link_state_d = LINK_ACTIVE;
    LINK_LP_DEEP  : if (wake_request)         link_state_d = LINK_RESET;  // §9
    LINK_RECOVERY : if      (recovery_failed) link_state_d = LINK_FAILED;
                    else if (recovery_done)   link_state_d = LINK_ACTIVE;
    LINK_FAILED   : ;                              // terminal until reset
    default       : link_state_d = LINK_RESET;     // illegal encoding recovers
  endcase
 
  // --- priority overrides, applied AFTER the case (§13) -----------------
  if (fatal_error)  link_state_d = LINK_FAILED;
  if (reset_assert) link_state_d = LINK_RESET;
end

Architecture. A state machine with several independent event sources needs an unambiguous resolution order, and it needs one place where that order is expressed.

State. None beyond the registers in §3.

Cycle behaviour. The case computes the normal-operation next state; the overrides below it then take precedence, in a written order. Note LINK_LP_DEEP exits to LINK_RESET rather than to LINK_ACTIVE — modelling the described behaviour that leaving the deeper low-power state goes back through initialisation.

Contract. Everything downstream may assume exactly one next state is computed per cycle.

Failure. Without the explicit default, an unhandled condition infers a latch. Without the default: arm, an illegal encoding has no recovery. And without the override structure, §13's ambiguity appears.

5. Entry Versus Residence

A distinction that produces a real and common bug.

Some actions belong to entering a state; others to being in it. Conflating them means a one-time action runs every cycle.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — intended as "on entry", executes every ACTIVE cycle.
if (link_state_q == LINK_ACTIVE)
  clear_error_history <= 1'b1;

If the intent was to clear counters once when the link becomes usable, this instead clears them continuously for as long as the link is up — so every error counter reads zero forever, and the telemetry Chapter 7.6 built is silently destroyed. The link works; the diagnostics are gone; nobody notices until something goes wrong and there is nothing to look at.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — a genuine one-cycle entry pulse, from registered state.
logic entered_active;
assign entered_active = (prev_state_q != LINK_ACTIVE) &&
                        (link_state_q == LINK_ACTIVE);
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) prev_state_q <= LINK_RESET;
  else        prev_state_q <= link_state_q;
end

Why registered rather than comparing against link_state_d. Writing (link_state_q != LINK_ACTIVE) && (link_state_d == LINK_ACTIVE) also produces a pulse, but it is combinational on the next-state function — so it glitches as the case statement's inputs settle, and it fires a cycle earlier than the state actually changes. For a pulse that gates a real action, deriving from two registered values is safer: it costs three flops and cannot glitch.

DV. Assert the pulse is exactly one cycle wide, and cover that each state's entry action fired once per entry rather than once per residence — a distinction ordinary state coverage does not make.

6. The State-Retention Matrix

The table this chapter exists for. What survives a transition is the difference between a low-power state that saves energy and one that loses data.

State elementACTIVELight low powerDeeper low powerRecoveryRESET
Lane mapin useretainspec/impl dependentretain if the lane set is unchangedinvalidate
Deskew settingsin useretainspec/impl dependentre-establish if retrainedinvalidate
Calibration settingsin useretainlikely re-established (§9)re-establish if disturbedinvalidate
Negotiated parametersin useretainspec/impl dependentretaininvalidate — renegotiate
Accepted-but-unsent payloadin flightmust be none (§8)must be nonemust not vanish (§10)architecture decides (Ch 8.1 §9)
Credit / flow-control statein useretain, or re-advertise on exitre-advertisere-advertise on re-entryreset to advertised
Diagnostic countersaccumulatingretainretainretainretain — broader domain (Ch 8.1 §18)
State historyaccumulatingretainretainretainretain

Three rows carry most of the weight.

Accepted-but-unsent payload. In the low-power columns the entry is not "retain" or "discard" — it is must be none, because §8 requires quiescence before entry. That is how the problem is avoided rather than solved.

Credit state. Chapter 8.1 §9 noted that UCIe/LPIF material describes credit counters being reassigned to their initially advertised values on transitions away from Active. Re-advertising is the safe default precisely because it makes both ends agree by construction rather than by both having remembered correctly.

Diagnostic counters retain everywhere. They are the only row that is uniform, and deliberately so.

The "spec/impl dependent" entries are honest rather than evasive: how much context a deep low-power state preserves is exactly the kind of thing that differs by revision and implementation, and inventing a value here would be the pre-audit mistake.

7. ACTIVE Is a State You Can Leave

ACTIVE means the physical layer is initialised and the link is usable under the defined contract. It does not mean healthy forever, and designs that treat it as an absorbing state handle degradation badly.

Entering it requires everything Chapter 8.5 assembled:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — ACTIVE requires every prerequisite, currently.
property p_active_requires_prerequisites;
  @(posedge clk) disable iff (!rst_n)
    (link_state_q == LINK_ACTIVE) |-> (training_done_q && calibration_valid_q
                                       && peer_operational_q && !fatal_fault_q);
endproperty

Note this holds for every ACTIVE cycle, not just on entry. Chapter 7.2 §14 made the same choice for electrical readiness and for the same reason: a prerequisite that dies underneath a running link must pull the link out, and a transition-only check cannot see that.

8. Quiescence Before Low Power

A low-power state is not "turn the clocks off". It is a contract about what is quiesced, what context remains, what must be restored, and what the wake costs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a request treated as a transition.
if (low_power_req)
  link_state_d = LINK_LP_LIGHT;

Three things are wrong at once. Pending traffic is still in flight and will be stranded or dropped. The peer has not agreed — a link is two ends, and one end powering down its mainband while the other transmits is a data-loss event. And resources may be torn down while still in use, because the request says nothing about local readiness.

A request is the beginning of a negotiation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative low-power sequencing — not UCIe normative signal naming.
logic local_quiescent;
logic lp_entry_agreed;
 
assign local_quiescent =
    (tx_occupancy_q == '0) &&      // nothing waiting to transmit
    (rx_occupancy_q == '0) &&      // nothing received and unconsumed
    !retry_pending_q     &&        // no unacknowledged work outstanding
    !accepted_unsent_q;            // nothing accepted that has not left
 
assign lp_entry_agreed = low_power_req && local_quiescent && peer_lp_ack;

Architecture. Entry must be safe locally and agreed remotely. Either alone is insufficient — local quiescence with an unaware peer loses the peer's traffic, and peer agreement without local quiescence loses your own.

State. Occupancy counters and pending flags that already exist for flow control; the low-power logic consumes rather than duplicates them.

Cycle behaviour. Combinational over registered occupancy. Entry occurs on the cycle all three terms hold.

Contract. The peer relies on your not tearing down resources it is still using. Upper layers rely on nothing accepted being lost.

Failure. Entering on the request alone strands in-flight work — and it is the same violation as Chapter 7.1 §12's accepted-data rule, arriving through a power transition instead of a fault.

A caution. local_quiescent above is illustrative and almost certainly incomplete for a real design: retimers, replay buffers, outstanding sideband transactions, and in-progress calibration all have their own notions of "busy". The lesson is the shape — enumerate every resource that can hold state, and require all of them idle — not this particular conjunction.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — no low-power entry while work is outstanding.
property p_no_lp_entry_with_outstanding_traffic;
  @(posedge clk) disable iff (!rst_n)
    $rose(link_state_q inside {LINK_LP_LIGHT, LINK_LP_DEEP}) |->
      $past(local_quiescent);
endproperty

9. Wake Is Not the Reverse of Entry

Symmetry is the intuition and it is wrong. Entering a low-power state tears down; waking must restore, then re-establish trust, then coordinate — and how much of each depends on how deep the state was.

The described distinction between the two low-power states is instructive. The lighter one disables much of the connection, especially across the mainband. The deeper one goes further, including clock shutdown. And RESET's described entry paths include the deeper low-power state — meaning exit from it goes back through initialisation rather than directly to ACTIVE.

That single fact is the entire wake-cost argument:

Light standbyDeeper standby
Torn downmuch of the mainbandmore, including clocks
Context retainedmoreless
Exit pathback toward ACTIVEback through initialisation
Wake latencylowersubstantially higher
Power savedlessmore

Low-power states are a latency-versus-power ladder, and the rung you choose is a system decision. A deeper state saves more and costs a full re-initialisation to leave — which for a latency-sensitive coherent link may be unacceptable, and for an idle accelerator may be free.

The digital consequence: wake is not a single transition to model. It is clocks restored, PLL relocked and requalified (Chapter 7.5 §11), retained state validated rather than assumed, the peer coordinated with, and only then traffic permitted. Each of those can fail, which means wake needs the same bounded-wait discipline as bring-up.

10. Recovery, and Who Owns What

Recovery exists because a link can lose trust while the system still has state depending on it.

That sentence separates recovery from initialisation. At initialisation nothing depends on the link yet. At recovery, transactions are outstanding, buffers hold accepted work, and upper layers have made commitments. The physical problem is often the easy part.

Ownership divides cleanly, and this is where Modules 5 and 7 tie together:

LayerOwns during recovery
Physicalrestoring physical trust — retrain, recalibrate, repair, requalify lanes
Adapterpreserving or resolving transport state — what was accepted, what must be replayed, what is reported lost
Protocolsemantic correctness — outstanding transactions, ordering, completion obligations

Each layer must not solve another's problem. A PHY that discards Adapter state during recovery has made a transport decision it is not entitled to make.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG as a default policy — recovery erases accepted work.
if (enter_recovery) begin
  tx_fifo_valid_q <= '0;
  retry_valid_q   <= '0;
end

This is the recovery-shaped instance of a bug this curriculum has now met four times — at the Adapter boundary (5.5 §7), at the PHY boundary (7.1 §12), at a clock loss (7.5 §14), and at a partial reset (8.1 §17). The rule does not change:

A flush is acceptable. An unannounced flush is not. If the recovery contract permits abandoning accepted work, it must be reported so the layer above can act.

Recovery also needs a generation, for the same reason bring-up does:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative implementation technique — NOT a UCIe-defined field.
logic [GEN_W-1:0] link_generation_q;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)                 link_generation_q <= '0;
  else if (recovery_complete) link_generation_q <= link_generation_q + 1'b1;
end
 
// A completion from before the link dropped cannot satisfy post-recovery state.
assign completion_is_stale = (cmpl_generation != link_generation_q);

Failure without it: an acknowledgement issued before the link dropped arrives afterwards and is matched against a reused tag — real data credited to the wrong transaction, which is corruption rather than loss.

A pure predicate answering "is from → to legal?" serves assertions, formal, and the scoreboard from one definition.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — one definition of legality, reused three ways.
function automatic logic legal_transition(link_state_t from, link_state_t to);
  if (from == to)            return 1'b1;                    // holding is legal
  if (to == LINK_RESET)      return 1'b1;                    // reset from anywhere
  if (to == LINK_FAILED)     return 1'b1;                    // fatal from anywhere
  unique case (from)
    LINK_RESET    : return (to == LINK_TRAINING);
    LINK_TRAINING : return (to == LINK_ACTIVE);
    LINK_ACTIVE   : return (to inside {LINK_LP_LIGHT, LINK_RECOVERY});
    LINK_LP_LIGHT : return (to inside {LINK_ACTIVE, LINK_LP_DEEP});
    LINK_LP_DEEP  : return (to == LINK_RESET);
    LINK_RECOVERY : return (to == LINK_ACTIVE);
    LINK_FAILED   : return 1'b0;
    default       : return 1'b0;
  endcase
endfunction
 
property p_only_legal_transitions;
  @(posedge clk) disable iff (!rst_n)
    legal_transition($past(link_state_q), link_state_q);
endproperty
 
a_only_legal_transitions :
  assert property (p_only_legal_transitions)
  else $error("Illegal transition %0s -> %0s", $past(link_state_q).name(),
              link_state_q.name());

Why a function rather than an expression. Three consumers need the same answer — the assertion, a formal property, and the scoreboard in §15 — and three copies of a transition table drift. It is also readable, which a single flattened boolean would not be.

Note the top three lines. Reset-from-anywhere and fatal-from-anywhere are legitimate and must be encoded, or the assertion fires on correct behaviour and gets waived — which is worse than not having it.

12. Traffic Permission Is Derived

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — one definition of "may traffic move", not a state comparison.
assign traffic_allowed = (link_state_q == LINK_ACTIVE) && link_operational_sync;
 
property p_traffic_only_when_allowed;
  @(posedge clk) disable iff (!rst_n)
    payload_transfer |-> traffic_allowed;
endproperty

Why derive it rather than compare against ACTIVE inline. Two reasons. If a revision or configuration permits traffic in another state, one definition changes and every consumer follows. And it composes the state with Chapter 8.5's operational conjunction, so a prerequisite dying withdraws permission even while the state register still reads ACTIVE — which is precisely the window §14's race lives in.

13. Simultaneous Requests Need Explicit Priority

Real systems present a low-power request, an error, and a reset on the same cycle.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — two independent ifs; behaviour depends on source order.
if (low_power_req) link_state_d = LINK_LP_LIGHT;
if (link_fault)    link_state_d = LINK_RECOVERY;

Whichever assignment appears later wins. That is a real decision — should a fault beat a power request? — being made by text ordering rather than by design, and it changes silently when someone reorders the block.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — priority stated once, explicitly, highest first.
always_comb begin
  link_state_d = normal_next_state;          // from the case in §4
  if      (lp_entry_agreed)  link_state_d = LINK_LP_LIGHT;
  if      (link_fault)       link_state_d = LINK_RECOVERY;   // beats power
  if      (fatal_error)      link_state_d = LINK_FAILED;     // beats recovery
  if      (reset_assert)     link_state_d = LINK_RESET;      // beats everything
end

A defensible ordering — reset > fatal > recovery > power > normal — with the reasoning: reset is unconditional by construction; an unrecoverable fault must not be masked by a recovery attempt; a fault must not be masked by a power transition, because powering down a faulty link hides the fault; and power transitions are discretionary.

This ordering is not normative. Where the specification defines precedence, use that. What is universal is that the priority must be written once, explicitly, rather than emerging from statement order.

14. The Race: ACTIVE → Recovery With a Transfer In Flight

The chapter's sharpest corner case, and the one that connects to Chapter 5.5.

Same cycle: the Adapter presents valid, the PHY's ready is asserted, and a fault is detected that will move the link toward recovery.

The question is exact: did the handshake occur? Ready-and-valid on the same edge is a transfer. If both were high, the item is the PHY's, and the state transition cannot silently delete it.

CycleStatevalidreadyTransfer?OccupancyAction
nACTIVE11yes0 → 1item accepted; PHY now owns it
nACTIVEfault detected in the same cycle
n+1ACTIVE10no1ready withdrawn — no new accepts
n+1ACTIVE1accepted item retained, not cleared
n+2RECOVERY00no1state moves; item still accounted for
n+3RECOVERY1either it survives recovery…
n+3′RECOVERY1 → 0…or it is reported lost to the Adapter

The design rules the trace encodes:

Withdraw ready first, transition second. Stopping new acceptance is a separate action from changing state, and doing them in that order closes the window in which an item is accepted into a link that is already leaving ACTIVE.

A transfer that completed is owned. The transition may not clear it. It survives or it is reported — never neither.

Do not retroactively un-accept. Deasserting ready in cycle n to "cancel" a handshake that already occurred is not possible; the Adapter has already retired its copy.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — a transfer that completed is not erased by a transition.
property p_accepted_survives_state_change;
  @(posedge clk) disable iff (!rst_n)
    (payload_transfer && $changed(link_state_q)) |=>
      (item_retained || item_loss_reported);
endproperty
 
// Illustrative — recovery does not silently reduce occupancy.
property p_recovery_no_silent_discard;
  @(posedge clk) disable iff (!rst_n)
    ($rose(link_state_q == LINK_RECOVERY) && (tx_occupancy_q != '0)) |->
      ##[0:$] (item_loss_reported || (tx_occupancy_q == '0 && drain_complete));
endproperty

The second is a liveness property and needs care: it is only meaningful with a fairness assumption that recovery eventually resolves, and it should be bounded in practice rather than left as ##[0:$]. Written unbounded it is a formal-tool property, not a simulation one — worth knowing which you are writing.

15. State Timeouts and Debug History

Transitional states should not persist indefinitely; stable states should not be timed out. Attaching a watchdog to ACTIVE is wrong — a healthy link stays there for years. Attaching one to RECOVERY or a wake sequence is right.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — state age, derived from REGISTERED state only.
logic [AGE_W-1:0] state_age_q;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)                          state_age_q <= '0;
  else if (link_state_q != prev_state_q) state_age_q <= '0;   // just entered
  else if (!(&state_age_q))            state_age_q <= state_age_q + 1'b1;
end

Note the comparison. It uses link_state_q against prev_state_q — two registered values — rather than link_state_q != link_state_d, which compares a register against a combinational function and clears the age a cycle early while glitching as the next-state logic settles. The same subtlety as §5.

For silicon debug, a current-state register alone is nearly useless — it tells you where the machine ended, not how it got there:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative debug state — small, and worth far more than its area.
link_state_t          prev_state_q;        // already needed for §5
link_state_t          state_before_fail_q; // state at the first failure
transition_cause_t    last_cause_q;        // what caused the last transition
logic [CNT_W-1:0]     recovery_count_q;    // saturating — link flapping?
logic [CNT_W-1:0]     lp_entry_count_q;    // saturating

Why recovery_count_q matters most. A link that recovers successfully every few minutes is working by every functional measure and is a serious problem. Without a saturating count in a domain that survives recovery, it is invisible — Chapter 8.1 §18's rule that observability state must outlive the events it observes.

16. Verifying the State Machine

Transition coverage matters more than state coverage. Visiting every state is easy and proves little; traversing every legal edge is what exercises the entry actions, the retention policy, and the priority logic.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative link-state coverage — not UCIe-defined.
covergroup cg_link_state @(posedge clk iff state_changed);
 
  cp_from : coverpoint prev_state_q;
  cp_to   : coverpoint link_state_q;
  cp_cause: coverpoint last_cause_q;
 
  // Every legal edge, with illegal ones excluded rather than left unreachable.
  x_transition : cross cp_from, cp_to {
    ignore_bins illegal = x_transition with (!legal_transition(cp_from, cp_to));
  }
  // Was an error injected from every state that can see one?
  x_error_from : cross cp_from, cp_cause;
 
endgroup

The scoreboard checks what coverage cannot: no illegal transition occurred, traffic moved only when permitted, required state was retained across each transition, and the exit reason matches the injected event. That last check is the one that catches a state machine which reaches the right state for the wrong reason — which passes every functional test and misleads every debug session.

The error-injection list: fault from each state that can see one; reset from each state; low-power request while busy; low-power request racing a fault; wake failure; recovery failure; a transfer completing on the exact cycle of a fault (§14); and an illegal state encoding forced directly.

17. Debug Checklist

  1. What state is it in, and what was the previous state? Current alone is nearly useless (§15).
  2. What caused the last transition? If unrecorded, add it — it is a few flops.
  3. Is traffic_allowed consistent with the state? Disagreement between the state register and the derived permission points at §12.
  4. If it is in ACTIVE but not carrying traffic, check Chapter 8.5's prerequisite bits — the state can be ACTIVE while operational is false.
  5. If it will not leave a low-power state, check the wake sequence stage: clocks, PLL requalification, retained-state validation, peer coordination.
  6. If it will not enter low power, check quiescence term by term — one non-empty queue blocks it, and the conjunction hides which (§8).
  7. How many recoveries have occurred? A flapping link looks healthy at any instant (§15).
  8. Did a transfer complete on the cycle of a state change? §14 — and check the item was retained or reported, not dropped.
  9. Is state retained across transitions per the intended policy? Compare against §6's matrix explicitly.
  10. Was the transition legal? Run the predicate against the observed pair (§11).
  11. Do both ends agree on the state? A link is two state machines, and only one is in front of you.

18. Common Misconceptions

"A link state is a software-visible label." It is a contract: which resources are valid, whether traffic may move, what is retained, which exits are legal, and what leaving costs (§1).

"ACTIVE means no failure can occur." It means the physical layer is initialised and the link is usable now. Prerequisites can die underneath it, which is why the prerequisite assertion holds every cycle rather than only on entry (§7).

"L1 and L2 work like PCIe's." Do not import them. The published UCIe descriptions distinguish a standby that disables much of the mainband from a deeper one including clock shutdown, with exit from the deeper state described as going back through RESET — and the specification is authority for the rest (§2, §9).

"A low-power request means low-power entry." A request begins a negotiation requiring local quiescence and peer agreement. Entering on the request alone strands in-flight work (§8).

"Recovery may clear all state." Recovery restores physical trust; transport and semantic state belong to layers that did not ask for it to be discarded. A flush is acceptable; an unannounced flush is not (§10).

"State transitions do not interact with payload ownership." A transfer that completed on the cycle of a transition is owned by the receiver, and the transition cannot delete it (§14).

"State coverage means the transitions were tested." Visiting states is easy; traversing every legal edge is what exercises entry actions, retention, and priority (§16).

"Two transition requests can be handled by independent if-statements." Then source order decides precedence, silently, and changes when someone reorders the block (§13).

"One state timeout should apply everywhere." Transitional states need bounds; stable states must not have them, or a healthy link times out (§15).

"The current state is enough for silicon debug." It tells you where the machine ended, not how it got there or how often it has been there (§15).

19. Understanding Check

20. Summary and What Comes Next

A link state is a contract, not a label — a set of promises about valid resources, permitted traffic, running clocks, retained configuration, legal exits, and the cost of leaving.

Published descriptions of UCIe's link state machine name RESET, SBINIT, MBINIT, MBTRAIN, LINKINIT, ACTIVE, L1, L2, PHYRETRAIN, and TRAINERROR, with RESET entered from a primary reset, from TRAINERROR, or from the deeper low-power state — and the specification is the authority for the exact set and its transitions.

The engineering that transfers regardless: entry actions are not residence actions, and deriving the entry pulse from two registered states avoids both the glitch and the off-by-one-cycle. Retention is a policy per state element per transition, and accepted payload is handled by requiring quiescence rather than by deciding what to discard. ACTIVE can be left, so its prerequisites are asserted every cycle rather than at entry. Quiescence and peer agreement both gate low-power entry. Wake is not entry reversed — the deeper state costs a re-initialisation, which makes low power a latency-versus-power ladder rather than a switch. Recovery restores physical trust only; transport and semantic state belong to layers that did not consent to losing them, and a flush must be announced. Priority is written once, explicitly, or statement order decides it silently.

Two things to carry into the rest of the curriculum. The ACTIVE-to-recovery race: a transfer that completed on the cycle of a transition is owned, so it survives or is reported — this is the same ownership rule the curriculum has now met at four different boundaries. And transition coverage over state coverage, with a legality predicate shared by assertion, formal, and scoreboard.

Module 8 is complete: the link comes up, and its state machine is understood. Everything so far has been about making a link exist. The next module is about what runs across it — and specifically about UCIe's own lightweight answer for protocols that are neither PCIe nor CXL:

  • 9.1 — The Streaming Model — lightweight packet-stream transport over UCIe, and why it is a core differentiator from PCIe-over-UCIe.

Browse the full path on the UCIe tutorials index.