Skip to content

PCIe · Module 18

LTSSM Overview — What the Link Is Currently Trying to Do

Detection, training, normal traffic, recovery, power states, disable and reset are behaviours. One hardware state machine decides which one is happening — and therefore which behaviours are legal right now.

Module 17 built the Physical Layer's machinery and left one question open in every chapter.

Chapter 17.1's distributor needed to know which lanes are active. Chapter 17.2's aligner needed a partner transmitting recognisable evidence. Chapter 17.3 produced a negotiated configuration but declined to say who ran the negotiation. Chapter 17.4 noted that equalization is coordinated during training and recovery, and pointed here.

Every one of those is a behaviour that is legal at some times and not at others — and something must decide which.

That something is the LTSSM: the Physical Layer's master state machine. It does not carry packets, route transactions or process credits. It decides what the Link is currently trying to accomplish, and therefore which signalling and control behaviours are permitted right now.

What is it, why does PCIe need one, and how should RTL expose that control architecture without collapsing the entire specification into one enormous case statement?

1. Scope, Sources, and What Belongs to the Child Chapters

2. State Is Not Packet Type

The first confusion to remove, because the vocabulary invites it.

QuestionAnswered by
What is the Link doing?LTSSM state
What information is this packet carrying?TLP / DLLP type

A Memory Write is not a state. Polling is not a packet.

3. It Is Hardware

The LTSSM reacts at Link timescales to events no software loop could service.

Its inputs are physical and immediate: receiver detection, PHY lock and alignment status, received training patterns, timers, errors, power-management requests, and reset.

Software's role is real but different. It sets policy and configuration — whether ASPM is enabled, target link speed, whether the Link may be retrained — through configuration registers, and it observes the result. It does not step the machine through states.

4. Terminology Collisions

PCIe reuses several words across completely unrelated axes, and this is where they collide.

5. The State Families

One sentence each. The chapters own the rest.

StateWhat it is forOwned by
Detectdetermines whether a receiver is present and begins Link establishment18.2
Pollingestablishes the low-level synchronization and training progress that configuration requires18.3
Configurationturns usable trained lanes into a coherent negotiated Link configuration18.4
L0normal, fully-active Link operation — the state in which traffic flows18.6
Recoveryre-establishes or changes a Link that cannot simply remain in normal operation18.5
L0sa shallow low-power state with low exit latency18.7
L1a deeper low-power state18.8
Disabledthe Link is deliberately not participating in normal operation18.9
Hot Resetin-band reset signalling and its propagation18.10

Read the table as a map, not as a specification. Each row is a purpose, and every entry condition, exit condition, substate, timeout and transition criterion belongs to the chapter named beside it.

6. The Shape

An overview of the major PCIe LTSSM states. Detect leads to Polling, Polling to Configuration, and Configuration to L0, the normal operating state. From L0 the link can enter the low-power states L0s and L1 and return, and can enter Recovery and return. Recovery is also reachable from the low-power states. Separate paths lead to Disabled and Hot Reset, which return toward Detect.DetectPollingConfigurationL0RecoveryL0sL1DisabledHotResetpartner presentpartner presentsynchronizedsynchronizedconfiguredconfiguredretrainretrainrestoredrestoredidleidleexitexitlow powerlow powerexit via retrainexit via retraindisabledisablerestartrestartrestartrestartin-band resetin-band reset
Figure 1 — overview topology of the major LTSSM states. Substates, entry and exit criteria, and timeout conditions are omitted: they belong to chapters 18.2 through 18.10. What this figure claims is the family structure — a bring-up path from Detect to L0, L0 as the hub from which power states and Recovery are reached, and Recovery as the route back to operation. It is deliberately not a complete transition map.

7. What the Machine Consumes and Produces

The orchestration view, and it is the reason a single controller exists at all.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
INPUTS                              OUTPUTS
receiver detection      ─┐      ┌─  PHY mode and power state
PHY lock / alignment    ─┤      ├─  training pattern generation
received ordered sets   ─┤ LTSSM├─  lane enable and configuration
timers                  ─┤      ├─  NORMAL TRAFFIC ENABLE
errors                  ─┤      ├─  recovery request
power-management req    ─┤      ├─  power-state control
reset / hot reset       ─┘      └─  link status, for software

The consequence that reaches all the way up to Chapter 16.5's transmit path.

Normal TLP and DLLP traffic flows in normal operation. In Detect, Polling, Configuration, Recovery, the low-power states, Disabled and Hot Reset, the Link is doing something else — and a packet handed to the PHY during those states has nowhere to go.

So there must be a gate, and Chapter 17.3 §12 already built its ownership discipline: gate both valid and ready, or a packet is destroyed at the boundary.

9. Why This Chapter Does Not Build an LTSSM

10. RTL — State Encoding and Observability

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Normalized LTSSM state representation and observability.
//
// ============================================================
// THIS IS AN INTERNAL DEBUG ENUM, NOT A PCIe WIRE ENCODING.
//
// The values are this design's; PCIe does not define an encoding for
// LTSSM state on any interface. The STATE NAMES are the canonical major
// families (section 5); their entry/exit criteria belong to chapters
// 18.2-18.10 and appear nowhere in this module.
// ============================================================
package ltssm_pkg;
 
  typedef enum logic [3:0] {
    ST_DETECT        = 4'd0,
    ST_POLLING       = 4'd1,
    ST_CONFIGURATION = 4'd2,
    ST_L0            = 4'd3,
    ST_RECOVERY      = 4'd4,
    ST_L0S           = 4'd5,
    ST_L1            = 4'd6,
    ST_DISABLED      = 4'd7,
    ST_HOT_RESET     = 4'd8
  } ltssm_state_e;
 
  localparam int N_STATES = 9;
 
  // Why a transition happened, for the history register (section 11).
  // ILLUSTRATIVE categories -- not a PCIe enumeration.
  typedef enum logic [2:0] {
    RSN_NONE      = 3'd0,
    RSN_PROGRESS  = 3'd1,   // normal forward progress
    RSN_TIMEOUT   = 3'd2,
    RSN_ERROR     = 3'd3,
    RSN_PM        = 3'd4,   // power-management request
    RSN_RESET     = 3'd5,
    RSN_SOFTWARE  = 3'd6    // configuration-driven
  } ltssm_reason_e;
 
  function automatic bit state_legal(input logic [3:0] s);
    return (s < 4'(N_STATES));
  endfunction
 
  // NORMAL TLP/DLLP TRAFFIC IS LEGAL ONLY IN NORMAL OPERATION.
  // Scoped as this teaching model's contract: the exact set of states in
  // which traffic may flow is chapter 18.6's, and a design must take it
  // from there rather than from this one-line function.
  function automatic bit traffic_legal(input ltssm_state_e s);
    return (s == ST_L0);
  endfunction
 
endpackage

Classification: synthesizable (package) — and the enum is a debug representation, not a wire format.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import ltssm_pkg::*;
 
// SYNTHESIZABLE. State observability: what the machine is doing, what it
// was doing, and how it got here.
// This is the register set an engineer reads FIRST when a Link fails
// (section 16). It observes; it never drives.
module ltssm_observe #(
  parameter int CNT_W = 16
) (
  input  logic clk,
  input  logic rst_n,
 
  // ---- From the state machine (which this module does NOT implement) ----
  input  ltssm_state_e  state,
  input  ltssm_reason_e reason,
 
  // ---- Debug-visible history ---------------------------------------------
  output ltssm_state_e  cur_state,
  output ltssm_state_e  prev_state,
  output ltssm_reason_e last_reason,
  output logic          state_changed,        // one cycle per transition
  output logic [CNT_W-1:0] transition_count,
  output logic [CNT_W-1:0] recovery_count,     // entries into Recovery
  output logic          illegal_state
);
 
  generate
    if (CNT_W < 1) $error("CNT_W must be at least 1");
  endgenerate
 
  ltssm_state_e  state_q, prev_q;
  ltssm_reason_e reason_q;
  logic [CNT_W-1:0] tcnt_q, rcnt_q;
  logic          ill_q;
 
  assign cur_state        = state_q;
  assign prev_state       = prev_q;
  assign last_reason      = reason_q;
  assign transition_count = tcnt_q;
  assign recovery_count   = rcnt_q;
  assign illegal_state    = ill_q;
 
  // THE TRANSITION EVENT. Compared against the REGISTERED state, so it is
  // exactly one cycle wide per change (P3) -- a comparison against a
  // combinational next-state would glitch.
  wire changed = (state != state_q);
  assign state_changed = changed;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q  <= ST_DETECT;
      prev_q   <= ST_DETECT;
      reason_q <= RSN_NONE;
      tcnt_q   <= '0; rcnt_q <= '0; ill_q <= 1'b0;
    end else begin
      state_q <= state;
 
      if (changed) begin
        // PREVIOUS STATE UPDATES ON THE TRANSITION, not before it. An
        // early update makes prev_state equal cur_state and destroys the
        // one piece of history that says where the machine came from
        // (section 18, mutation 2).
        prev_q   <= state_q;
        reason_q <= reason;
        // SATURATING. A wrapped count reads low and looks healthy, which
        // is the reading least distinguishable from a stable Link.
        if (tcnt_q != {CNT_W{1'b1}}) tcnt_q <= tcnt_q + CNT_W'(1);
 
        // RECOVERY ENTRIES ARE COUNTED SEPARATELY, because a Link that
        // keeps re-entering Recovery is the single most useful physical-
        // layer signal available to software (section 16).
        if ((state == ST_RECOVERY) && (rcnt_q != {CNT_W{1'b1}}))
          rcnt_q <= rcnt_q + CNT_W'(1);
      end
 
      if (!state_legal(state)) ill_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. Registered state, previous state, last reason, and two saturating counters.

recovery_count earns its own counter rather than being inferred from the transition count. A Link that enters Recovery repeatedly is reporting a physical problem (§16), and that signal is far more valuable when it is a single number software can read than when it must be reconstructed from a history buffer.

Failure — four. Updating prev_state before the transition makes it equal cur_state and destroys the history. Comparing against a combinational next-state produces a glitching state_changed. A wrapping counter reads low and looks healthy. And counting Recovery entries while in Recovery rather than on entry inflates the number by its dwell time.

11. RTL — State Dwell Timer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. A reusable dwell timer primitive.
// THE LTSSM USES TIMING CONDITIONS EXTENSIVELY, but NO PCIe TIMEOUT
// CONSTANT APPEARS HERE -- the normative values belong to the chapters
// that own each state (section 1). This is the mechanism only.
module ltssm_timer #(
  parameter int CNT_W = 24
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic             enable,
  input  logic             restart,          // e.g. a state change
  input  logic [CNT_W-1:0] limit,
 
  output logic [CNT_W-1:0] count,
  output logic             expired
);
 
  generate
    if (CNT_W < 1) $error("CNT_W must be at least 1");
  endgenerate
 
  logic [CNT_W-1:0] cnt_q;
  assign count = cnt_q;
 
  // ==================================================================
  // limit == 0 CONTRACT, DECLARED: the timer is DISABLED, never
  // instantly expired. A zero limit almost always means "not configured
  // yet", and treating it as immediate expiry fires every timeout in the
  // design during reset (section 18, mutation 6).
  // ==================================================================
  assign expired = enable && (limit != '0) && (cnt_q >= limit);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cnt_q <= '0;
    end else if (restart) begin
      // RESTART HAS PRIORITY OVER COUNTING, declared. A state change must
      // reset the dwell measurement even if the timer would have ticked
      // in the same cycle -- otherwise the new state inherits part of the
      // old state's elapsed time.
      cnt_q <= '0;
    end else if (enable && !expired) begin
      // SATURATING at the limit. A wrapping dwell timer would silently
      // re-arm and fire again, turning one timeout into a periodic one.
      cnt_q <= cnt_q + CNT_W'(1);
    end
  end
 
endmodule

Classification: synthesizable.

Three contracts declared rather than left implicit: limit == 0 disables, restart beats counting, and the counter saturates at the limit rather than wrapping.

Each has an opposite that is worse. A zero limit read as immediate expiry fires every timeout during reset. Counting winning over restart lets a new state inherit the previous state's elapsed time — so a short state can appear to time out instantly. And wrapping turns a single timeout into a repeating one, which presents as a Link that cycles on a period nobody configured.

12. RTL — Entry Pulses

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import ltssm_pkg::*;
 
// SYNTHESIZABLE. One-cycle entry pulses, one per state.
// A small block with an outsized architectural effect (see below).
module ltssm_entry_pulse (
  input  logic         clk,
  input  logic         rst_n,
  input  ltssm_state_e state,
 
  output logic [N_STATES-1:0] enter,   // one-hot, one cycle per entry
  output logic                exit_any
);
 
  ltssm_state_e state_q;
  wire changed = (state != state_q);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) state_q <= ST_DETECT;
    else        state_q <= state;
  end
 
  always_comb begin
    enter = '0;
    // EXACTLY ONE BIT, EXACTLY ONE CYCLE. One-hot by construction, because
    // it is indexed by the new state rather than assembled from
    // per-state comparisons that could disagree.
    if (changed && state_legal(state)) enter[state] = 1'b1;
  end
 
  assign exit_any = changed;
 
endmodule

Classification: synthesizable (combinational plus one register).

13. RTL — Traffic Gate and Event Aggregation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import ltssm_pkg::*;
 
// SYNTHESIZABLE. Normal traffic is permitted only when the Link state
// says it is legal.
// THE GATING DISCIPLINE is Chapter 17.3 section 12's: gate BOTH
// directions, or a packet is destroyed at the boundary. Which states
// permit traffic is chapter 18.6's -- this model uses L0 only.
module ltssm_traffic_gate (
  input  ltssm_state_e state,
 
  input  logic up_valid,
  output logic up_ready,
  output logic down_valid,
  input  logic down_ready,
 
  output logic traffic_enabled
);
 
  assign traffic_enabled = traffic_legal(state);
 
  // ==================================================================
  // BOTH DIRECTIONS GATED.
  //
  // WRONG:  down_valid = up_valid && traffic_enabled;
  //         up_ready   = down_ready;              // <-- not gated
  //
  // With traffic_enabled = 0, up_valid = 1 and down_ready = 1, upstream
  // sees valid && ready and RELEASES the packet. Downstream sees
  // valid = 0 and receives nothing. The packet is destroyed -- and this
  // happens during Recovery and bring-up, when traffic is most likely to
  // be pushed and least likely to be instrumented (section 18).
  // ==================================================================
  assign down_valid = up_valid   && traffic_enabled;
  assign up_ready   = down_ready && traffic_enabled;
 
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import ltssm_pkg::*;
 
// SYNTHESIZABLE. Normalize control events from several engines into one
// prioritized stream for the state machine to consume.
// THE PRIORITY ORDER IS LOCAL IMPLEMENTATION POLICY, not a PCIe rule --
// the normative precedence among these conditions belongs to the
// chapters that own the states (section 1).
module ltssm_event_norm (
  input  logic clk,
  input  logic rst_n,
 
  // ---- From the physical and control engines -----------------------------
  input  logic detect_done,
  input  logic training_progress,
  input  logic config_done,
  input  logic recovery_needed,
  input  logic pm_request,
  input  logic hot_reset_seen,
  input  logic disable_request,
 
  // ---- Normalized, to the state machine -----------------------------------
  output logic          evt_valid,
  input  logic          evt_ready,
  output ltssm_reason_e evt_reason,
  output logic [2:0]    evt_source,
  output logic          evt_overflow
);
 
  // ==================================================================
  // PRIORITY, DECLARED AND ORDERED BY IRREVERSIBILITY.
  //
  // Reset-class events outrank power management, which outranks normal
  // progress -- because a reset invalidates the outcome of everything
  // below it, so deferring it would let the machine act on a decision
  // that is about to be discarded.
  //
  // This is THIS DESIGN'S ordering. PCIe's normative precedence among
  // these conditions is owned by chapters 18.2-18.10.
  // ==================================================================
  logic          v_q;
  ltssm_reason_e r_q;
  logic [2:0]    s_q;
  logic          ovf_q;
 
  assign evt_valid    = v_q;
  assign evt_reason   = r_q;
  assign evt_source   = s_q;
  assign evt_overflow = ovf_q;
 
  logic          sel_any;
  ltssm_reason_e sel_reason;
  logic [2:0]    sel_source;
 
  always_comb begin
    sel_any = 1'b1;
    if      (hot_reset_seen)    begin sel_reason = RSN_RESET;    sel_source = 3'd0; end
    else if (disable_request)   begin sel_reason = RSN_SOFTWARE; sel_source = 3'd1; end
    else if (recovery_needed)   begin sel_reason = RSN_ERROR;    sel_source = 3'd2; end
    else if (pm_request)        begin sel_reason = RSN_PM;       sel_source = 3'd3; end
    else if (config_done)       begin sel_reason = RSN_PROGRESS; sel_source = 3'd4; end
    else if (detect_done)       begin sel_reason = RSN_PROGRESS; sel_source = 3'd5; end
    else if (training_progress) begin sel_reason = RSN_PROGRESS; sel_source = 3'd6; end
    else begin sel_any = 1'b0; sel_reason = RSN_NONE; sel_source = 3'd7; end
  end
 
  wire fire     = v_q && evt_ready;
  wire can_take = !v_q || evt_ready;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      v_q <= 1'b0; r_q <= RSN_NONE; s_q <= 3'd7; ovf_q <= 1'b0;
    end else begin
      // Capture beats drain, so a same-cycle accept-and-consume holds the
      // new event rather than dropping it.
      if (sel_any && can_take) begin
        v_q <= 1'b1; r_q <= sel_reason; s_q <= sel_source;
      end else if (fire) begin
        v_q <= 1'b0;
      end
 
      // An event that could not be held is REPORTED. A lost reset or
      // recovery request is not something to discover in the lab.
      if (sel_any && !can_take) ovf_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

The traffic gate is two lines, and the symmetry is the entire content — Chapter 17.3 §12 proved why, and this is the same discipline applied to Link state rather than to link readiness.

The event normalizer's priority is ordered by irreversibility. Reset outranks power management outranks progress, because a reset discards the outcome of everything beneath it — so acting on a lower-priority event first means acting on a decision that is about to be thrown away.

And the priority is declared as this design's, not as PCIe's. §1's boundary applies: normative precedence among these conditions belongs to the state chapters.

14. Hierarchical Architecture

The alternative to §9's monolith, and the reason this chapter's RTL looks the way it does.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
         top-level major-mode controller

     ┌────────┬───────┼────────┬──────────┐
   Detect  Polling  Config  Recovery   Power
   subctl   subctl  subctl   subctl    subctl

The top level owns the major mode. Each subcontroller owns its own local progress, substates and timers.

The interface between them is narrow, and narrowness is the point:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
child_start    →    "begin your work"
child_active   ←    "I am working"
child_done     ←    "I finished successfully"
child_fail     ←    "I could not"
child_result   ←    whatever the parent needs (width, lane map, rate)

15. RTL — Stuck-State Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import ltssm_pkg::*;
 
// SYNTHESIZABLE. Lab-visibility monitor: warn when the machine has not
// moved for an unusually long time.
//
// ============================================================
// THIS IS DIAGNOSTIC ONLY. IT NEVER DRIVES A STATE TRANSITION.
//
// A monitor that could force the machine would be a second, undeclared
// controller -- and the resulting behaviour would depend on a debug
// threshold rather than on protocol conditions. The real timeouts belong
// to the chapters that own each state (section 1); this warns, and that
// is all it does.
// ============================================================
module ltssm_stuck_monitor #(
  parameter int CNT_W = 24
) (
  input  logic             clk,
  input  logic             rst_n,
  input  ltssm_state_e     state,
  input  logic             state_changed,
  input  logic [CNT_W-1:0] warn_limit,      // debug threshold, software-set
 
  output logic             stuck_warning,
  output ltssm_state_e     stuck_state,
  output logic [CNT_W-1:0] dwell
);
 
  logic [CNT_W-1:0] dwell_q;
  logic             warn_q;
  ltssm_state_e     stuck_q;
 
  assign dwell         = dwell_q;
  assign stuck_warning = warn_q;
  assign stuck_state   = stuck_q;
 
  wire at_limit = (warn_limit != '0) && (dwell_q >= warn_limit);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      dwell_q <= '0; warn_q <= 1'b0; stuck_q <= ST_DETECT;
    end else if (state_changed) begin
      // A transition clears the dwell measurement. The WARNING is sticky
      // so a transient stall that has since resolved is still readable --
      // which is the whole reason a debug register exists.
      dwell_q <= '0;
    end else begin
      if (dwell_q != {CNT_W{1'b1}}) dwell_q <= dwell_q + CNT_W'(1);
      if (at_limit && !warn_q) begin
        warn_q  <= 1'b1;
        stuck_q <= state;             // WHICH state it was stuck in
      end
    end
  end
 
endmodule

Classification: synthesizable (diagnostic).

stuck_state is captured, not just stuck_warning. By the time software reads the register the machine may have moved on — and "it was stuck in Polling" is a completely different investigation from "it was stuck in Recovery" (§16). A flag without the state is half a diagnostic.

And the warning is sticky while the dwell counter is not. The counter answers how long now; the sticky flag answers did this ever happen. Both are needed, and they are different questions.

16. A Trace

Internal teaching signals, not PCIe wire signals.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
step             1      2      3      4      5      6      7      8
state           DET    POL    CFG    L0     L0    RECOV  RECOV   L0
state_changed    -      1      1      1      0      1      0      1
prev_state      DET    DET    POL    CFG    CFG    L0     L0    RECOV
last_reason      -    PROG   PROG   PROG   PROG   ERROR  ERROR  PROG
transition_cnt   0      1      2      3      3      4      4      5
recovery_cnt     0      0      0      0      0      1      1      1
 
traffic_enabled  0      0      0      1      1      0      0      1
up_valid         1      1      1      1      1      1      1      1
up_ready         0      0      0      1      1      0      0      1
down_valid       0      0      0      1      1      0      0      1

Read steps 1–3. Bring-up. A packet has been waiting since step 1up_ready is low, nothing transfers, and it stays upstream.

Read step 4. L0. traffic_enabled rises, up_ready follows, and the packet finally moves.

Read step 6 — the case the whole chapter is about. Recovery is entered. traffic_enabled goes low and up_ready goes low with it. The packet in flight at that moment does not transfer — it remains upstream-owned, exactly as §8 requires. Nothing is dropped.

Read recovery_cnt at step 6. It increments once, on entry — not once per cycle spent in Recovery. That distinction is §10's, and it is what makes the counter readable.

Read step 8. Recovery completes, L0 returns, and the waiting packet transfers.

And note what never happens: up_ready is never high while traffic_enabled is low. That single invariant is P8, and it is the difference between a stalled packet and a destroyed one.

17. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over ltssm_observe, ltssm_timer, ltssm_entry_pulse,
// ltssm_traffic_gate, ltssm_event_norm and ltssm_stuck_monitor.
//
// These assert the OVERVIEW INFRASTRUCTURE's contract. They assert
// NOTHING about PCIe's legal state transitions, entry or exit criteria,
// or timeout values -- all owned by chapters 18.2-18.10 (section 1) --
// and nothing about training eventually succeeding, which depends on a
// partner and a channel this design does not control.
 
// ---- ENVIRONMENT ------------------------------------------------------
// A1: the state input is a legal encoding.
assume property (@(posedge clk) disable iff (!rst_n) state_legal(state));
// A2: reason accompanies a transition.
assume property (@(posedge clk) disable iff (!rst_n)
  (state != $past(state)) |-> (reason != RSN_NONE));
 
// ---- STATE OBSERVABILITY ----------------------------------------------
 
// P1: the exposed state tracks the machine with a defined one-cycle
// relationship -- never an ambiguous mixture.
property p_state_tracks;
  @(posedge clk) disable iff (!rst_n)
  cur_state == $past(state);
endproperty
a_track : assert property (p_state_tracks);
 
// P2: PREVIOUS STATE IS THE STATE BEFORE THE TRANSITION. Updating it
// early makes it equal cur_state and destroys the history.
property p_prev_correct;
  @(posedge clk) disable iff (!rst_n)
  $rose(state_changed) |=> (prev_state == $past(cur_state, 2));
endproperty
a_prev : assert property (p_prev_correct);
 
// P3: the transition count increments EXACTLY ONCE per change.
property p_count_once;
  @(posedge clk) disable iff (!rst_n)
  (transition_count != $past(transition_count))
    |-> ($past(state_changed)
      && (transition_count == $past(transition_count) + 1));
endproperty
a_count : assert property (p_count_once);
 
// P3b: and it does not move while the state is stable.
property p_count_stable;
  @(posedge clk) disable iff (!rst_n)
  !state_changed |=> $stable(transition_count);
endproperty
a_count_stable : assert property (p_count_stable);
 
// P3c: recovery_count increments on ENTRY to Recovery, not per cycle in it.
property p_recovery_on_entry;
  @(posedge clk) disable iff (!rst_n)
  (recovery_count != $past(recovery_count))
    |-> ($past(state_changed) && ($past(state) == ST_RECOVERY));
endproperty
a_rec : assert property (p_recovery_on_entry);
 
// ---- ENTRY PULSES -----------------------------------------------------
 
// P4: EXACTLY ONE entry pulse per transition, and it is one-hot.
property p_entry_one_hot;
  @(posedge clk) disable iff (!rst_n)
  (state != $past(state)) |-> ($countones(enter) == 1);
endproperty
a_entry : assert property (p_entry_one_hot);
 
// P5: NO entry pulse while the state is stable. The complementary half --
// a design producing a level rather than a pulse fails here.
property p_no_pulse_when_stable;
  @(posedge clk) disable iff (!rst_n)
  (state == $past(state)) |-> (enter == '0);
endproperty
a_stable : assert property (p_no_pulse_when_stable);
 
// P6: the pulse names the state being ENTERED.
property p_entry_names_new_state;
  @(posedge clk) disable iff (!rst_n)
  (enter != '0) |-> enter[state];
endproperty
a_names : assert property (p_entry_names_new_state);
 
// ---- TIMER ------------------------------------------------------------
 
// P7: the timer RESTARTS on a state change -- a new state never inherits
// the previous state's elapsed time.
property p_timer_restarts;
  @(posedge clk) disable iff (!rst_n)
  restart |=> (count == '0);
endproperty
a_restart : assert property (p_timer_restarts);
 
// P7b: limit == 0 means DISABLED, never instantly expired.
property p_zero_limit_disables;
  @(posedge clk) disable iff (!rst_n)
  (limit == '0) |-> !expired;
endproperty
a_zero : assert property (p_zero_limit_disables);
 
// P7c: SATURATES rather than wrapping -- the count never decreases
// except on a restart.
property p_timer_saturates;
  @(posedge clk) disable iff (!rst_n)
  (count < $past(count)) |-> $past(restart);
endproperty
a_sat : assert property (p_timer_saturates);
 
// ---- TRAFFIC GATE -----------------------------------------------------
 
// P8: THE CHAPTER'S CENTRAL PROPERTY. A packet blocked by an illegal
// state remains UPSTREAM-OWNED -- neither transferred nor destroyed.
// This is the property section 18's counterexample fails.
property p_packet_stays_upstream;
  @(posedge clk) disable iff (!rst_n)
  (up_valid && !traffic_enabled) |-> (!up_ready && !down_valid);
endproperty
a_ownership : assert property (p_packet_stays_upstream);
 
// P9: no transfer occurs while traffic is not legal.
property p_no_transfer_when_illegal;
  @(posedge clk) disable iff (!rst_n)
  !traffic_enabled |-> !(up_valid && up_ready);
endproperty
a_gated : assert property (p_no_transfer_when_illegal);
 
// P10: traffic_enabled implies the teaching model's traffic state.
property p_enabled_implies_l0;
  @(posedge clk) disable iff (!rst_n)
  traffic_enabled |-> (cur_state == ST_L0);
endproperty
a_l0 : assert property (p_enabled_implies_l0);
 
// P11: when traffic IS legal the gate is transparent -- it adds no
// behaviour of its own.
property p_transparent;
  @(posedge clk) disable iff (!rst_n)
  traffic_enabled |-> ((down_valid == up_valid) && (up_ready == down_ready));
endproperty
a_transparent : assert property (p_transparent);
 
// ---- EVENTS -----------------------------------------------------------
 
// P12: an event that could not be held is REPORTED, never silent.
property p_event_overflow_reported;
  @(posedge clk) disable iff (!rst_n)
  (sel_any && !can_take) |=> evt_overflow;
endproperty
a_ovf : assert property (p_event_overflow_reported);
 
// P13: the declared priority holds -- a reset-class event outranks
// everything beneath it.
property p_reset_priority;
  @(posedge clk) disable iff (!rst_n)
  (hot_reset_seen && can_take) |=> (evt_reason == RSN_RESET);
endproperty
a_priority : assert property (p_reset_priority);
 
// ---- SCOPE ------------------------------------------------------------
 
// P14: THE DIAGNOSTIC MONITOR NEVER DRIVES THE STATE MACHINE. A monitor
// that could force a transition would be a second, undeclared controller
// whose behaviour depended on a debug threshold (section 15).
property p_monitor_does_not_drive;
  @(posedge clk) disable iff (!rst_n)
  $rose(stuck_warning) |=> $stable(cur_state);
endproperty
a_scope : assert property (p_monitor_does_not_drive);
 
// P15: the stuck monitor captures WHICH state it was stuck in.
property p_stuck_state_captured;
  @(posedge clk) disable iff (!rst_n)
  $rose(stuck_warning) |-> (stuck_state == cur_state);
endproperty
a_stuck : assert property (p_stuck_state_captured);
 
// P16: reset initializes the observability set to a defined value.
property p_reset;
  @(posedge clk)
  !rst_n |=> ((transition_count == '0) && (recovery_count == '0)
           && !stuck_warning && !traffic_enabled);
endproperty
a_reset : assert property (p_reset);

P8 and P9 are not the same property, and the difference is Chapter 17.3 §12's counterexample all over again. P9 forbids a transfer reaching downstream; P8 forbids the asymmetric state in which upstream believes one happened. A design gating only valid satisfies P9 and fails P8 — which is why a property watching only the consumer cannot catch it.

P4 and P5 are the entry-pulse pair. One pulse per transition, and no pulse while stable — a design producing a level rather than a pulse passes P4 and fails P5.

P7, P7b and P7c are the three declared timer contracts, each asserted rather than left to inspection.

P14 is a scope property, in the style established since Chapter 15.5 §11: the diagnostic monitor must never become a controller. It cannot be written inside either module, and it is what catches a refactor that wires a debug threshold into the state machine.

No liveness is asserted. "The Link eventually reaches L0" depends on a partner and a physical channel, and asserting it would require assuming away precisely the failures Module 18 exists to explain.

18. Verification and Fault Injection

This verifies the overview infrastructure. It does not verify a PCIe LTSSM — there is no LTSSM here to verify (§9).

The scoreboard maintains its own state-history, timer and gate model, driven from observed transitions, and never reads the DUT's registers as its expected value.

State observability

  • A single transition — verify prev_state, last_reason, one increment (P2, P3).
  • The same state held for many cycles — verify no counting, no pulse (P3b, P5).
  • A transition to Recovery — verify recovery_count increments once (P3c).
  • Many cycles in Recovery — verify it does not keep incrementing. Required.
  • Counter to saturation — verify it holds rather than wrapping.
  • An illegal state encoding — verify illegal_state and no corruption.

Timer

  • restart and a would-be tick in the same cycle — verify restart wins (P7). Required.
  • limit = 0 — verify disabled, not instantly expired (P7b). Required.
  • Count to the limit — verify expired and saturation (P7c).
  • CNT_W = 1 and 24.

Entry pulses

  • Every state entered in turn — verify one-hot, one cycle, naming the new state (P4, P6).
  • A transition back to a previously-visited state — verify a fresh pulse.
  • State stable across many cycles — verify no pulses (P5).

Traffic gate

  • Traffic illegal, up_valid high, down_ready high — verify up_ready is low and the packet stays (P8). Required, and the counterexample test.
  • Transition into L0 with a packet waiting — verify it transfers.
  • Transition out of L0 mid-stall — verify the packet remains offered.
  • Downstream stalling while in L0 — verify transparency (P11).

Events

  • Two events in the same cycle — verify the declared priority (P13).
  • An event while the machine is not ready — verify holding, then overflow if it cannot be held (P12).
  • Hot reset concurrent with a progress event — verify reset wins.

Mutations

#MutationCaught byLab symptom
1traffic enabled in RecoveryP10, P9packets sent into a Link that cannot carry them
2prev_state updates before the transitionP2history register useless; prev == cur always
3transition counter increments every cycleP3bcounts dwell time, not transitions; huge numbers
4entry pulse stays highP5blocks re-initialise continuously
5timer does not restart on a state changeP7new states inherit elapsed time; spurious timeouts
6limit = 0 treated as instant expiryP7bevery timeout fires during reset
7timer wraps instead of saturatingP7cone timeout becomes periodic
8illegal enum acceptedillegal_state never assertsundefined behaviour downstream
9stuck monitor drives a transitionP14Link behaviour depends on a debug threshold
10recovery_count increments per cycle in RecoveryP3ccount reflects dwell, not entries; useless
11up_ready not gated (only valid)P8packets silently destroyed during Recovery
12hot reset treated as an ordinary PM eventP13reset deferred behind power management
13cur_state and the machine differ by two cyclesP1debug register disagrees with reality
14stuck warning without capturing the stateP15"something was stuck" — no idea what

19. Debugging

The LTSSM state is the first thing a hardware engineer reads when a PCIe Link misbehaves, and this section is the map from state to investigation.

The first question is not "why is the Link down" — it is "where did the machine stop".

Stuck inInvestigate
Detectphysical presence, termination, power, the partner being alive at all
Pollingbit and symbol lock, signal quality, the partner transmitting recognisable patterns
Configurationlane usability and numbering, width negotiation, partner capability
cycling through Recoveryrate stability, equalization, channel margin

§15's stuck_state exists for exactly this, and it captures the state rather than only the fact — because these four rows lead to four completely different investigations.

And it separates a PHY problem from an enumeration problem in one reading. Chapter 7.3's discovery cannot find a device whose Link never trained. "Not in lspci" plus "stuck in Detect" is a physical problem; "not in lspci" plus "reached L0" is a software one.

Read recovery_count first (§10). A Link entering Recovery occasionally is normal; a Link entering it continuously is reporting a physical problem it keeps trying to fix.

Look below the packet layers first: PHY error counters and lock-loss counts (Chapter 17.5 §16), equalization results (Chapter 17.4), and channel margin.

The distinguishing experiment: correlate recovery_count against rate. If it stops at a lower negotiated speed, the cause is margin. If it persists at every rate, look at the control plane.

And resist the terminology trap (§4): repeated Recovery is not Data Link retry. A high replay count and a high recovery count can both be present, and the recovery count is the one closer to the cause.

The device disappears intermittently after boot

Capture LTSSM history, not just software logs.

Software sees a device vanish. transition_count, recovery_count, prev_state and last_reason show what the Link did — and whether it went through Recovery, Hot Reset, Disabled, or a power state on the way.

last_reason is the highest-value field here (§10). A departure from L0 for RSN_PM is power management working; for RSN_ERROR it is not, and the two are indistinguishable from software's point of view.

Check the traffic gate before anything else (§18's counterexample).

Inspect whether up_ready is ever high while traffic_enabled is low. That single condition is the bug, and it is a one-line fix.

The distinguishing experiment: hold traffic off until the state has been stable in L0 for many cycles. If packets stop disappearing, it is the gate.

A state name alone is not a root cause

"Stuck in Polling" is a location, not a diagnosis. It narrows the search enormously — and the actual cause is in the physical layer, the partner, or the channel.

The child chapters (18.218.10) provide the per-state detail that turns a location into a cause.

20. Common Misconceptions

  • "The LTSSM is software." Hardware, reacting at Link timescales. Software sets policy and observes (§3).
  • "The LTSSM only runs at boot." It runs continuously — Recovery, power states and reset all happen during normal operation (§6).
  • "L0 means the device is in D0." Different axes: Link state versus Function power state (§4).
  • "Polling means polling a register." A name collision with a software idiom (§4).
  • "Recovery means Transaction Layer retry." Physical re-training versus Data Link replay (§4).
  • "Configuration state means Configuration Space access." Physical Link configuration versus a software register read (§4).
  • "Detect means software discovery." Hardware receiver presence versus enumeration (§4).
  • "The LTSSM processes Memory Reads." It carries no packets and interprets none (§2).
  • "Equalization and the LTSSM are one block." Equalization is coordinated by states owned by Chapter 18.5; it is not the machine (§7).
  • "All LTSSM states may carry normal traffic." Traffic is legal in normal operation only, and the gate enforces it (§8).
  • "TS1 and TS2 are Transaction Layer packets." Physical-layer ordered sets, used before either upper layer can operate (§2).
  • "One giant case statement is the best RTL architecture." It is harder to verify, debug, reuse and change (§9, §14).
  • "A state name alone diagnoses the root cause." It localises the search; the cause is usually below it (§19).

21. Module 18 Learning Map

What each remaining chapter answers.

Engineering question
18.2Detecthow does a port determine that a receiver is present at all?
18.3Pollinghow are bit and symbol lock established before anything else can proceed?
18.4Configurationhow do lane numbering and width negotiation turn lanes into a Link?
18.5Recoveryhow does a Link re-train, and how does a speed change actually happen?
18.6L0what is true during normal operation, and what may happen there?
18.7L0show is a shallow low-power state entered and exited quickly?
18.8L1what does the deeper state save, and what does exit cost?
18.9Disabledhow and why is a Link deliberately taken out of operation?
18.10Hot Resethow does in-band reset propagate through a hierarchy?

22. Understanding Check

23. What's Next

The LTSSM answers one question continuously — what is the Link doing? — and that answer determines which behaviours are legal. It is hardware, it runs for the Link's whole life rather than only at boot, and its state is the first register an engineer reads when a Link misbehaves.

This chapter built what surrounds it: observability and history so a nanosecond-scale machine leaves a readable record; a dwell timer with declared contracts; a shared entry pulse so several blocks agree about when; a traffic gate that keeps a packet upstream rather than destroying it; and an event normalizer with a declared priority.

And it deliberately did not build the machine, because the states are where the engineering is — and each of them is a chapter.

Chapter 18.2 — Detect begins there, with the first question any Link must answer: is there anything on the other end at all?

The idea to carry forward: a mode controller's real product is not the mode — it is the guarantee that everything incompatible with it is off.