Skip to content

PCIe · Module 25

Link Training Failures — Which Exit Condition Is Missing?

A link that will not reach L0 is a state machine waiting for evidence it never received. Residency tells you where it stopped — and two different faults can produce the identical residency signature.

Chapter 25.2 §3 routed one case away twice: the Link never reached L0. Nothing above it can be diagnosed until this is resolved.

And the good news is that this is the most tractable class in Module 25, because the LTSSM is a state machine with a small number of states and each one has a small number of exit conditions. The question is never "why won't it train" — it is "which state is it in, and which exit condition has not been satisfied."

1. Sources, Scope, and What Module 18 Owns

2. Training Is a Sequence of Proof Obligations

Each state holds the Link until it has observed enough evidence to justify the next one. That framing makes a large machine tractable, because it converts "why won't it train" into a question with a bounded answer.

The four-question loop, applied to any stuck link:

QuestionAnswered by
which state is it in?the LTSSM state, and its residency (§11)
what does that state require to exit?Module 18's chapter for that state
what evidence has arrived?lane status, training observations, partner behaviour
what is missing?the difference — and that is the finding

Why residency and not just state. A state read once tells you where the machine is now; residency tells you whether it has been there for one microsecond or the whole run — and it distinguishes stuck from looping, which are different faults with different causes (§7).

And the transition history matters as much as the residency. A machine sitting in Polling forever and a machine cycling Detect→Polling→Detect thousands of times both report "not in L0". §13 measured 802 loop transitions in the second case and 0 in the first — the same symptom, entirely different investigations.

3. The First Split

4. The State Progression Table

Each row is a proof obligation, its evidence, and what its failure looks like.

StateBroadly requiresReaching the next state provesFailure signature
Detect (18.2)receiver presencesomething is electrically presentresident in Detect, 0 transitions
Polling (18.3)training ordered-set exchange, lock and alignmentthe partner is communicating, not merely presentresident in Polling, or Polling↔Detect looping
Configuration (18.4)link and lane numbering agreedboth ends agree on the link's shaperesident in Configuration, or Configuration↔Polling looping
L0 (18.6)the Link is operationalreached and stays
Recovery (18.5)re-establish lock, config, or change speedthe Link recoveredrepeated entry (§7)

Read the third column, because it is the diagnostic value. Each transition proves something, and that proof eliminates candidate causes permanently. Reaching Polling proves presence. Reaching Configuration proves the exchange. Reaching L0 proves the negotiation.

Two deliberate omissions.

No timer values and no ordered-set counts appear in this table. Those are Module 18's, and inventing them here would produce a chapter that fails correct hardware (§1).

And "broadly requires" is deliberately broad. The exact exit conditions are per-state and per-generation (18.3 §3 notes that lock and alignment are generation-specific). This chapter's contribution is the diagnostic loop; the conditions themselves are cited, not restated.

5. Polling Failures

Reaching Polling proves presence (§3). Not leaving it means the exchange did not produce what the state needs (18.3).

Categories, each pointing at a different investigation:

CategoryEvidence to gather
training ordered sets never observeddid the receiver see any at all?
observed but lock or alignment not achievedper-lane lock status (18.3 §3)
lanes disagreethe detected-lane mask versus the active count (§11)
a reset or disable interactionis something asserting reset or Link Disable (18.9)?
the partner is not progressingthe failure may be on the other end entirely

The lane mask is the highest-value observation here, and §11 captures it at the transition. One lane failing to lock while the others succeed is a completely different problem from no lane locking — the first points at a specific lane, connector or channel; the second at the whole link.

And §13's warning applies directly. No training exchange and no lane lock produced identical residency and loop counts — both gate the same edge. Residency says "Polling"; only the guard vector says which condition is absent, which is 25.4 §5's subject.

One thing this chapter will not do. It publishes no ordered-set counts (18.3 §7 owns confidence-by-repetition). A debugging chapter that asserts "N consecutive sets are required" and is wrong produces a false diagnosis with total confidence.

6. Configuration Failures

Reaching Configuration proves presence and a working exchange (§4). Not leaving it is therefore a negotiation problem, not a physical one.

What Configuration is doing (18.4): the two ends agree on the link's shape — which lanes participate, how they are numbered, and what width results.

So the evidence to gather is about agreement:

ObservationPoints at
the negotiated width is narrower than expectedsome lanes did not participate (§5's mask)
the width oscillates across attemptsthe two ends are not converging
Configuration → Polling loopingthe negotiation restarts rather than completes
a specific lane never appears in the negotiated setthat lane, its connector, or its channel

The width observation is the one most often misread. A link that trains to x1 when x8 was expected has succeeded — it reached L0, and it is operating correctly at a narrower width. That is a performance finding, not a training failure (22.6 §6 measured what a wrong width does to a benchmark), and it belongs in a different investigation than a link that never reaches L0 at all.

And "capable x8" is not "negotiated x8" (17.3). §14's mutation 17 is exactly this confusion, and it produces bug reports against hardware that is behaving correctly.

7. Recovery Loops

8. The Equalization Boundary

At higher generations a speed change involves equalization (17.4), and Chapter 18.5 §6 establishes that Recovery.Speed is a multi-phase process, not a register write.

This chapter does not re-teach it. The debugging contribution is narrower and it is a question:

Did the failure occur specifically during the speed-change phase?

If it did, the evidence to gather is what the PHY exposed about the phases — and how much that is depends entirely on the implementation (§1). Some controllers expose per-phase status; many expose almost nothing.

Two things follow.

A link that trains reliably at a lower generation and fails at a higher one localizes to this phase — which is a strong result, because it eliminates presence, exchange and negotiation in one experiment.

And a vendor's equalization status is not a PCIe interface. §14's mutation 22 is describing an implementation's visibility as though every device provides it. Chapter 17.4 owns the procedure; this chapter owns noticing that the failure is in it.

9. The Training Path

The main PCIe link training path for debugging. Detect moves to Polling when receiver presence is established, which proves something is electrically present. Polling moves to Configuration when the training exchange succeeds, which proves the partner is communicating. Polling can also return to Detect, which indicates a loop. Configuration moves to L0 when link and lane numbering are agreed, which proves both ends agree on the link shape. Configuration can return to Polling, which indicates a negotiation loop. L0 is the operational state. L0 moves to Recovery and Recovery returns to L0, which is a retrain rather than a training failure.Detect -presencePolling-exchangeConfiguration- shapeL0 -operationalRecovery-retrainproves presenceproves presenceproves exchangeproves exchangeloop: see 25.4loop: see25.4proves agreed shapeproves agreed shapeloop: renegotiatingloop: renegotiatingretrain or speed changeretrain or speed changeretrain orspeed…recoveredrecovered
Figure 1 — the main training path annotated with what each transition proves rather than with the conditions it requires. Reaching Polling proves electrical presence; reaching Configuration proves a working ordered-set exchange; reaching L0 proves the two ends agreed on the link's shape. Recovery is drawn as a side path from L0 because entering it means the link trained and is now retraining, which is a different investigation.

Four things to read out of the figure.

The edges are labelled with what they prove, not what they require. The requirements are Module 18's and are generation-specific (§1); the proofs are what let you eliminate causes, which is what a debugging diagram is for.

The two backward edges are the loop cases (§5, §6). Each has the same external symptom as the corresponding stuck case and a completely different cause — §13 measured 802 and 1,922 loop transitions respectively.

Recovery hangs off L0, not off the training path. A link in Recovery trained successfully (§7); drawing it inline would suggest otherwise.

And there are no substates here. Chapter 18.3 and 18.5 own them, and 25.4 is where a substate-level diagram earns its place — at this level the four major transitions are what triage needs.

10. The Waveform

Two loops with the same symptom and different last-known-good states

10 cycles
Ten cycles showing two failing link training cases. In case A the state cycles Detect, Polling, Detect, Polling repeatedly and lane lock is never asserted. In case B the state advances Detect, Polling, Configuration and then returns to Polling and Configuration repeatedly, with lane lock asserted throughout and the negotiated width never settling. Neither case asserts L0 reached.A: Polling to Detect — exchange failedA: Polling to Detect —exchange failedB: reached Configuration — exchange OKB: reached Configuration —exchange OKB: back to Polling — no agreementB: back to Polling — noagreementclkA_in_detectA_in_pollA_lane_lockB_in_pollB_in_configB_lane_lockl0_reachedt0t1t2t3t4t5t6t7t8t9
Figure 2 — two failing links, drawn against the same time base. The upper trace reaches Polling and returns to Detect repeatedly: presence is established but the exchange never completes. The lower trace reaches Configuration and returns to Polling: presence and exchange both succeeded and the negotiation does not converge. Neither reaches L0, and the residency and loop signatures distinguish them without any capture.

Four things to read out of the figure.

Both cases report the same thing to software: not in L0. Everything that distinguishes them is in the state trace.

Case A's lane_lock is never asserted, and it returns to Detect. Presence was established and the exchange failed — §5's territory.

Case B's lane_lock is asserted throughout and it reaches Configuration. Presence and exchange both succeeded; the failure is negotiation — §6's territory, and an entirely different set of candidates.

And neither trace says which exit condition is missing. Case A's loop is consistent with both "no training observed" and "observed but no lock" — §13 measured those as identical signatures, and separating them is 25.4's guard vector.

11. RTL — The Training Instruments

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Training-debug types, built on 18.1 §10's state enum.
// The STATE NAMES and their meanings are Module 18's; the reason codes and
// thresholds below are LOCAL (§1).
package train_dbg_pkg;
  import ltssm_pkg::*;                    // ST_DETECT, ST_POLLING, ... (18.1)
 
  parameter int HIST_D   = 16;
  parameter int HIST_W   = (HIST_D <= 1) ? 1 : $clog2(HIST_D);
  parameter int N_LANE   = 16;
  parameter int LANE_W   = (N_LANE <= 1) ? 1 : $clog2(N_LANE);
 
  // IMPLEMENTATION POLICY. PCIe defines no Recovery reason encoding; if the
  // hardware cannot distinguish, the answer is UNKNOWN, never a guess (§7).
  typedef enum logic [2:0] {
    RR_UNKNOWN      = 3'd0,
    RR_DIRECTED     = 3'd1,   // software or an explicit retrain request
    RR_SPEED_CHANGE = 3'd2,   // a speed phase was requested (18.5 §6)
    RR_RELIABILITY  = 3'd3,   // an error/reliability indication from below
    RR_PHY_INDICATED= 3'd4    // the PHY asked for it
  } recovery_reason_e;
 
  typedef struct packed {
    logic [3:0]        from_state;
    logic [3:0]        to_state;
    logic [31:0]       residency;     // cycles spent in from_state
    logic [N_LANE-1:0] lane_mask;     // lanes locked at the transition
    logic [5:0]        width;
    logic [2:0]        speed;
    recovery_reason_e  reason;
    logic [31:0]       cycle;
  } train_event_t;
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import train_dbg_pkg::*;
 
// SYNTHESIZABLE. ONE state-entry pulse, shared by every instrument below.
// Computing entry separately in each block is how they disagree about how
// many transitions occurred (§14, mutation 15).
module ltssm_entry_pulse (
  input  logic clk,
  input  logic rst_n,
  input  logic [3:0] state,
 
  output logic       state_enter,
  output logic [3:0] prev_state,
  output logic [31:0] transition_count
);
  logic [3:0] prev_q; logic [31:0] cnt_q;
 
  assign state_enter      = (state != prev_q);
  assign prev_state       = prev_q;
  assign transition_count = cnt_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin prev_q <= 4'd0; cnt_q <= '0; end
    else begin
      prev_q <= state;
      if (state_enter && !(&cnt_q)) cnt_q <= cnt_q + 32'd1;
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import train_dbg_pkg::*;
 
// SYNTHESIZABLE. State residency and entry counts (§2).
// Distinguishes STUCK from LOOPING -- §13 measured 20,000 cycles in Detect
// with 0 transitions versus 19,598 in Polling with 802 loop transitions.
module ltssm_residency #(parameter int N_STATE = 16) (
  input  logic clk,
  input  logic rst_n,
  input  logic [3:0] state,
  input  logic       state_enter,
  input  logic       dbg_clear,
 
  output logic [31:0] residency [N_STATE],
  output logic [31:0] entries   [N_STATE],
  output logic [31:0] current_residency,
  output logic [3:0]  longest_state,
  output logic        saturated
);
  logic [31:0] res_q [N_STATE], ent_q [N_STATE], cur_q;
  logic sat_q;
 
  always_comb begin
    for (int i=0;i<N_STATE;i++) begin residency[i]=res_q[i]; entries[i]=ent_q[i]; end
    longest_state = 4'd0;
    for (int i=1;i<N_STATE;i++) if (res_q[i] > res_q[longest_state]) longest_state = 4'(i);
  end
  assign current_residency = cur_q;
  assign saturated = sat_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin
      cur_q<='0; sat_q<=1'b0;
      for (int i=0;i<N_STATE;i++) begin res_q[i]<='0; ent_q[i]<='0; end
    end else begin
      // Range-safe: an out-of-range encoding accrues to nothing rather
      // than aliasing into state 0 (23.6 pattern 11).
      if (state < 4'(N_STATE)) begin
        if (state_enter) begin
          cur_q <= '0;
          if (!(&ent_q[state])) ent_q[state] <= ent_q[state] + 32'd1;
        end else cur_q <= cur_q + 32'd1;
        if (&res_q[state]) sat_q <= 1'b1;
        else res_q[state] <= res_q[state] + 32'd1;
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import train_dbg_pkg::*;
 
// SYNTHESIZABLE. Residency watchdog -- a DEBUG HEURISTIC (§1).
// It reports "this state has been occupied longer than the debug budget",
// which is a fact about the design. It is NOT a PCIe timeout, and calling
// it one is §14's mutation 21.
module ltssm_stuck_watchdog #(parameter int unsigned STUCK_BUDGET = 1_000_000) (
  input  logic clk,
  input  logic rst_n,
  input  logic [3:0]  state,
  input  logic        state_enter,
  input  logic [31:0] current_residency,
  input  logic        dbg_clear,
 
  output logic        state_stuck,        // sticky
  output logic [3:0]  stuck_state,
  output logic [31:0] stuck_residency
);
  logic st_q; logic [3:0] which_q; logic [31:0] res_q;
 
  assign state_stuck = st_q; assign stuck_state = which_q;
  assign stuck_residency = res_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin st_q<=1'b0; which_q<='0; res_q<='0; end
    // The watchdog must NOT expire after the state has been left (§12).
    else if (state_enter) begin /* residency restarts; nothing latches */ end
    else if (!st_q && (current_residency >= 32'(STUCK_BUDGET))) begin
      st_q <= 1'b1; which_q <= state; res_q <= current_residency;
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import train_dbg_pkg::*;
 
// SYNTHESIZABLE. Transition history with a lane snapshot (§5, §6).
// A COMPACT set of transitions beats an N-squared matrix: what debugging
// needs is the sequence, not every possible pair.
module ltssm_history (
  input  logic clk,
  input  logic rst_n,
  input  logic        state_enter,
  input  logic [3:0]  state,
  input  logic [3:0]  prev_state,
  input  logic [31:0] residency_of_prev,
  input  logic [N_LANE-1:0] lane_lock_mask,
  input  logic [5:0]  negotiated_width,
  input  logic [2:0]  negotiated_speed,
  input  recovery_reason_e reason,
  input  logic [31:0] cycle,
  input  logic        freeze,
  input  logic        dbg_clear,
 
  output train_event_t history [HIST_D],
  output logic [HIST_W-1:0] wr_ptr,
  output logic              wrapped,
  output logic [31:0]       dropped_while_frozen
);
  train_event_t h_q [HIST_D];
  logic [HIST_W-1:0] wr_q; logic wrap_q; logic [31:0] drop_q;
 
  always_comb for (int i=0;i<HIST_D;i++) history[i]=h_q[i];
  assign wr_ptr=wr_q; assign wrapped=wrap_q; assign dropped_while_frozen=drop_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin
      wr_q<='0; wrap_q<=1'b0; drop_q<='0;
      for (int i=0;i<HIST_D;i++) h_q[i]<='0;
    end else if (state_enter) begin
      if (freeze) begin
        if (!(&drop_q)) drop_q <= drop_q + 32'd1;
      end else begin
        // The lane mask is captured AT the transition -- reading it later
        // gets the next state's value (§14, mutation 12).
        h_q[wr_q] <= '{from_state: prev_state, to_state: state,
                       residency:  residency_of_prev,
                       lane_mask:  lane_lock_mask,
                       width:      negotiated_width,
                       speed:      negotiated_speed,
                       reason:     reason,
                       cycle:      cycle};
        if (HIST_D == 1) wrap_q <= 1'b1;
        else if (wr_q == HIST_W'(HIST_D-1)) begin wr_q<='0; wrap_q<=1'b1; end
        else wr_q <= wr_q + HIST_W'(1);
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import train_dbg_pkg::*;
 
// SYNTHESIZABLE. Recovery reason latch (§7).
// Captured ON ENTRY, from the conditions that caused it -- sampling after
// entry reads the conditions Recovery itself created (§14, mutation 11).
// If the hardware cannot distinguish, it stores UNKNOWN rather than
// fabricating certainty.
module recovery_reason_latch (
  input  logic clk,
  input  logic rst_n,
  input  logic       entering_recovery,      // the entry pulse for ST_RECOVERY
  input  logic       req_directed,
  input  logic       req_speed_change,
  input  logic       ind_reliability,
  input  logic       ind_phy,
  input  logic       dbg_clear,
 
  output recovery_reason_e reason,
  output logic [31:0]      entries_total,
  output logic [31:0]      entries_by_reason [5],
  output logic             multiple_causes    // sticky: more than one was true
);
  recovery_reason_e r_q; logic [31:0] tot_q, by_q [5]; logic mc_q;
 
  assign reason = r_q; assign entries_total = tot_q; assign multiple_causes = mc_q;
  always_comb for (int i=0;i<5;i++) entries_by_reason[i]=by_q[i];
 
  // A DECLARED priority. When several indications are true at once the
  // record keeps the ranked one AND sets multiple_causes, so the ambiguity
  // is visible rather than resolved silently.
  recovery_reason_e pick;
  always_comb begin
    if      (req_directed)     pick = RR_DIRECTED;
    else if (req_speed_change) pick = RR_SPEED_CHANGE;
    else if (ind_reliability)  pick = RR_RELIABILITY;
    else if (ind_phy)          pick = RR_PHY_INDICATED;
    else                       pick = RR_UNKNOWN;
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin
      r_q<=RR_UNKNOWN; tot_q<='0; mc_q<=1'b0;
      for (int i=0;i<5;i++) by_q[i]<='0;
    end else if (entering_recovery) begin
      r_q <= pick;
      if (!(&tot_q)) tot_q <= tot_q + 32'd1;
      if (!(&by_q[pick])) by_q[pick] <= by_q[pick] + 32'd1;
      if ($countones({req_directed, req_speed_change, ind_reliability, ind_phy}) > 1)
        mc_q <= 1'b1;
    end
  end
endmodule

Classification: all six synthesizable debug hooks.

Failure — seven. Each block computing its own entry pulse. A watchdog that expires after the state was left. A lane mask sampled after the transition. A Recovery reason sampled after entry. A fabricated reason instead of UNKNOWN. Residency counters that wrap. And a debug budget described as a PCIe timer.

12. Same-Cycle Audit and Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---- STATE OBSERVATION (§2) -------------------------------------
// P1: the state encoding is always one of the defined values (18.1 §10).
property p_state_encoding_valid;
  @(posedge clk) disable iff (!rst_n)
    (state inside {ST_DETECT, ST_POLLING, ST_CONFIGURATION, ST_L0,
                   ST_RECOVERY, ST_L0S, ST_L1, ST_DISABLED, ST_HOT_RESET});
endproperty
 
// P2: the entry pulse is exactly one cycle.
property p_entry_pulse_one_cycle;
  @(posedge clk) disable iff (!rst_n)
    state_enter |=> (!state_enter || (state != $past(state)));
endproperty
 
// P3: every instrument uses the SAME entry pulse -- computing it locally
// is how two debug blocks disagree about the transition count (§11).
property p_single_entry_source;
  @(posedge clk) disable iff (!rst_n)
    (transition_count != $past(transition_count)) |-> $past(state_enter);
endproperty
 
// P4: the transition count advances once per transition.
property p_transition_count_once;
  @(posedge clk) disable iff (!rst_n)
    state_enter |=> (transition_count == $past(transition_count) + 32'd1);
endproperty
 
// ---- RESIDENCY (§2) ---------------------------------------------
// P5: residency accrues to the state actually occupied.
property p_residency_correct_state;
  @(posedge clk) disable iff (!rst_n)
    (!state_enter && (state < 4'(N_STATE)))
      |=> (residency[$past(state)] == $past(residency[state]) + 32'd1);
endproperty
 
// P6: the current-residency counter restarts on entry.
property p_current_residency_restarts;
  @(posedge clk) disable iff (!rst_n)
    state_enter |=> (current_residency == '0);
endproperty
 
// P7: entry counters increment per entry, not per resident cycle.
property p_entries_per_entry;
  @(posedge clk) disable iff (!rst_n)
    (entries[ST_POLLING] != $past(entries[ST_POLLING])) |-> $past(state_enter);
endproperty
 
// ---- WATCHDOG (§11) -- a DEBUG HEURISTIC, not a PCIe timer ------
// P8: the watchdog does not expire after the state has been left.
property p_watchdog_not_after_exit;
  @(posedge clk) disable iff (!rst_n)
    state_enter |=> !$rose(state_stuck);
endproperty
 
// P9: when it fires, it names the state that was actually occupied.
property p_watchdog_names_occupied_state;
  @(posedge clk) disable iff (!rst_n)
    $rose(state_stuck) |-> (stuck_state == $past(state));
endproperty
 
// P10: the stuck flag is sticky until an explicit clear.
property p_stuck_sticky;
  @(posedge clk) disable iff (!rst_n)
    (state_stuck && !dbg_clear) |=> state_stuck;
endproperty
 
// ---- HISTORY (§5, §6) -------------------------------------------
// P11: a history entry records the transition's from/to exactly.
property p_history_from_to_exact;
  @(posedge clk) disable iff (!rst_n)
    (state_enter && !freeze) |=> (history[$past(wr_ptr)].from_state == $past(prev_state)
                               && history[$past(wr_ptr)].to_state   == $past(state));
endproperty
 
// P12: a frozen history is immutable and drops are counted.
property p_frozen_history_immutable;
  @(posedge clk) disable iff (!rst_n)
    freeze |=> $stable(history[0]);
endproperty
 
// P13: events arriving while frozen are counted, not silently discarded.
property p_frozen_drops_counted;
  @(posedge clk) disable iff (!rst_n)
    (freeze && state_enter) |=> (dropped_while_frozen == $past(dropped_while_frozen)+1);
endproperty
 
// P14: the write pointer is in range -- HIST_D = 1 included.
property p_hist_ptr_in_range;
  @(posedge clk) disable iff (!rst_n) (wr_ptr < HIST_W'(HIST_D));
endproperty
 
// ---- RECOVERY REASON (§7) ---------------------------------------
// P15: the reason is captured ON ENTRY, from the pre-entry conditions --
// sampling later reads what Recovery itself produced.
property p_reason_captured_at_entry;
  @(posedge clk) disable iff (!rst_n)
    entering_recovery |=> (reason == $past(pick));
endproperty
 
// P16: an indistinguishable cause records UNKNOWN, never a guess (§7).
property p_unknown_not_fabricated;
  @(posedge clk) disable iff (!rst_n)
    (entering_recovery && !req_directed && !req_speed_change
      && !ind_reliability && !ind_phy) |=> (reason == RR_UNKNOWN);
endproperty
 
// P17: simultaneous causes are REPORTED, not silently ranked away.
property p_multiple_causes_flagged;
  @(posedge clk) disable iff (!rst_n)
    (entering_recovery && ($countones({req_directed, req_speed_change,
                                       ind_reliability, ind_phy}) > 1))
      |=> multiple_causes;
endproperty
 
// P18: per-reason counts sum to the total.
property p_reason_counts_conserve;
  @(posedge clk) disable iff (!rst_n)
    (entries_by_reason[0] + entries_by_reason[1] + entries_by_reason[2]
      + entries_by_reason[3] + entries_by_reason[4] == entries_total);
endproperty
 
// ---- LANE AND WIDTH ---------------------------------------------
// P19: the lane mask is captured AT the transition, not read afterwards.
property p_lane_mask_at_transition;
  @(posedge clk) disable iff (!rst_n)
    (state_enter && !freeze) |=> (history[$past(wr_ptr)].lane_mask == $past(lane_lock_mask));
endproperty
 
// P20: the negotiated width never exceeds the lanes actually locked.
property p_width_le_locked_lanes;
  @(posedge clk) disable iff (!rst_n)
    (state == ST_L0) |-> (negotiated_width <= 6'($countones(lane_lock_mask)));
endproperty
 
// P21: L0 is reported only when the state is L0 -- a separate "link up"
// flag that disagrees with the state is §14's mutation 25.
property p_l0_flag_matches_state;
  @(posedge clk) disable iff (!rst_n)
    (link_operational == (state == ST_L0));
endproperty
 
// ---- HYGIENE ----------------------------------------------------
// P22: reset clears history, residency and the stuck flag.
property p_reset_clears_debug;
  @(posedge clk)
    (!rst_n) |=> (!state_stuck && (transition_count == '0));
endproperty
 
// P23: counters saturate rather than wrap.
property p_counters_saturate;
  @(posedge clk) disable iff (!rst_n)
    (transition_count == 32'hFFFF_FFFF) |=> (transition_count == 32'hFFFF_FFFF);
endproperty
 
// P24: no debug output drives the state machine.
property p_debug_non_functional;
  @(posedge clk) disable iff (!rst_n)
    $stable(state) or !$stable({transition_count, current_residency});
endproperty
 
// P25: traffic is gated on the operational state (18.6, and 24.2 §12's P42).
property p_no_traffic_unless_operational;
  @(posedge clk) disable iff (!rst_n)
    (state != ST_L0) |-> !tx_valid;
endproperty
 
// P26: EVIDENCE -- the machine actually attempted to train.
property c_left_detect;
  @(posedge clk) disable iff (!rst_n) (state_enter && ($past(state) == ST_DETECT));
endproperty

Twenty-six properties. P3 is the one that keeps the instruments coherent — one entry pulse, one transition count, and every block agreeing about what happened. P16 and P17 are unusual and worth stealing: they assert that the design admits uncertainty rather than manufacturing a reason, which is what makes §7's reason latch trustworthy.

13. Measured Behaviour

14. Verification — Mutations

#MutationSymptomCaught by
1The entry pulse is held for two cyclesevery transition counted twiceP2, P4
2Each debug block computes its own entry pulsethe instruments disagree about the historyP3
3Residency accrues after the state was leftthe stuck state named wronglyP5
4Current residency not restarted on entrya brief state reported as stuckP6
5Entry counter increments every resident cycleone entry counted thousands of timesP7
6The watchdog expires after the state has been exiteda healthy transition reported as stuckP8
7The watchdog names the current state, not the occupied onethe wrong state blamedP9
8The stuck flag is a pulse, not stickythe evidence is gone before it is readP10
9History records the destination twicefrom_state unusableP11
10The lane mask read after the transitionthe next state's mask recorded (§11)P19
11The Recovery reason sampled after entrythe conditions Recovery created, not its causeP15
12A reason fabricated when the cause is indistinguishablethe investigation ends at the wrong place (§7)P16
13Simultaneous causes silently rankedambiguity hiddenP17
14Per-reason counts do not sum to the totalentries lost or double-countedP18
15Freeze one cycle latethe transition that failed is overwrittenP12
16Frozen history keeps writingthe captured window is destroyedP12
17Capable width mistaken for negotiated widthcorrect hardware reported as broken (§6)P20
18Negotiated speed confused with target speeda successful lower-speed link called a failurereview
19An unsupported state encoding defaults to Detectan illegal state hidden as a legal oneP1
20The state width truncatedtwo states aliasP1
21A debug budget described as a PCIe timeoutan unobserved mechanism claimed (§1)review
22A vendor's equalization visibility described as universala claim about all devices from one (§8)review
23Polling failure diagnosed as a BAR problemwrong layer entirely (25.2 §3)review
24L0 assumed because Data Link traffic was seena separate flag disagreeing with the stateP21
25A separate "link up" flag not derived from the statetwo sources of truth about one factP21
26Traffic issued while not in L0packets into a training linkP25
27A Recovery loop treated as one root causefour substates are four problems (18.5 §5)review
28A legal retrain reported as a loopprogress ignored (§7)review
29Equalization assumed required at a generation that does not use ita phase blamed that does not exist (§8)review
30Residency counters wrapa stuck state reports a small numberP23
31History ring assumed power-of-two; depth 1 breaks the pointerout-of-range writeP14
32A debug register gates the training FSMthe measured link is not the shipping oneP24
33An exact ordered-set count published from memorya false diagnosis stated with confidence (§5)review
34"No device connected" maintained after Polling was reachedpresence was already proven (§3)review

Two counterexamples worth stating explicitly.

Mutation 11 is the one that makes the Recovery reason register actively misleading. Sampling the cause indications after entering Recovery reads the conditions Recovery itself produced — its own lock loss, its own speed activity. The register then reports the same reason for every entry, and it is the reason Recovery generates rather than the one that caused it. §11 samples on the entry pulse from pre-entry conditions, and P15 pins it. A register that always reports the same cause is worse than no register, because it terminates the investigation with false confidence.

Mutation 17 is the most common bug report filed against working hardware. A device reports x8 capability; the link negotiated x4 because of a lane issue or a slot; someone reads the capability register and reports a training failure. The link trained successfully (17.3) — the finding is the width, and it belongs to a performance investigation (22.6 §6). P20 asserts the negotiated width never exceeds the lanes actually locked, which turns the question into a measurement.

15. Debugging

Symptom — never leaves Detect. Presence, power or reset (§3). §13 measured this as 0 transitions in 20,000 cycles — the machine is not trying and failing, it is waiting. This is the one case where physical checks are a reasonable early experiment, and 17.5 owns the electrical side.

Symptom — Detect and Polling alternate. Presence is established and the exchange is not completing (§5). Read the lane-lock mask: no lanes locking points at the whole link; one lane failing while others succeed points at that lane, its connector or its channel.

Symptom — resident in Polling with no transitions. Different from the previous case. Stuck rather than looping — the state is not even attempting to fall back. Compare entries[POLLING] against residency[POLLING]: high residency with a count of 1 is stuck; high residency with hundreds of entries is looping.

Symptom — reaches Configuration and returns to Polling. Presence and exchange both proved (§4). The negotiation is not converging (§6). Read the negotiated width across attempts: oscillating width means the two ends disagree; a consistently narrower width than expected means some lanes never participated — and the lane mask in the history says which.

Symptom — reaches L0 at a lower speed than expected. The link trained. This is §8's boundary: did the failure occur during the speed phase? A link that is reliable at the lower generation and fails at the higher one localizes to equalization, and 17.4 owns what to look at.

Symptom — x8-capable device negotiates x1. Not a training failure (§6, mutation 17). Read the negotiated width and the lane mask. One lane locking out of eight is a lane-specific problem; all eight locking with x1 negotiated is a negotiation problem.

Symptom — reaches L0 then immediately enters Recovery, repeatedly. Read the reason latch and the substate (§7). §13's recovery-loop case was resident in L0 for 93% of the run — it is operational and retraining. Correlate with replay and error counters: reliability pressure from the Data Link layer is a different cause from a speed change, and 18.5 §5's four substates are four different problems.

Symptom — works warm, fails cold. Something is different at power-on that a warm restart does not exercise — reset sequencing, or a PHY calibration that persists. The history's first few entries after each boot are the comparison, and §11's freeze on the stuck condition preserves them.

Symptom — one platform works, another does not. Compare negotiated generation and width on both (17.3), then the transition histories. Identical negotiation with different behaviour points at the platform; different negotiation points at the link itself.

Symptom — the debug counters disagree with each other. Two blocks computing their own entry pulse (mutation 2). §11 shares one, and P3 asserts it — without that, the residency table and the history describe different transition sequences and neither can be trusted.

16. Misconceptions

"It won't train" names a fault. It names a symptom; the state and its residency name the fault (§2).

"No device connected." Not once Polling has been reached — presence is proven (§3).

"Reaching L0 at x1 is a training failure." It trained; the width is a separate finding (§6).

"Capable x8 means the link is x8." Read the negotiated width (17.3).

"Repeated Recovery is a bug." Not if it makes progress (§7).

"A Recovery loop has one root cause." Four substates, four problems (18.5 §5).

"The reason register says why." Only if it was sampled at entry, and only if it admits UNKNOWN (§7, mutation 11).

"The stuck watchdog firing is a protocol violation." It is a debug heuristic (§1, §11).

"Residency tells you which condition is missing." It tells you which state — §13's rows 3 and 4 are identical.

"Equalization is always involved in a speed change." Generation-dependent, and vendor visibility varies (§8).

"Data Link traffic means L0." Derive the operational flag from the state, not the reverse (mutation 24).

"Check the cable first." Only if the machine never left Detect (§3).

17. Understanding Check

Q1. The link is not in L0. What is the first thing you read, and what does it eliminate? The LTSSM state and its residency (§2). Reaching Polling eliminates presence; reaching Configuration additionally eliminates the exchange (§4). §13 measured the no-detect case as 0 transitions in 20,000 cycles versus hundreds or thousands for every other fault — so one read separates "waiting for a partner" from "failing to agree with one."

Q2. Two links both sit in Polling. One has 1 entry, the other has 802. Same fault? No — one is stuck and one is looping (§15). High residency with a single entry means the state is not even attempting to fall back; high residency with hundreds of entries means it repeatedly tries and returns. Different causes and different experiments, and the distinction costs one counter (§11).

Q3. §13 shows "no training exchange" and "no lane lock" producing identical signatures. What does that mean for this chapter's limits? Residency identifies the state, not the missing condition — both faults gate the same Polling exit, so every counter this chapter has reports the same values. Separating them requires seeing which exit guards were true, which is 25.4's guard vector. This chapter's honest boundary is naming the state and the class; the next one names the condition.

Q4. A link reaches L0 and enters Recovery 916 times. Is it broken? Not necessarily (§7). §13's case was resident in L0 for 93% of the run — it trained and it is retraining. The test is progress: did a speed phase complete, did more lanes lock, did packets flow? Entering Recovery is a mechanism, not a fault (18.5 §4); entering it repeatedly without progress is.

Q5. Why does §11's reason latch store UNKNOWN instead of picking the most likely cause? Because a fabricated reason terminates the investigation at the wrong place (§7, mutation 12). If the hardware cannot distinguish a speed change from a reliability event, a register that asserts one is confidently wrong — and a debugger who trusts it stops looking. P16 asserts UNKNOWN when no indication is present, and P17 flags the case where several are.

Q6. A device advertises x8 and the link runs at x1. Where does this belong? Not in this chapter (§6). The link trained; it reached L0. The finding is the negotiated width, and the next questions are which lanes locked (§11's mask) and what that costs in throughput (22.6 §6). Reading the capability register and reporting a training failure is mutation 17 — the most common bug report filed against hardware that is working.

18. What's Next

This chapter localizes to a state. Chapter 25.4 LTSSM Issues localizes to a contract inside it.

§13's identical rows are the handoff. When two faults gate the same edge, residency cannot separate them — you need to see which exit guards were true, which one was selected, and why. That is a question about the state machine's internal contracts: entry actions, resident invariants, exit guards, timers, and the priority between simultaneous exits.

And several threads from this chapter continue there. §7's Recovery loops become a question of whether a legal retrain can be distinguished from a real loop by evidence rather than by count. §11's reason latch becomes a full transition-reason capture with a guard vector. §14's mutation 11 — sampling a cause after the event it caused — becomes an entire section, because it is the archetype of state-machine debug error.

Then the rest of Module 25 returns to the layers above. 25.5 takes the BAR stage, 25.6 DMA, 25.7 completion timeouts, 25.8 credit deadlocks, and 25.9 the analyzer that 25.1 §3 argued should never be the first tool.