Skip to content

PCIe · Module 17

Link Training — A Negotiated Contract, Not a Connection

Connected traces are not a usable Link. Training produces a negotiated width, speed and lane configuration that everything above depends on — and the final result is bounded by mutual capability rather than equal to it.

Chapters 17.1 and 17.2 both ended by assuming something they never established.

The transmitter's distributor needed to know which lanes are active. The receiver's aligner needed a partner transmitting recognisable evidence, and its deskew logic needed lane identity — which physical lane is logical lane 0.

None of that is true when a Link powers up. Two ports are electrically connected and know nothing about each other: not whether anyone is there, not how fast the other end can run, not how many lanes actually work, not even which lane is which.

Link training is how they find out — and what it produces is not a connection. It is a negotiated contract that everything above the Physical Layer then depends on.

How do two directly connected ports get from "traces are attached" to a usable, agreed Link configuration?

1. What Is Verified, and What Belongs Elsewhere

2. Training Is Not Enumeration

Two processes that both "bring up" a PCIe system, at completely different layers.

Link trainingEnumeration (Module 7)
Scopeone Link, two adjacent portsthe whole hierarchy
Performed byhardwaresoftware
Establisheswidth, speed, lane configurationbus numbers, BARs, resources
Answerscan these two ports talk, and how well?what is out there, and how is it addressed?
Requires the otherrequires trained Links to exist first

3. What This Chapter Owns, and What Module 18 Owns

The boundary matters enough to draw explicitly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
17.3  — WHAT training accomplishes, and what the rest of the stack
         does with the result: a committed width, speed and lane
         configuration, and the gate that traffic passes through.
 
Mod 18 — HOW the process runs: Detect, Polling, Configuration,
         Recovery, and the state-by-state transition criteria.

4. What Must Be Established

At a high level, and in the order the dependencies fall.

EstablishedWhy nothing works without it
partner presencethere is no point training against nothing
synchronization17.2 §3 — without boundaries, correct bits are useless
lane usabilitya lane that does not work must not carry traffic
lane identity / numbering17.2 §7 — recombination is impossible without it
negotiated widthhow many lanes the Link actually operates
negotiated speedthe rate both ends will run
completiona point after which the stack may rely on all of the above

The mechanism by which adjacent ports exchange this information is training ordered sets — structured patterns sent on the lanes themselves, repeatedly, so that both ends can observe them, agree, and converge.

Port A detects the presence of a receiver on the lanes. Both ports begin transmitting training signalling. Each port synchronizes to the other's stream and establishes lane identity. The ports then exchange capability and lane information by repeated observation. Both converge on a mutually supported configuration, commit it, and only then does normal link traffic begin to flow.From electrical connection to a committed configurationPort ALanesPort Breceiver presencepresence detectedtraining signallingobserved repeatedlytraining signallingsynchronize, laneidentitycapability and lanescapability and lanesconverge, thencommitconverge, thencommitnormal traffic -link ready
Figure 1 — the high-level shape of a training exchange between two adjacent ports. No fixed delays are shown, because the timing is governed by the state machine Module 18 owns. What this figure claims is the ordering of dependencies: presence before signalling, signalling before synchronization, synchronization before any information exchange, and a committed configuration before normal traffic.

Three things to read out of the figure, and one thing not to.

The dependencies are ordered and the timing is not shown. Presence precedes signalling, signalling precedes synchronization, synchronization precedes any exchange of information, and commitment precedes traffic. No delays are drawn, because the durations and timeouts belong to Module 18.

Both directions do the same work. Each port must synchronize to the other's stream and commit its own view of the result — there is no master here, which is why §12's commit logic is a per-port structure rather than one shared decision.

And the exchange is drawn as repeated observation, not as a request and a reply. §4's callout is why.

What not to read into it: this is not a state diagram. It does not show Detect, Polling, Configuration or their transitions — Chapter 18.1 onward do that, and mapping this figure onto those states would be reading structure into it that it does not have (§3).

5. Lane Identity

Physical lanes are not automatically logical lanes.

A x8 connector has eight lane positions, but which one the receiver should treat as logical lane 0 is not determined by the board — it is determined during training. §1's source is explicit: the link number "changes to the negotiated Link Number determined during LTSSM Configuration."

Why it cannot be assumed. Boards route lanes in whatever order layout permits. Lane reversal — where the physical ordering is inverted end to end — is a supported capability, and §1's source lists polarity inversion alongside it. A receiver that assumed physical lane n is logical lane n would work on some boards and fail on others, with no protocol error to explain it.

What this chapter claims: training converts a set of raw available lanes into a coherent logical Link, and that mapping is what Chapter 17.2 §7's recombination depends on.

What it does not claim: the algorithm. Chapter 18.4 owns lane-number negotiation, and publishing a guess at the reversal algorithm here would be both out of scope and unverified.

6. Mutual Capability Is a Ceiling, Not a Result

The most useful idea in the chapter, and the one that changes how an engineer reads a link-speed complaint.

The naive model is min(local, remote). Both ends advertise what they support; the Link runs at the lesser. It gives the right answer often enough to be believed, and it is wrong in a specific and important way.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
final configuration  ≤  mutual advertised capability

Not equal. Bounded.

7. Width and Speed Are Independent

A common conflation worth separating explicitly.

WidthSpeed
What it ishow many lanes operatethe per-lane rate
Set bylane viability + both ends' capabilitygeneration support + channel
Doubling ittwice the parallel streamstwice the rate per stream
Fallbackdrop lanesdrop a generation

They are negotiated separately and can degrade separately. A Link can be Gen5 x4 — full speed, half width — or Gen3 x16 — full width, reduced speed. Both are ordinary outcomes, and they have different causes: width loss points at lanes, speed loss points at the channel and equalization.

Which is why §1's source exposes them as separate fields"Bits 3:0 — Link Speed, Bits 9:4 — Negotiated Link Width." Two numbers, because they are two decisions.

8. The Contract Is Not Permanent

§1's source lists support for "Autonomous Link Width/Speed changes", so the committed configuration can change after initial training.

This chapter does not model why or howChapter 18.5 owns re-training and speed change through Recovery, and Module 18 owns the conditions.

What matters here is the architectural consequence, and it is what §12's coordinator is built around:

Everything above the Physical Layer consumes a configuration it does not control and cannot assume is eternal.

So the committed configuration needs an explicit lifecycle: it becomes valid at a commit, remains stable while the Link is usable, and becomes invalid through a defined path — a retrain or a Link-down. A design that latches the width once at power-up and never revisits it will drive lanes that are no longer part of the Link.

9. 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
partner_seen     0     1     1     1     1     1     1     0
train_valid      0     0     1     1     1     1     1     0
lane_good_mask  --    --   0F    0F    0F    0F    0F    00
 
result_valid     0     0     0     1     0     0     0     0
result_width     -     -     -    x4     -     -     -     -
result_speed     -     -     -   gen4    -     -     -     -
 
config_valid     0     0     0     0     1     1     1     0
link_ready       0     0     0     0     1     1     1     0
 
pkt_valid        1     1     1     1     1     1     1     1
pkt_ready        0     0     0     0     1     0     1     0
send_fire        0     0     0     0     1     0     1     0
link_down        0     0     0     0     0     0     0     1

Read steps 1–3. A partner is detected and training begins. A packet has been waiting since step 1 and is going nowherepkt_ready is low, so nothing transfers and the packet stays upstream.

Read step 4. The training machinery produces a result: x4 at Gen4, from a lane-good mask of 0x0F.

Read step 5. The result is committed. config_valid and link_ready rise together, pkt_ready follows, and the packet that has been waiting four cycles finally transfers.

Read step 6 — the case that matters. link_ready is high but the downstream path is not ready. pkt_ready is low and send_fire is low. Both conditions are required (§11).

Read step 8 — Link down. config_valid and link_ready clear together, pkt_ready goes low, and the packet in flight at that moment simply does not transfer. It remains upstream-owned. Nothing is dropped, and §11 is entirely about why that is harder to get right than it looks.

10. RTL — Capability Intersection and Speed Selection

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Capability intersection and highest-common-rate
// selection.
// THIS IS AN ILLUSTRATIVE CONTROL CALCULATION. Real PCIe speed
// negotiation is a multi-cycle protocol exchange owned by the LTSSM
// (Chapter 18.5). What generalises is the arithmetic and its range safety.
module capability_intersect #(
  // One bit per supported rate, ascending: bit 0 is the lowest rate.
  parameter int RATES = 5
) (
  input  logic [RATES-1:0] local_speed_mask,
  input  logic [RATES-1:0] remote_speed_mask,
 
  input  logic [4:0]       local_max_width,     // in lanes
  input  logic [4:0]       remote_max_width,
 
  output logic [RATES-1:0] mutual_speed_mask,
  output logic             speed_valid,         // any common rate at all?
  output logic [$clog2(RATES)-1:0] highest_common_speed,
  output logic [4:0]       width_cap
);
 
  generate
    if (RATES < 1) $error("RATES must be at least 1");
  endgenerate
 
  assign mutual_speed_mask = local_speed_mask & remote_speed_mask;
  assign speed_valid       = (mutual_speed_mask != '0);
 
  // WIDTH CEILING: neither end can operate wider than it supports.
  // This is a CEILING, not the result -- section 6.
  assign width_cap = (local_max_width < remote_max_width) ? local_max_width
                                                          : remote_max_width;
 
  // HIGHEST SET BIT, written as a downward scan rather than a shift loop.
  // A shift-based search invites an out-of-range shift amount; this form
  // cannot produce one, and it is trivially provable (P2).
  always_comb begin
    highest_common_speed = '0;
    for (int r = 0; r < RATES; r++)
      if (mutual_speed_mask[r]) highest_common_speed = ($clog2(RATES))'(r);
  end
 
endmodule

Classification: synthesizable (illustrative control calculation).

Architecture. A bitwise AND, a comparison, and an ascending scan that leaves the highest set index. The scan form is chosen deliberately over a shift-and-test loop, which is where out-of-range shift amounts come from.

Behaviour at the corners.

localremotemutualspeed_validhighest
11111011110111113
00001000010000110
11000000110000000 (meaningless — gated)
11111111111111114

The third row is the one that needs care. With no common rate, highest_common_speed has no meaningful value — so speed_valid must gate every use of it (P3), and §12's coordinator refuses to commit without it.

Failure — four. A shift-based search can shift by an out-of-range amount when the mask is empty. Using highest_common_speed without checking speed_valid commits a configuration that no common capability supports. Taking the local maximum rather than the intersection advertises a rate the partner cannot meet. And treating width_cap as the negotiated width confuses a ceiling with a result (§6).

11. RTL — Lane Mask to Width

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Normalized lane mask -> operating width.
// THE CONTIGUOUS-FROM-ZERO POLICY IS ILLUSTRATIVE (see the callout).
// What generalises: a width is a legal Link width, not a popcount, and a
// non-conforming mask must be REJECTED rather than rounded.
module lane_width_classifier #(
  parameter int LANES = 16
) (
  input  logic [LANES-1:0] lane_good_mask,     // normalized logical lanes
 
  output logic [4:0]       width,              // 1,2,4,8,16
  output logic             width_valid,
  output logic [LANES-1:0] width_mask          // lanes the width uses
);
 
  generate
    if (LANES < 1) $error("LANES must be at least 1");
  endgenerate
 
  // COUNT THE CONTIGUOUS RUN FROM LANE 0. A gap ends the run -- lane 3
  // being good does not help if lane 1 is not.
  logic [5:0] run;
  always_comb begin
    run = '0;
    for (int i = 0; i < LANES; i++)
      if (lane_good_mask[i] && (run == 6'(i))) run = 6'(i) + 6'd1;
  end
 
  // ===================================================================
  // A WIDTH IS NOT A POPCOUNT.
  //
  // Five good lanes is not "x5" -- there is no such Link width. The run
  // is reduced to the largest legal width at or below it, and a run of
  // zero is rejected outright rather than reported as some width.
  // ===================================================================
  always_comb begin
    width_valid = 1'b1;
    if      (run >= 6'd16) width = 5'd16;
    else if (run >= 6'd8)  width = 5'd8;
    else if (run >= 6'd4)  width = 5'd4;
    else if (run >= 6'd2)  width = 5'd2;
    else if (run >= 6'd1)  width = 5'd1;
    else begin
      width       = 5'd0;
      width_valid = 1'b0;              // no usable lane at all
    end
  end
 
  // The mask the chosen width actually uses -- which is NOT necessarily
  // lane_good_mask. Extra good lanes beyond the legal width are not used,
  // and must not be driven (Chapter 17.1 P14).
  always_comb begin
    width_mask = '0;
    for (int i = 0; i < LANES; i++)
      if (width_valid && (6'(i) < 6'(width))) width_mask[i] = 1'b1;
  end
 
endmodule

Classification: synthesizable (illustrative policy).

Architecture. A contiguous-run count, a reduction to a legal width, and the mask that width actually uses.

The distinction between lane_good_mask and width_mask is the real content. Five usable lanes yield x4, and the fifth lane is not drivenChapter 17.1's distributor takes width_mask, not the good mask, and P14 there forbids traffic on anything else.

lane_good_maskrunwidthwidth_mask
0x00FF8x80x00FF
0x001F5x40x000F
0x000D1x10x0001 — lane 1 is bad, so the run stops
0x00000width_valid low

Failure — four. Using a popcount produces widths that do not exist. Ignoring the gap in 0x000D claims x2 or x4 from lanes that are not contiguous under this policy. Driving every good lane rather than width_mask puts traffic on a lane outside the negotiated Link. And reporting a width when no lane is usable lets a Link come up with nothing behind it.

12. RTL — Committed Configuration and the Traffic Gate

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Commit a training result and expose it as stable Link
// configuration.
// THAT A NEGOTIATED WIDTH AND SPEED BECOME ARCHITECTURALLY VISIBLE STATE
// is verified (section 1's Link Status register). THIS IS NOT THE PCIe
// LTSSM -- it consumes an already-normalized result (section 3).
module link_config_commit #(
  parameter int LANES = 16,
  parameter int RATES = 5
) (
  input  logic clk,
  input  logic rst_n,
 
  // ---- Normalized result, from lower training machinery -----------------
  input  logic                     result_valid,
  input  logic [4:0]               result_width,
  input  logic                     result_width_valid,
  input  logic [$clog2(RATES)-1:0] result_speed,
  input  logic                     result_speed_valid,
  input  logic [LANES-1:0]         result_lane_mask,
 
  // ---- Invalidation ------------------------------------------------------
  input  logic                     link_down,
  input  logic                     retrain_req,
 
  // ---- Committed configuration, upward -----------------------------------
  output logic                     config_valid,
  output logic [4:0]               active_width,
  output logic [$clog2(RATES)-1:0] active_speed,
  output logic [LANES-1:0]         active_lane_mask,
  output logic                     link_ready
);
 
  typedef enum logic [1:0] { S_WAIT_RESULT, S_COMMIT, S_LINK_READY } st_e;
  st_e st_q;
 
  logic [4:0]                     w_q;
  logic [$clog2(RATES)-1:0]       s_q;
  logic [LANES-1:0]               m_q;
  logic                           v_q;
 
  assign config_valid     = v_q;
  assign active_width     = w_q;
  assign active_speed     = s_q;
  assign active_lane_mask = m_q;
  // LINK_READY IMPLIES A VALID COMMITTED CONFIGURATION -- never one
  // without the other (P6).
  assign link_ready       = (st_q == S_LINK_READY) && v_q;
 
  // A RESULT IS ONLY COMMITTABLE IF IT IS INTERNALLY COHERENT.
  // An unknown width, a rate with no common capability, or a lane mask
  // that does not match the width are all refusals -- not things to
  // commit and hope about (P4, P8).
  wire width_legal = (result_width inside {5'd1, 5'd2, 5'd4, 5'd8, 5'd16});
  wire mask_matches = ($countones(result_lane_mask) == result_width);
  wire committable = result_valid && result_width_valid && result_speed_valid
                                  && width_legal && mask_matches;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_q <= S_WAIT_RESULT;
      w_q  <= '0; s_q <= '0; m_q <= '0; v_q <= 1'b0;
    end else if (link_down) begin
      // ================================================================
      // LINK DOWN INVALIDATES THE CONFIGURATION, UNCONDITIONALLY AND
      // WITH HIGHEST PRIORITY.
      //
      // Checked before everything else, so a result arriving in the same
      // cycle cannot be committed onto a Link that has just gone away.
      // ================================================================
      st_q <= S_WAIT_RESULT;
      v_q  <= 1'b0;
    end else begin
      unique case (st_q)
        S_WAIT_RESULT :
          if (committable) begin
            w_q  <= result_width;
            s_q  <= result_speed;
            m_q  <= result_lane_mask;
            v_q  <= 1'b1;
            st_q <= S_COMMIT;
          end
 
        // One cycle of separation between capturing and declaring ready,
        // so consumers see config_valid settle before link_ready rises.
        S_COMMIT :
          st_q <= S_LINK_READY;
 
        S_LINK_READY :
          // THE CONFIGURATION IS STABLE WHILE THE LINK IS READY. It changes
          // only through a represented path -- a retrain request or a link
          // down (section 8). A new result_valid alone does NOT overwrite
          // it, because traffic is relying on the current values.
          if (retrain_req) begin
            st_q <= S_WAIT_RESULT;
            v_q  <= 1'b0;
          end
 
        default : st_q <= S_WAIT_RESULT;
      endcase
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. Three states and one set of committed registers. The configuration changes only through a represented path — commit, retrain, or Link down.

link_down is checked before the case statement, deliberately. A result arriving in the same cycle as a Link-down must not be committed, and putting the invalidation first makes that unconstructable rather than dependent on if ordering.

Cycle behaviour.

EventResult
incoherent result — bad width, no common speed, mask mismatchrefused, no commit
coherent result while waitingcommitted; config_valid rises
result_valid again while LINK_READYignored — traffic is relying on the current values
retrain_reqconfig invalidated; back to waiting
link_downconfig invalidated immediately, from any state
link_down and result_valid same cycledown wins

Failure — four. Committing an incoherent result brings the Link up with a width no lane mask supports. Asserting link_ready before config_valid lets traffic through against uncommitted configuration. Allowing a new result to overwrite while ready changes the width under active traffic. And not clearing on link_down leaves the stack driving a Link that no longer exists.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Gate normal traffic on link readiness.
// A STATELESS gate: the packet remains upstream-owned while the Link is
// not ready. See the counterexample below for why the obvious version
// loses packets.
module link_traffic_gate (
  input  logic link_ready,
 
  // ---- Upstream (Data Link Layer) ----------------------------------------
  input  logic up_valid,
  output logic up_ready,
 
  // ---- Downstream (PHY transmit path) ------------------------------------
  output logic down_valid,
  input  logic down_ready
);
 
  // ==================================================================
  // BOTH DIRECTIONS ARE GATED. This is the entire module and it is the
  // entire lesson.
  //
  // WRONG:
  //     assign down_valid = up_valid && link_ready;
  //     assign up_ready   = down_ready;              // <-- NOT gated
  //
  // With link_ready = 0, up_valid = 1 and down_ready = 1, upstream sees
  // valid && ready and considers the packet TRANSFERRED. Downstream sees
  // valid = 0 and receives nothing. THE PACKET IS GONE -- not stalled,
  // not retried, silently destroyed at a layer boundary.
  // ==================================================================
  assign down_valid = up_valid   && link_ready;
  assign up_ready   = down_ready && link_ready;
 
endmodule

Classification: synthesizable (combinational).

Two lines, and the symmetry is the whole point. Gating only valid creates a state in which the upstream believes a transfer occurred and the downstream never saw one — a packet destroyed by an interface convention violation, with no error anywhere.

Why a stateless gate is the right choice here. The packet stays where it was: upstream owns it, upstream holds it, and it transfers when the Link is ready. No storage, no ownership transfer, nothing to lose. A design that buffered the packet inside the gate would have to answer what happens to it on a Link-down, and every answer is worse than not having taken it.

And a packet already past this point is not this module's problem. If it entered the replay machinery, Chapter 14.4 owns its recovery — which is precisely why the boundary is drawn here rather than deeper.

13. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over capability_intersect, lane_width_classifier,
// link_config_commit and link_traffic_gate. These assert the LOCAL
// negotiation-result contract. They assert NOTHING about the LTSSM
// (Module 18), equalization (17.4), the channel (17.5), or that training
// eventually succeeds -- which depends on a partner and a physical
// channel this design does not control.
 
// ---- ENVIRONMENT ------------------------------------------------------
// A1: the result comes from lower training machinery and is stable while
// result_valid is asserted.
assume property (@(posedge clk) disable iff (!rst_n)
  result_valid |-> !$isunknown({result_width, result_speed, result_lane_mask}));
// A2: link_down and retrain_req are not asserted together.
assume property (@(posedge clk) disable iff (!rst_n)
  !(link_down && retrain_req));
 
// ---- CAPABILITY -------------------------------------------------------
 
// P1: THE SELECTED RATE IS SUPPORTED BY BOTH ENDS. The chapter's
// foundational negotiation property.
property p_speed_mutually_supported;
  @(posedge clk) disable iff (!rst_n)
  speed_valid |-> (local_speed_mask[highest_common_speed]
                && remote_speed_mask[highest_common_speed]);
endproperty
a_mutual : assert property (p_speed_mutually_supported);
 
// P2: IT IS THE HIGHEST such rate -- no higher bit is common. Restated
// independently so a broken scan cannot satisfy it by agreeing with itself.
property p_speed_is_highest;
  @(posedge clk) disable iff (!rst_n)
  speed_valid |-> ((mutual_speed_mask >> (highest_common_speed + 1)) == '0);
endproperty
a_highest : assert property (p_speed_is_highest);
 
// P3: with NO common rate, speed_valid is low -- and every consumer must
// gate on it, because the index is meaningless.
property p_no_common_no_valid;
  @(posedge clk) disable iff (!rst_n)
  ((local_speed_mask & remote_speed_mask) == '0) |-> !speed_valid;
endproperty
a_empty : assert property (p_no_common_no_valid);
 
// P4: WIDTH IS A LEGAL LINK WIDTH, NEVER A POPCOUNT.
property p_width_legal;
  @(posedge clk) disable iff (!rst_n)
  width_valid |-> (width inside {5'd1, 5'd2, 5'd4, 5'd8, 5'd16});
endproperty
a_width_legal : assert property (p_width_legal);
 
// P5: the width uses only lanes that are actually good, and exactly as
// many as the width claims.
property p_width_mask_consistent;
  @(posedge clk) disable iff (!rst_n)
  width_valid |-> (((width_mask & ~lane_good_mask) == '0)
                && ($countones(width_mask) == width));
endproperty
a_mask : assert property (p_width_mask_consistent);
 
// ---- COMMITTED CONFIGURATION ------------------------------------------
 
// P6: LINK_READY IMPLIES A VALID COMMITTED CONFIGURATION. Never one
// without the other.
property p_ready_implies_valid;
  @(posedge clk) disable iff (!rst_n)
  link_ready |-> config_valid;
endproperty
a_ready_valid : assert property (p_ready_implies_valid);
 
// P7: THE CONFIGURATION IS STABLE WHILE LINK_READY. Traffic is relying on
// these values; they cannot move underneath it.
property p_config_stable;
  @(posedge clk) disable iff (!rst_n)
  (link_ready && !link_down && !retrain_req)
    |=> ($stable(active_width) && $stable(active_speed)
      && $stable(active_lane_mask));
endproperty
a_stable : assert property (p_config_stable);
 
// P8: an INCOHERENT result never commits -- no illegal width, no rate
// without common capability, no mask that disagrees with the width.
property p_no_incoherent_commit;
  @(posedge clk) disable iff (!rst_n)
  $rose(config_valid) |-> $past(committable);
endproperty
a_coherent : assert property (p_no_incoherent_commit);
 
// P9: the committed width never exceeds either end's capability.
property p_width_within_capability;
  @(posedge clk) disable iff (!rst_n)
  config_valid |-> (active_width <= width_cap);
endproperty
a_within : assert property (p_width_within_capability);
 
// P10: LINK_DOWN CLEARS READINESS AND VALIDITY, from any state.
property p_down_clears;
  @(posedge clk) disable iff (!rst_n)
  link_down |=> (!link_ready && !config_valid);
endproperty
a_down : assert property (p_down_clears);
 
// P11: link_down WINS over a same-cycle result.
property p_down_beats_result;
  @(posedge clk) disable iff (!rst_n)
  (link_down && result_valid) |=> !config_valid;
endproperty
a_priority : assert property (p_down_beats_result);
 
// P12: the configuration changes ONLY through a represented path.
property p_change_only_via_path;
  @(posedge clk) disable iff (!rst_n)
  (!$stable(active_width) && $past(config_valid))
    |-> ($past(link_down) || $past(retrain_req));
endproperty
a_path : assert property (p_change_only_via_path);
 
// ---- TRAFFIC GATE -----------------------------------------------------
 
// P13: NO TRANSFER WHILE THE LINK IS NOT READY.
property p_no_traffic_when_down;
  @(posedge clk) disable iff (!rst_n)
  !link_ready |-> !(up_valid && up_ready);
endproperty
a_gated : assert property (p_no_traffic_when_down);
 
// P14: THE OWNERSHIP PROPERTY. A packet blocked by a not-ready Link
// remains upstream-owned -- it is neither transferred nor destroyed.
// This is the property the broken gate in section 12 fails.
property p_packet_stays_upstream;
  @(posedge clk) disable iff (!rst_n)
  (up_valid && !link_ready) |-> (!up_ready && !down_valid);
endproperty
a_ownership : assert property (p_packet_stays_upstream);
 
// P15: when the Link IS ready, the gate is transparent -- it adds no
// behaviour of its own.
property p_transparent_when_ready;
  @(posedge clk) disable iff (!rst_n)
  link_ready |-> ((down_valid == up_valid) && (up_ready == down_ready));
endproperty
a_transparent : assert property (p_transparent_when_ready);
 
// P16: a packet stalled by a link-down is not lost -- it is still offered
// once the Link returns.
property p_stalled_packet_survives;
  @(posedge clk) disable iff (!rst_n)
  (up_valid && !up_ready) |=> up_valid;
endproperty
a_survives : assert property (p_stalled_packet_survives);
 
// ---- SCOPE ------------------------------------------------------------
 
// P17: nothing here is the LTSSM. The coordinator never claims a link
// state -- an integration property, asserted so a future refactor that
// merges the two is caught (section 3).
property p_not_an_ltssm;
  @(posedge clk) disable iff (!rst_n)
  config_valid |-> (st_q inside {S_COMMIT, S_LINK_READY});
endproperty
a_scope : assert property (p_not_an_ltssm);

P1 and P2 are the negotiation pair, and P2 is restated independently: mutual_speed_mask >> (highest + 1) must be zero. A property written against the module's own scan would agree with a scan that stopped early.

P13 and P14 are not the same property, and the difference is the entire §12 counterexample. P13 forbids a transfer; P14 forbids the asymmetric state in which upstream sees ready while downstream sees no valid. A design with the broken gate satisfies P13 — no transfer reaches downstream — and fails P14, because upstream believed one happened.

P11 exists because same-cycle priority is a real design decision, not an accident of if ordering. §12 checks link_down before the case statement so the property holds structurally.

No liveness is asserted. "Training eventually succeeds" depends on a partner and a physical channel — neither of which this design controls, and asserting it would require fairness assumptions that assume away the failures worth catching.

14. Verification and Fault Injection

The scoreboard computes the intersection and the expected commitment itselfintersection = local & remote, expected = highest_set_bit(intersection)and never calls the DUT's selector or classifier.

Speed

  • Identical capabilities — verify the highest common is the highest supported.
  • Local faster than remote, and remote faster than local — verify the result is bounded by the lesser (P1).
  • Several common rates, verify the highest is chosen (P2).
  • No overlap at all — verify speed_valid low and no commit (P3, P8). Required.
  • All single-bit combinations — exhaustive at RATES = 5 is 1024 pairs and cheap.

Width

  • Same max width both ends.
  • Asymmetric widths — verify width_cap is the lesser (P9).
  • A contiguous run of 8, 5, 1 — verify x8, x4, x1 (P4).
  • A gap at lane 1 (0x000D) — verify x1, not x2 or x4.
  • An empty mask — verify width_valid low and no commit.
  • A mask with more good lanes than the width uses — verify width_mask is the subset and the extras are not driven (Chapter 17.1 P14).

Result ownership

  • Commit a coherent result — verify config_valid then link_ready (P6).
  • An incoherent result — illegal width, mask mismatch, missing speed_valid. Verify refusal (P8).
  • A second result_valid while LINK_READY — verify it is ignored (P7, P12).
  • retrain_req — verify invalidation and return to waiting.
  • link_down from every state (P10).
  • link_down and result_valid in the same cycle — verify down wins (P11). Required.

Traffic gate

  • Link down with a valid packet and down_ready high — verify up_ready is low and the packet stays (P14). The required test, and the one §12's counterexample fails.
  • Link becomes ready — verify the waiting packet transfers.
  • Downstream stalls while ready — verify no transfer, packet retained.
  • Link drops during a stall — verify the packet is still offered afterwards (P16).

Mutations

#MutationCaught bySilicon symptom
1up_ready = down_ready (not gated)P14packets silently destroyed when the Link is not ready
2highest local rate chosen, not highest commonP1Link fails to train, or trains and errors immediately
3width exceeds remote capabilityP9lanes driven the partner is not listening on
4lane_good_mask shifted by oneP5wrong lanes driven; partner sees traffic on dead lanes
5configuration changed while link_readyP7, P12width changes under active traffic; corruption
6link_ready asserted before commitP6traffic sent against uncommitted configuration
7packet accepted while Link not readyP13packet lost at the layer boundary
8link_down does not clear config_validP10stack drives a Link that no longer exists
9result committed from transient signalsP8, A1intermittently wrong committed configuration
10x8 committed with only 4 lanes in the maskP8's mask_matcheshalf the lanes carry nothing; partner sees a broken Link
11empty intersection still commitsP3, P8Link comes up at a rate one end cannot do
12width taken as $countones(lane_good_mask)P4"x5" — a width that does not exist
13link_down loses to a same-cycle resultP11configuration committed onto a dead Link
14shift-based highest-bit searchout-of-range shift at an empty maskX-propagation or a wrong rate

15. Hop-Local Training

Each Link trains independently. Training state is not forwarded.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Endpoint ══ Link A ══ Switch ══ Link B ══ Root Complex

Link A and Link B negotiate separately, and can reach entirely different configurations — Gen4 x4 on one and Gen5 x8 on the other — if the capabilities and channels differ.

16. Debugging

Symptom → hypothesis → signals → distinguishing experiment.

Do not call this a bug yet. §6: the result is bounded by mutual capability and the channel, so this may be correct fallback.

Inspect, in order: both ends' advertised capabilities; the negotiated result in the Link Status register (§1); whether the Link is retraining repeatedly; and whether equalization at the higher rate succeeded (Chapter 17.4).

The distinguishing experiment: change the channel — a shorter cable, a different slot, a different riser. If the negotiated speed changes, it was channel-limited fallback and the design is working. If it is identical across channels, it is a capability or logic problem.

Same reasoning, different axis (§7): lanes, not rate.

Inspect: lane_good_mask — which lanes trained; width_mask — which the width uses; and whether the gap pattern explains the run (§11's 0x000D case).

The distinguishing experiment: if lanes 0–3 are good and 4–7 are not, the fault is in a specific physical group and the question is board or connector. If the good mask is 0x00FF and the width is still x4, the fault is in the classifier — and that is mutation 12 or 4.

Do not start at the Transaction Layer. A width problem never originates there.

Traffic-gate ownership (§12's counterexample), or a configuration committed while link_ready was already asserted.

Inspect: whether up_ready is ever high while link_ready is low — that single condition is the bug (P14).

The distinguishing experiment: hold traffic off until link_ready has been stable for many cycles. If packets stop disappearing, it is the gate. If they still disappear with a stable ready Link, it is not this chapter.

The configuration changes while traffic is active

Either an unintended retrain, or a result-ownership bug (P7, P12).

Inspect: retrain_req, link_down, and whether a new result_valid is being committed while LINK_READY — which §12 forbids and mutation 5 introduces.

Only one partner fails, and the same card works elsewhere

Interoperability or channel, not local logic — local logic does not know which partner it is talking to.

Inspect: the advertised capabilities of the failing partner specifically, and whether the failure is at a particular rate.

The distinguishing experiment: swap only the partner, keeping the channel. If it works, the issue is capability or interoperability. If it still fails, the channel is implicated even though the card changed.

17. Common Misconceptions

  • "Connected traces mean the Link is ready." They mean two ports can attempt to train (§4).
  • "Link training is software enumeration." Hardware, one Link. Enumeration is software, whole hierarchy, and requires trained Links to exist first (§2).
  • "Both ends supporting Gen5 guarantees Gen5." Mutual capability is a ceiling (§6).
  • "An x16 connector guarantees x16." The connector provides lane positions; training determines the width (§7).
  • "Equalization and training are the same thing." Equalization is a channel-adaptation mechanism used within training at higher rates — Chapter 17.4.
  • "Link training and LTSSM are interchangeable terms." Training is the process; the LTSSM is the state machine that runs itModule 18 (§3).
  • "TS1 and TS2 are Transaction Layer packets." They are physical-layer ordered sets. No TLP exists until a Link is trained (§4).
  • "A training sequence number relates to DLL sequence numbering." Different layer, different purpose, no relationship (Chapter 14.5).
  • "Traffic can be accepted while the Link is down and just discarded." Accepting means the upstream released it. That is a destroyed packet (§12, P14).
  • "Negotiated width is the popcount of good lanes." It is a legal Link width; five good lanes give x4 (§11, P4).
  • "Speed negotiation is a one-cycle min(local, remote)." The arithmetic is a ceiling; the negotiation is a multi-cycle protocol exchange (§10, Chapter 18.5).
  • "A Switch forwards training end to end." Each Link trains independently; training state is never forwarded (§15).
  • "Training happens once and the configuration is permanent." §1's source lists autonomous width and speed changes (§8).

18. Understanding Check

19. What's Next

Connected traces are not a Link. Training establishes presence, synchronization, lane usability and lane identity, and produces a negotiated width, speed and lane configuration that everything above consumes and none of it controls.

The result is bounded by mutual capability, not equal to it — and the gap between the ceiling and the outcome is channel, equalization and lane viability, which is why a slower-than-expected Link is a question before it is a bug.

And the contract needs a lifecycle. Committed on a coherent result, stable while the Link is ready, invalidated through a defined path — with a traffic gate that gates both directions, because gating only valid destroys the packet it was meant to protect.

Chapter 17.4 — Equalization takes the mechanism this chapter kept pointing at: how a high-rate Link adapts to a real channel, and why it is what stands between mutual capability and the actual negotiated speed.

Chapter 17.5 then covers the electrical foundation underneath all of it, and Module 18 takes the LTSSM — Detect, Polling, Configuration, Recovery and the power states — the state machine that has been orchestrating everything this chapter described from the outside.

The idea to carry forward: advertised capability bounds the outcome; measured reality determines it.