Skip to content

PCIe · Module 18

Detect — Is Anything There?

Before training, before negotiation, before any bit is exchanged, the port must answer one question: is a receiver connected? Detect answers it electrically, in hardware, with no cooperation from the other end.

Chapter 18.1 framed the LTSSM as the machine that decides what the Link is trying to do. This is the first thing it tries.

Everything Module 17 built assumed a partner. Chapter 17.2's aligner needed a stream to align to. Chapter 17.3's negotiation needed someone to negotiate with.

At power-up none of that is known. Traces are routed, a connector may or may not have a card in it, and the port has no idea whether anything is on the other end.

And it cannot find out by transmitting and listening, because a receiver that is not there will not answer, and a receiver that is there is not yet doing anything either.

How does a port establish that a receiver physically exists before any signalling is possible, and what digital state must surround an operation that is fundamentally electrical?

1. The Verified Substates

2. Why This Cannot Be Answered by Transmitting

The obvious approach fails, and understanding why is the chapter.

"Send something and see if anyone replies" requires a partner that is already listening, already locked, and already willing to respond. At this point none of that is true — the far end may itself be in Detect, may be unpowered, or may not exist.

So detection has to work on a port that is doing nothing at all, which rules out every protocol-level mechanism and leaves only the electrical one: does the far end present the characteristics of a terminated receiver?

3. Detect Is Not Enumeration

The first terminology collision Chapter 18.1 §4 warned about, and here it is concrete.

Detect (LTSSM)Device discovery (7.3)
Asksis a receiver electrically present?what Functions exist and how are they addressed?
Performed byhardware, the PHYsoftware
Producesa presence resultVendor/Device IDs, bus numbers, BARs
Needsnothing but electrical accessa fully trained Link
Happensfirstmuch later

Detect learns nothing about identity. It does not know whether the thing on the other end is a network card or a GPU, what its Vendor ID is, or whether it has one Function or eight. It knows that something with a receiver's electrical signature is there.

And the ordering is strict. Configuration accesses are TLPs; TLPs need a trained Link; a trained Link needs Detect to have succeeded. A device whose Detect never succeeds is not a device that enumerates badly — it is electrically invisible, and no amount of software will find it.

4. Detect Is Not Signal Detect

A subtler and more damaging conflation, because both involve "is there something on this lane".

Receiver detectionSignal detect / LOS
Initiated bythe transmitter side, deliberatelycontinuous, receiver side
Asksis a receiver terminated on the far end?is the input above a threshold right now?
Whenduring Detect, as an operationalways, once powered
Answer meansa partner existssomeone is transmitting

5. The Detect Subtree

The Detect subtree. Detect Quiet is the waiting state and leads to Detect Active, where a first receiver detection is performed. If no receiver is present the machine waits an interval and performs a second detection. A not-present result returns to Detect Quiet to wait and retry. A present result leads out of Detect toward Polling.Detect.QuietDetect.Active— firstintervalbetweenattemptsDetect.Active— secondtoPollingbegin detectionbegin detectionreceiver presentreceiver presentnot presentnot presentinterval elapsedinterval elapsedreceiver presentreceiver presentstill not presentstill not present
Figure 1 — the Detect subtree only. Detect.Quiet is the waiting state; Detect.Active performs the detection operation, and the vendor decomposition in §1 shows it as a first attempt, an interval, and a second attempt if needed. A not-present result returns to Quiet rather than advancing. The full LTSSM map, and every other state's internals, belong to Chapter 18.1 and the chapters that own them.

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

There is no path from "not present" to Polling. Every failing route leads back to waiting and trying again (§6). That absence is the most important feature of the diagram.

The interval between attempts is a real element, not padding — §1's source names a substate whose entire job is "Wait 12mS between Rx Detection attempts." Detection is not free and is not instantaneous, and a design that retried continuously would be doing electrical work at a rate nobody specified.

What not to read into it: this is the Detect subtree only. Entry conditions from other states, other exits, and the speed-change substates §1 lists as implementation detail are deliberately absent — Chapter 18.1 §6 owns the full map.

6. Retry, Not Fabricate

The behavioural rule that everything else in the chapter enforces.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
receiver present      → proceed toward Polling
receiver NOT present  → wait, and try again

Never: not present → proceed anyway.

7. Candidate Lanes Are Not a Width

Detection on a multi-lane port produces per-lane results, and it is tempting to read them as the Link's width. They are not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Detect          → which lanes have a receiver present   ← candidates
Polling         → which lanes can carry trained communication
Configuration   → which lanes form one logical Link, and how wide it is

Each stage can reduce the set. A lane with a receiver present may fail to achieve lock in Polling. A set of lanes that all pass Polling may still not form a legal Link width in Configuration (Chapter 18.4 §8).

So §12's mask is named candidate_lane_mask, deliberately, and this chapter makes no claim that it becomes the width. Chapter 17.3 §11 already showed that five usable lanes yield x4 — the reduction from candidates to a committed width is Configuration's, and doing it here would both be wrong and consume Chapter 18.4.

8. A Trace

Internal teaching signals, not PCIe pins. Two attempts, the first finding nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
step             1     2     3     4     5     6     7     8     9
state           QUI   ACT   ACT   WAIT  WAIT  ACT   ACT   ACT   →POL
detect_start     0     1     0     0     0     1     0     0     0
detect_busy      0     1     1     0     0     1     1     1     0
detect_done      0     0     1     0     0     0     0     1     0
rx_present       -     -     0     -     -     -     -     1     -
 
attempt_id       0     1     1     1     1     2     2     2     2
result_valid     0     0     1     0     0     0     0     1     1
candidate_mask  00    00    00    00    00    00    00    0F    0F
 
traffic_enabled  0     0     0     0     0     0     0     0     0

Read steps 2–3. An attempt is issued and completes with rx_present = 0. The result is captured against attempt 1.

Read steps 4–5. The interval. Nothing is being transmitted and no detection is running — this is the wait §1's source names.

Read steps 6–8. A second attempt, this time finding a receiver. attempt_id is 2, so the result is unambiguously attributed (§10).

Read step 8's mask. Four lanes report present. That is a candidate set, not a width (§7).

And note traffic_enabled across the whole trace. It is zero everywhere — normal traffic is not legal in Detect, and Chapter 18.1 §13's gate enforces it.

9. The Boundary

10. RTL — Detection Command and Result Ownership

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Digital wrapper around the PHY's receiver-detection
// operation.
// THAT RECEIVER DETECTION IS A PHY OPERATION WITH A DEFINED NOT-PRESENT
// OUTCOME is vendor-verified (section 1). The attempt-ID mechanism, the
// no-overlap rule and the error reports are ILLUSTRATIVE IMPLEMENTATION
// POLICY. The electrical operation itself is NOT modelled (section 9).
package detect_pkg;
 
  // An attempt identifier, wide enough that a stale result from an
  // abandoned attempt cannot alias the current one within any plausible
  // window. Two bits would wrap in four attempts; this is cheap insurance.
  localparam int ATT_W = 4;
 
  typedef struct packed {
    logic             valid;
    logic [ATT_W-1:0] attempt;
    logic             present;
  } detect_result_t;
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import detect_pkg::*;
 
module detect_attempt_ctrl #(
  parameter int LANES  = 16,
  // GUARDED. $clog2(1) is zero; LANES = 1 is a legal configuration.
  parameter int LANE_W = (LANES <= 1) ? 1 : $clog2(LANES)
) (
  input  logic clk,
  input  logic rst_n,
 
  // ---- From the Detect state logic ---------------------------------------
  input  logic              start_attempt,
  output logic              busy,
 
  // ---- To the PHY macro ---------------------------------------------------
  // A DECOUPLED command. The macro may not be able to accept immediately,
  // and the request must survive that without being reissued.
  output logic              detect_cmd_valid,
  input  logic              detect_cmd_ready,
 
  // ---- From the PHY macro -------------------------------------------------
  input  logic              detect_done,
  input  logic              detect_present,
 
  // ---- Result, attributed to a specific attempt --------------------------
  output detect_result_t    result,
  output logic [ATT_W-1:0]  attempt_id,
 
  output logic              err_start_while_busy,
  output logic              err_unowned_result
);
 
  generate
    if (LANES  < 1) $error("LANES must be at least 1");
    if (LANE_W < 1) $error("LANE_W must be at least 1");
  endgenerate
 
  typedef enum logic [1:0] { S_IDLE, S_ISSUE, S_WAIT, S_REPORT } st_e;
  st_e              st_q;
  logic [ATT_W-1:0] att_q;
  detect_result_t   res_q;
  logic             esb_q, eur_q;
 
  assign busy                 = (st_q != S_IDLE);
  assign attempt_id           = att_q;
  assign result               = res_q;
  assign err_start_while_busy = esb_q;
  assign err_unowned_result   = eur_q;
 
  // ==================================================================
  // THE COMMAND IS A LEVEL, HELD UNTIL THE MACRO ACCEPTS IT.
  //
  // Pulsing it for one cycle loses the request whenever the macro is not
  // ready that cycle -- and the machine then waits forever for a result
  // to an operation that was never started (section 14, mutation 1).
  // ==================================================================
  assign detect_cmd_valid = (st_q == S_ISSUE);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_q  <= S_IDLE;
      att_q <= '0;
      res_q <= '0;
      esb_q <= 1'b0; eur_q <= 1'b0;
    end else begin
      unique case (st_q)
        S_IDLE :
          if (start_attempt) begin
            // A NEW ATTEMPT GETS A NEW IDENTIFIER, allocated here and
            // never again -- so a result can be checked against it.
            att_q <= att_q + ATT_W'(1);
            res_q.valid <= 1'b0;
            st_q  <= S_ISSUE;
          end
 
        // Hold the command until accepted. EXACTLY ONE acceptance per
        // start, because the state leaves S_ISSUE on the handshake.
        S_ISSUE :
          if (detect_cmd_ready) st_q <= S_WAIT;
 
        S_WAIT :
          if (detect_done) begin
            // ==========================================================
            // THE RESULT IS CAPTURED, NOT SAMPLED LATER.
            //
            // detect_present is only meaningful in the cycle detect_done
            // asserts. Reading it afterwards reads whatever the macro has
            // moved on to -- section 14's mutation 8.
            // ==========================================================
            res_q.valid   <= 1'b1;
            res_q.attempt <= att_q;
            res_q.present <= detect_present;
            st_q          <= S_REPORT;
          end
 
        S_REPORT :
          st_q <= S_IDLE;
 
        default : st_q <= S_IDLE;
      endcase
 
      // NO OVERLAPPING ATTEMPTS in this teaching model. A start while busy
      // is a caller error and is REPORTED rather than silently starting a
      // second operation whose result could not be attributed.
      if (start_attempt && busy) esb_q <= 1'b1;
 
      // A result arriving with no attempt outstanding cannot belong to
      // anything. Reported, never captured.
      if (detect_done && (st_q != S_WAIT)) eur_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. A four-state wrapper with an attempt identifier allocated at start and a result captured in the cycle the macro reports done.

The attempt ID is not decoration. It is what makes "this result belongs to that request" checkable — by the design, by P4, and by the scoreboard. Without it, a late result from an abandoned attempt is indistinguishable from a fresh one.

Cycle behaviour.

SituationResult
start_attempt while idlenew attempt ID; command asserted
macro not readycommand held, not reissued
macro acceptsone acceptance, exactly once
detect_done while waitingresult captured with the current attempt ID
detect_done while not waitingreported, never captured
start_attempt while busyreported, no second operation
reset mid-attemptattempt abandoned, result invalid

Failure — five. Pulsing the command loses it when the macro stalls, and the machine then waits forever. Leaving S_ISSUE on something other than the handshake can issue twice (§14). Sampling detect_present after detect_done reads a signal that is no longer meaningful. Omitting the attempt ID makes a stale result indistinguishable from a current one. And allowing overlapping attempts creates results that cannot be attributed at all.

11. RTL — Detect Interval Timer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. The wait between detection attempts.
// THE INTERVAL IS A PARAMETER. Section 1's source states 12 ms for that
// vendor device; this chapter does not publish it as a PCIe constant, and
// a design must take the required interval from the specification.
// Contract identical to Chapter 18.1 section 11's timer primitive.
module detect_interval #(
  parameter int CNT_W = 24
) (
  input  logic             clk,
  input  logic             rst_n,
  input  logic             enable,
  input  logic             restart,
  input  logic [CNT_W-1:0] limit,          // interval, in local cycles
 
  output logic [CNT_W-1:0] count,
  output logic             elapsed
);
 
  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 DISABLES, never expires instantly (Chapter 18.1 section 11).
  assign elapsed = enable && (limit != '0) && (cnt_q >= limit);
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)               cnt_q <= '0;
    else if (restart)         cnt_q <= '0;          // restart beats counting
    else if (enable && !elapsed) cnt_q <= cnt_q + CNT_W'(1);   // saturates
  end
 
endmodule

Classification: synthesizable.

Same three declared contracts as Chapter 18.1 §11limit == 0 disables, restart beats counting, the counter saturates — reused deliberately rather than reimplemented with different semantics. A design with two timers that disagree about what limit = 0 means has a bug waiting in whichever one is configured last.

12. RTL — Candidate Lane Mask

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import detect_pkg::*;
 
// SYNTHESIZABLE. Accumulate per-lane detection results into a candidate
// mask.
// THIS IS CANDIDATE PRESENCE, NOT NEGOTIATED WIDTH (section 7). Chapter
// 18.4 owns the reduction from candidates to a committed configuration.
module detect_lane_mask #(
  parameter int LANES  = 16,
  parameter int LANE_W = (LANES <= 1) ? 1 : $clog2(LANES)
) (
  input  logic clk,
  input  logic rst_n,
 
  // A new attempt invalidates everything the previous one found.
  input  logic              attempt_begin,
 
  input  logic              lane_result_valid,
  input  logic [LANE_W-1:0] lane_id,
  input  logic              lane_present,
 
  output logic [LANES-1:0]  candidate_lane_mask,
  output logic              lane_range_error
);
 
  generate
    if (LANES  < 1) $error("LANES must be at least 1");
    if (LANE_W < 1) $error("LANE_W must be at least 1");
    if ((LANES > 1) && ((1 << LANE_W) < LANES))
      $error("LANE_W too narrow to index LANES");
  endgenerate
 
  // RANGE SAFETY on an externally-supplied lane id, checked one bit wider
  // BEFORE any indexing. A LANE_W-wide value can name a lane that does not
  // exist whenever LANES is not a power of two.
  localparam int CHK = LANE_W + 1;
  wire lane_legal = (CHK'(lane_id) < CHK'(LANES));
 
  logic [LANES-1:0] mask_q;
  logic             err_q;
 
  assign candidate_lane_mask = mask_q;
  assign lane_range_error    = err_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      mask_q <= '0; err_q <= 1'b0;
    end else begin
      // ==============================================================
      // A NEW ATTEMPT CLEARS THE MASK, and this ordering is deliberate:
      // checked FIRST, so a result arriving in the same cycle as a new
      // attempt cannot be counted against the old one. Accumulating
      // across attempts would report lanes that were present once, ever
      // (section 14, mutation 6).
      // ==============================================================
      if (attempt_begin) begin
        mask_q <= '0;
      end else if (lane_result_valid && lane_legal) begin
        // ONE BIT, THE NAMED ONE. Assigning the whole vector here is
        // mutation 12, and it makes every lane share one lane's answer.
        mask_q[lane_id] <= lane_present;
      end
 
      if (lane_result_valid && !lane_legal) err_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. A bit per lane, cleared on a new attempt, written one bit at a time by a range-checked index.

The clear-on-new-attempt ordering is the interesting part. Checked before the result arm, so a lane result coinciding with a new attempt is attributed to the new attempt's (empty) mask rather than the old one's. Accumulating across attempts would produce a mask meaning "lanes that were ever present", which is not a question anybody asked.

Failure — four. Accumulating across attempts reports stale presence. Indexing before the range check sets a bit for a lane that does not exist, or aliases lane 0. Assigning the whole mask instead of one bit gives every lane one lane's result. And treating this mask as the width is §7's error, and belongs to Chapter 18.4.

13. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over detect_attempt_ctrl, detect_interval and detect_lane_mask.
// These assert the ATTEMPT-OWNERSHIP contract. They assert NOTHING about
// the electrical detection operation (section 9), nothing about Polling
// (Chapter 18.3), and nothing about a receiver eventually existing --
// which depends on whether a card is plugged in.
 
// ---- ENVIRONMENT ------------------------------------------------------
// A1: detect_present is meaningful only in the cycle detect_done asserts.
assume property (@(posedge clk) disable iff (!rst_n)
  detect_done |-> !$isunknown(detect_present));
// A2: the macro asserts detect_done only for an operation it accepted.
assume property (@(posedge clk) disable iff (!rst_n)
  detect_done |-> $past(detect_cmd_valid && detect_cmd_ready, 1, 1, 64));
 
// ---- COMMAND OWNERSHIP ------------------------------------------------
 
// P1: THE COMMAND IS STABLE UNDER STALL. It is a held level, not a pulse.
property p_cmd_held;
  @(posedge clk) disable iff (!rst_n)
  (detect_cmd_valid && !detect_cmd_ready) |=> detect_cmd_valid;
endproperty
a_held : assert property (p_cmd_held);
 
// P2: ONE START, AT MOST ONE COMMAND ACCEPTANCE. The chapter's central
// ownership property -- section 14's counterexample fails it.
// (g_starts and g_cmds are testbench counters.)
property p_one_command_per_start;
  @(posedge clk) disable iff (!rst_n)
  (g_cmds <= g_starts);
endproperty
a_once : assert property (p_one_command_per_start);
 
// P2b: and the command drops immediately on acceptance -- it cannot be
// accepted twice in consecutive cycles.
property p_cmd_drops_on_accept;
  @(posedge clk) disable iff (!rst_n)
  (detect_cmd_valid && detect_cmd_ready) |=> !detect_cmd_valid;
endproperty
a_drop : assert property (p_cmd_drops_on_accept);
 
// ---- RESULT OWNERSHIP -------------------------------------------------
 
// P3: a result is captured only while an attempt is outstanding.
property p_result_needs_attempt;
  @(posedge clk) disable iff (!rst_n)
  $rose(result.valid) |-> ($past(st_q) == S_WAIT);
endproperty
a_owned : assert property (p_result_needs_attempt);
 
// P4: THE RESULT NAMES THE ATTEMPT THAT PRODUCED IT. Without this, a late
// result from an abandoned attempt is indistinguishable from a fresh one.
property p_result_attributed;
  @(posedge clk) disable iff (!rst_n)
  $rose(result.valid) |-> (result.attempt == attempt_id);
endproperty
a_attributed : assert property (p_result_attributed);
 
// P5: the captured result is STABLE -- present is not re-sampled later.
property p_result_stable;
  @(posedge clk) disable iff (!rst_n)
  (result.valid && !start_attempt) |=> $stable(result.present);
endproperty
a_stable : assert property (p_result_stable);
 
// P6: A NOT-PRESENT RESULT CANNOT REPORT PRESENCE. The property that
// forbids fabricating a partner (section 6).
property p_absent_is_absent;
  @(posedge clk) disable iff (!rst_n)
  ($rose(result.valid) && !$past(detect_present)) |-> !result.present;
endproperty
a_absent : assert property (p_absent_is_absent);
 
// P7: an unowned result is REPORTED and never captured.
property p_unowned_reported;
  @(posedge clk) disable iff (!rst_n)
  (detect_done && (st_q != S_WAIT)) |=> (err_unowned_result && !result.valid);
endproperty
a_unowned : assert property (p_unowned_reported);
 
// P8: NO OVERLAPPING ATTEMPTS -- a start while busy is refused and
// reported, not silently started.
property p_no_overlap;
  @(posedge clk) disable iff (!rst_n)
  (start_attempt && busy) |=> (err_start_while_busy && $stable(attempt_id));
endproperty
a_overlap : assert property (p_no_overlap);
 
// ---- LANE MASK --------------------------------------------------------
 
// P9: the mask changes only on a valid, in-range lane result or a new
// attempt.
property p_mask_change_owned;
  @(posedge clk) disable iff (!rst_n)
  (!$stable(candidate_lane_mask))
    |-> $past(attempt_begin || (lane_result_valid && lane_legal));
endproperty
a_mask : assert property (p_mask_change_owned);
 
// P10: a result writes ONE lane's bit -- never the whole vector.
property p_one_lane_bit;
  @(posedge clk) disable iff (!rst_n)
  ($past(lane_result_valid && lane_legal && !attempt_begin))
    |-> ($countones(candidate_lane_mask ^ $past(candidate_lane_mask)) <= 1);
endproperty
a_one_bit : assert property (p_one_lane_bit);
 
// P11: an out-of-range lane id touches nothing and is reported.
property p_lane_range;
  @(posedge clk) disable iff (!rst_n)
  (lane_result_valid && !lane_legal)
    |=> (lane_range_error && $stable(candidate_lane_mask));
endproperty
a_range : assert property (p_lane_range);
 
// P12: A NEW ATTEMPT CLEARS THE MASK -- no accumulation across attempts.
property p_mask_cleared;
  @(posedge clk) disable iff (!rst_n)
  attempt_begin |=> (candidate_lane_mask == '0);
endproperty
a_clear : assert property (p_mask_cleared);
 
// ---- TIMER ------------------------------------------------------------
 
// P13: the interval timer's three declared contracts (Chapter 18.1 §11).
property p_timer_restart;
  @(posedge clk) disable iff (!rst_n) restart |=> (count == '0);
endproperty
a_restart : assert property (p_timer_restart);
 
property p_timer_zero_disables;
  @(posedge clk) disable iff (!rst_n) (limit == '0) |-> !elapsed;
endproperty
a_zero : assert property (p_timer_zero_disables);
 
// ---- SCOPE ------------------------------------------------------------
 
// P14: NO NORMAL TRAFFIC IN DETECT. Bound against Chapter 18.1's gate.
property p_no_traffic_in_detect;
  @(posedge clk) disable iff (!rst_n)
  (dut_ltssm.cur_state == ST_DETECT) |-> !dut_gate.traffic_enabled;
endproperty
a_no_traffic : assert property (p_no_traffic_in_detect);
 
// P15: reset abandons any pending attempt and clears the result.
property p_reset;
  @(posedge clk)
  !rst_n |=> (!busy && !result.valid && (candidate_lane_mask == '0));
endproperty
a_reset : assert property (p_reset);

P2 and P2b are the command-ownership pair. P2 is the counting statement — acceptances never exceed starts — and P2b is the structural one that makes it true: the command drops on the handshake. §14's counterexample fails both.

P3 and P4 are the result-ownership pair. P3 says a result requires an outstanding attempt; P4 says the result names it. A design can satisfy P3 and still misattribute if attempts can overlap, which is why P8 forbids that in this model.

P6 is short and is the most important property in the chapter. A not-present result cannot report presence. Everything else is bookkeeping; this one is the rule that a Link is never fabricated (§6).

No liveness is asserted. "A receiver eventually appears" depends on whether a card is plugged in.

14. Verification and Fault Injection

The scoreboard tracks attempts and results independently — counting starts, command acceptances and results, and maintaining its own expected mask — and never reads st_q, att_q or mask_q.

Command and result

  • Receiver present, first attempt — verify one command, one result, present high.
  • Receiver absent — verify one result with present low and no advance (P6).
  • Macro command stalled for many cycles — verify the command is held, and accepted exactly once (P1, P2b). Required.
  • detect_done in the earliest possible cycle after acceptance.
  • detect_done with no attempt outstanding — verify reported, not captured (P7).
  • start_attempt while busy — verify reported, no second attempt (P8). Required.
  • Repeated absent attempts — verify each gets a distinct attempt ID.
  • Reset mid-attempt — verify nothing is reported afterwards (P15).

Lane mask

  • LANES = 1, 2, 4, 8, 16 — including the $clog2(1) corner. Required.
  • All lanes present, then some absent — verify per-lane accuracy.
  • A new attempt after a partial mask — verify it clears (P12). Required.
  • An out-of-range lane id — verify nothing touched and reported (P11).
  • A lane result coinciding with attempt_begin — verify it is attributed to the new attempt.

Timer

  • limit = 0 — verify disabled, not instant.
  • restart and a tick in the same cycle — verify restart wins.

Mutations

#MutationCaught byLab symptom
1command pulsed for one cycleP1detection never starts if the macro stalls; stuck in Detect forever
2same attempt issued twiceP2, P2btwo electrical operations per request; wasted time, confused results
3result attributed to the wrong attemptP4a stale "absent" answer blocks a partner that is present
4absent interpreted as presentP6proceeds to Polling with no partner; fails looking like a training bug
5stale result survives resetP15first attempt after reset returns the previous run's answer
6mask accumulates across attemptsP12lanes that were once present reported forever
7out-of-range lane id aliases lane 0P11a phantom lane 0 candidate
8detect_present sampled after doneP5intermittently wrong presence, timing-dependent
9normal traffic enabled in DetectP14packets pushed into a Link with no partner
10not-present path advances to PollingP6, and 18.3's entry conditionPolling failure reported for an empty slot
11command retriggered while busyP8overlapping operations; unattributable results
12one lane result writes the whole maskP10every lane reports one lane's answer

15. Debugging

Symptom → state → signal → distinguishing experiment.

The LTSSM never leaves Detect

This is Detect doing its job, and the question is which half is failing.

  1. Is detect_cmd_valid ever asserted? If not, the state logic is not requesting detection at all.
  2. Is it ever accepted? A command asserted and never accepted means the macro is not ready — a PHY configuration or power-state problem, not a Link problem.
  3. Does detect_done arrive? If a command was accepted and no result comes back, the operation is not completing.
  4. What does rx_present say? If it says absent, believe it — the answer is physical.

At step 4 the investigation leaves the digital domain entirely: card seating, slot power, connector contact, lane routing, and the far end's own power state. Nothing in the packet layers is relevant, and no amount of driver work will change the answer.

An endpoint works in one slot and not another

A physical difference between two paths, and Detect is the earliest place it shows.

Inspect the per-lane candidate mask in both slots. If the same card produces different masks, the difference is the slot, the riser, or the routing to it.

The distinguishing experiment: move a known-good card into the failing slot. If it also fails to detect, the slot is implicated; if it detects, the original card's transmitter or termination is.

An x8 card shows fewer candidate lanes than expected

A per-lane physical problem — and the mask says exactly which lanes.

Inspect which bits are clear, and whether they are contiguous. Lanes 4–7 all absent suggests a connector half or a routing group; a single scattered lane suggests one trace or one pin.

And do not call the mask a width. §7: this is candidate presence. A reduced mask does not yet mean a reduced LinkChapter 18.4 decides that, and it may reduce further.

Detection is intermittent

Suspect the digital wrapper before the physical path (§14's counterexample).

Check whether more command acceptances occur than starts — that single count comparison distinguishes a double-issue bug from a marginal electrical connection, and it is cheap to instrument.

16. Common Misconceptions

  • "Detect discovers the Device and Vendor ID." It learns nothing about identity. Discovery is software, much later, over a trained Link (§3).
  • "Detect means signal detect." Receiver detection is a transmitter-initiated operation; signal detect is a continuous receiver-side threshold (§4).
  • "A routed lane means a receiver is present." Routing is a board fact; presence is an electrical measurement (§2).
  • "CDR lock is needed before receiver detection." The opposite order — detection precedes any signalling, and there is nothing to lock to yet (§4).
  • "Detect is software-controlled." Hardware, at Link timescales (Chapter 18.1 §3).
  • "A lane with no receiver can still be trained." It cannot, and attempting it wastes the whole training sequence (§6).
  • "Detect determines the final x8 or x4 width." It produces candidates; Chapter 18.4 commits a width (§7).
  • "The detection operation can be implemented in ordinary RTL." It is analog; the RTL is the wrapper (§9).
  • "receiver_present can be sampled whenever convenient." It is meaningful in the cycle done asserts, and belongs to a specific attempt (§10, P5).
  • "With no partner, proceeding to Polling is harmless." It converts a clear diagnostic into a misleading one, and transmits into an unterminated line (§6).
  • "Two detection attempts mean something is wrong." §1's source shows a first attempt, an interval, and a second if needed — retry is the design.

17. Understanding Check

18. What's Next

Detect answers the first question any Link must answer, and it answers it electrically: a receiver terminates the line, an empty trace does not, and the PHY can tell the difference before any signalling exists.

The digital contribution is ownership. One request, one operation; a result that names its attempt; a candidate mask that clears when a new attempt begins; and — most importantly — no path from "nobody there" to "proceed anyway."

Chapter 18.3 — Polling takes the next question, and it is a much harder one. A receiver being present says nothing about whether anything can be communicated over the lane. Timing must be recovered, boundaries found, and training information recognised repeatedly enough to be trusted — which is the step where signal quality stops being theoretical.

The idea to carry forward: a state machine's diagnostic value comes from stopping exactly where the truth is.