PCIe · Module 18
Polling — Can We Read Each Other?
Detect proved somebody is there. Polling proves the two ports can actually communicate — bit lock, alignment, and consecutively recognised training sets, with no acknowledgement mechanism available to confirm any of it.
Chapter 18.2 established that somebody is there. That is a much weaker statement than it sounds.
A receiver can be electrically present and the lane still unusable: the channel may be too lossy to sample reliably (Chapter 17.5), the receiver may not find where units begin (Chapter 17.2 §3), or the far end may be transmitting something this port cannot recognise.
Polling is where those possibilities are eliminated. It is the first stage in which the two ports actually communicate — and the first in which signal quality stops being theoretical.
How do two ports get from "a receiver exists" to "we can reliably interpret each other's training stream", and why is that two independent problems rather than one?
1. The Verified Substates
2. Present Is Not Communicating
The gap between Detect's answer and Polling's requirement.
| Detect established | Polling must establish | |
|---|---|---|
| Question | is a receiver terminated there? | can I read what it sends? |
| Evidence | an electrical measurement | recovered, aligned, recognised data |
| Depends on | connectivity | signal quality, equalization, the channel |
| Fails because | no card, no power, bad contact | loss, jitter, ISI, marginal margin |
3. Lock and Alignment Are Generation-Specific
The registry names this chapter's purpose as bit-lock and symbol-lock, and that phrasing is exactly right for one generation family and wrong for another.
| Generation | Encoding | What "aligned" means |
|---|---|---|
| Gen1 / Gen2 | 8b/10b (5.1, 5.2) | symbol boundary within the code stream |
| Gen3 – Gen5 | 128b/130b (5.3) | block boundary and its framing |
| Gen6 | changed again (5.6) | changed again |
Two things are common to all of them, and this chapter uses the general terms.
Timing recovery — the CDR extracts a sampling relationship from the stream's own transitions (Chapter 17.2 §2). This is the same problem in every generation.
Boundary recovery — deciding where a meaningful unit starts, by searching and confirming (Chapter 17.2 §§3–5). The mechanism differs; the necessity does not.
So this chapter says "lock" and "alignment" rather than committing to symbol or block language, and marks the generation where the distinction matters. "Polling equals comma alignment" is true for Gen1 and Gen2 and false from Gen3 onward — §17's misconception list, and a real source of confusion when a design that works at the base rate fails at a higher one for reasons that have nothing to do with commas.
4. Two Problems, Two Failures
The chapter's central separation, and the one that pays off in the lab.
PHY lock and alignment → can I sample and frame the incoming stream?
Training recognition → does the framed stream contain valid training information?5. What Training Ordered Sets Accomplish
TS1 and TS2 are the mechanism by which two ports communicate before any packet layer exists.
What they are for, at the level this chapter owns:
| Purpose | Why it needs a dedicated mechanism |
|---|---|
| be recognisable | a receiver searching for alignment needs something with known structure to find |
| carry training context | link and lane identity, and control information the partner must agree on |
| be repeated | confidence comes from repetition, because nothing can be acknowledged (§7) |
| signal intent | control flags let a port indicate what it is trying to do |
TS1 and TS2 are distinct, and the distinction carries progress: a port transmits one kind while it is still establishing something, and the other once it is satisfied — so seeing the second kind from a partner is itself information about the partner's state.
6. Training Ordered Sets Are Not Packets
A recurring misconception with real consequences, worth stating flatly.
| TS1 / TS2 | TLP | DLLP | |
|---|---|---|---|
| Layer | Physical | Transaction | Data Link |
| Scope | two adjacent ports | end to end | one Link |
| Routed by a Switch | no | yes | no |
| Acknowledged | no | via the Link's ACK | — |
| Carries a Requester ID | no | yes | no |
| Exists before a trained Link | yes — that is the point | no | no |
Every mechanism this curriculum built for reliability assumes a working Link. Sequence numbers (Chapter 14.5), ACK and NAK (Chapter 14.2), replay (Chapter 14.4) — none of it exists yet. Training ordered sets are what two ports have instead.
And a Switch does not forward them. Chapter 17.3 §15: each Link trains independently, and training state is never propagated. A Switch's downstream port trains against the Endpoint while its upstream port trains against the Root Complex, as two separate negotiations.
7. Confidence by Repetition
Polling does not progress because one observation looked correct.
Why not: with no reliable channel and no acknowledgement, a single correct-looking pattern could be a coincidence — random data that happened to match, or a momentarily-correct alignment on a marginal lane. Repetition is the only evidence available.
qualifying observation → count up
disqualifying observation → count resets
count reaches threshold → this is trustworthy8. The Polling Subtree
Three things to read, and one not to.
Polling.Active is where the work happens — §1's source describes it as the substate in which training ordered sets are transmitted, and it is where lock, alignment and qualification all occur.
Polling.Compliance is a branch, not a step. §1's source: "Transmitting compliance pattern." It exists for compliance testing rather than for establishing a working Link, which is why it is drawn to the side and gets one paragraph rather than a section.
Failure returns toward Detect, not onward. Same principle as Chapter 18.2 §6: a state machine that cannot establish what it needs goes back rather than proceeding on unfounded assumptions.
What not to read into it: the transition criteria are absent, deliberately (§1). This is a topology of the Polling subtree — entry conditions, counts and timeouts are not published here.
9. A Trace
Internal teaching signals, not PCIe pins. Illustrative threshold of 4.
step 1 2 3 4 5 6 7 8 9 10
cdr_lock 0 1 1 1 1 1 1 1 1 1
rx_aligned 0 0 1 1 1 1 1 1 1 1
obs_valid 0 0 1 1 1 0 1 1 1 1
obs_kind - - TS1 TS1 BAD - TS1 TS1 TS1 TS1
qual_count 0 0 1 2 0 0 1 2 3 4
threshold_met 0 0 0 0 0 0 0 0 0 1
polling_done 0 0 0 0 0 0 0 0 0 1
traffic_enabled 0 0 0 0 0 0 0 0 0 0Read steps 1–3. Lock first, then alignment. These are separate signals because they are separate problems (§4) — and a design that had only one of them could not distinguish the two failures.
Read steps 3–5 — the reset. Two qualifying observations, then a bad one. The count returns to zero, not to one. Consecutive is the requirement (§7), and this is what marginal signal quality looks like: recognition that starts and does not continue.
Read steps 7–10. Four consecutive qualifying observations. threshold_met asserts on the fourth, not the third or the fifth — §15 verified that boundary exhaustively, and §15's counterexample is what happens when it is off by one.
And note traffic_enabled throughout. Zero. Normal traffic is not legal in Polling, and Chapter 18.1 §13's gate enforces it.
10. RTL — Ordered-Set Classifier and Observation Capture
// SYNTHESIZABLE. Normalized training-observation representation.
// THE KINDS are canonical (section 5: TS1 and TS2 are distinct ordered
// sets carrying training context). THE FIELD WIDTHS here are INTERNAL
// NORMALIZED widths, not verified normative field widths -- section 1
// declines to publish TS layouts, and Chapter 18.4 goes deeper on the
// link and lane fields.
package polling_pkg;
typedef enum logic [2:0] {
OS_NONE = 3'd0,
OS_TS1 = 3'd1,
OS_TS2 = 3'd2,
OS_OTHER = 3'd3, // a training set this port does not qualify on
OS_INVALID = 3'd4 // structurally malformed
} os_kind_e;
// INTERNAL normalized widths. A design with verified normative widths
// substitutes them here and nowhere else.
localparam int LINK_W = 9;
localparam int LANE_W = 5;
localparam int RATE_W = 4;
localparam int CTRL_W = 8;
typedef struct packed {
logic valid;
os_kind_e kind;
logic [LINK_W-1:0] link_num;
logic [LANE_W-1:0] lane_num;
logic [RATE_W-1:0] rate;
logic [CTRL_W-1:0] ctrl;
} training_obs_t;
// ONLY TS1 AND TS2 QUALIFY. An unrecognised or malformed training set
// must never count toward progress (section 12, P5).
function automatic bit obs_qualifies(input training_obs_t o);
return o.valid && ((o.kind == OS_TS1) || (o.kind == OS_TS2));
endfunction
function automatic bit obs_disqualifies(input training_obs_t o);
return o.valid && ((o.kind == OS_OTHER) || (o.kind == OS_INVALID));
endfunction
endpackageimport polling_pkg::*;
// SYNTHESIZABLE. Capture one training observation whole, and hold it while
// the consumer is busy.
// THE PARSER CANNOT BE BACKPRESSURED -- observations arrive from a
// continuously running receive path (Chapter 17.2 section 9). So an
// observation that cannot be held is REPORTED, never silently dropped.
module training_obs_capture (
input logic clk,
input logic rst_n,
// ---- From the normalized ordered-set parser. NO `ready`. --------------
input logic parse_valid,
input os_kind_e parse_kind,
input logic [LINK_W-1:0] parse_link,
input logic [LANE_W-1:0] parse_lane,
input logic [RATE_W-1:0] parse_rate,
input logic [CTRL_W-1:0] parse_ctrl,
// ---- Entering Polling clears any observation from an earlier state ----
input logic state_entry,
// ---- To the progress controller ----------------------------------------
output training_obs_t obs,
input logic obs_ready,
output logic obs_overflow
);
training_obs_t o_q;
logic ovf_q;
assign obs = o_q;
assign obs_overflow = ovf_q;
wire fire = o_q.valid && obs_ready;
wire can_take = !o_q.valid || obs_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
o_q <= '0; ovf_q <= 1'b0;
end else if (state_entry) begin
// ==============================================================
// ENTERING POLLING DISCARDS ANY PENDING OBSERVATION.
//
// Checked FIRST, so an observation parsed just before entry cannot
// count toward this state's qualification. A stale ordered set from
// a previous state is exactly the kind of evidence that must not
// contribute (section 15, mutation 6).
// ==============================================================
o_q <= '0;
end else begin
if (parse_valid && can_take) begin
// THE WHOLE DESCRIPTOR AT ONCE. Nothing downstream re-reads the
// parser -- an observation's fields belong to that observation.
o_q.valid <= 1'b1;
o_q.kind <= parse_kind;
o_q.link_num <= parse_link;
o_q.lane_num <= parse_lane;
o_q.rate <= parse_rate;
o_q.ctrl <= parse_ctrl;
end else if (fire) begin
o_q.valid <= 1'b0;
end
// An observation that could not be held is REPORTED. The receive
// path did not stop; pretending it did would hide lost evidence.
if (parse_valid && !can_take) ovf_q <= 1'b1;
end
end
endmoduleClassification: synthesizable.
Architecture. A one-entry holding stage capturing the whole observation, with entry clearing checked first.
The state-entry clear is the subtle part. Without it, an ordered set parsed in the cycle before Polling was entered could count toward Polling's qualification — evidence gathered in a state whose conditions were different. Checking it before the capture arm makes that unconstructable rather than a matter of timing.
Failure — four. Re-reading parser outputs at consume time attaches the wrong fields to an observation. A ready on the parser side models an interface the receive path does not have. Dropping silently on overflow hides lost evidence. And omitting the entry clear lets a pre-entry observation contribute.
11. RTL — Consecutive Qualifier Counter
import polling_pkg::*;
// SYNTHESIZABLE. Count CONSECUTIVE qualifying observations.
// THAT PROGRESS REQUIRES REPEATED RECOGNITION is the mechanism (section
// 7). THE THRESHOLD IS A PARAMETER and is ILLUSTRATIVE -- section 1 could
// not source the normative counts, and this chapter publishes none.
module obs_qualifier #(
parameter int THRESH = 4,
// GUARDED. $clog2 of a small threshold, never zero-width.
parameter int CNT_W = (THRESH <= 1) ? 1 : $clog2(THRESH + 1)
) (
input logic clk,
input logic rst_n,
input logic restart, // state entry, or a lost precondition
input logic qualifying,
input logic disqualifying,
output logic [CNT_W-1:0] count,
output logic threshold_met
);
generate
if (THRESH < 1) $error("THRESH must be at least 1");
if (CNT_W < 1) $error("CNT_W must be at least 1");
if ((1 << CNT_W) < (THRESH + 1))
$error("CNT_W too narrow to hold THRESH");
endgenerate
logic [CNT_W-1:0] cnt_q;
assign count = cnt_q;
// ==================================================================
// THE COMPARISON THAT MUST BE EXACTLY RIGHT.
//
// >= THRESH CORRECT -- asserts on the THRESH-th observation
// >= THRESH-1 ONE EARLY -- and INVISIBLE at THRESH == 1, which
// is why section 15 requires THRESH >= 2
// > THRESH ONE LATE -- never asserts if the counter saturates
//
// Section 15 verified this exhaustively against an independent model.
// ==================================================================
assign threshold_met = (cnt_q >= CNT_W'(THRESH));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_q <= '0;
end else if (restart) begin
// Restart has priority: entering the state, or losing a
// precondition, invalidates the history regardless of this cycle's
// observation.
cnt_q <= '0;
end else if (disqualifying) begin
// CONSECUTIVE, NOT CUMULATIVE. A bad observation resets to ZERO,
// not to one. Counting up only would let scattered good
// observations across a mostly-bad stream reach the threshold --
// qualifying a lane that is not working (section 7).
cnt_q <= '0;
end else if (qualifying) begin
// SATURATE. A wrapping counter would drop below the threshold after
// reaching it and de-assert threshold_met while the stream is fine.
if (cnt_q < CNT_W'(THRESH)) cnt_q <= cnt_q + CNT_W'(1);
end
end
endmoduleClassification: synthesizable.
Architecture. One saturating counter, with restart before disqualify before qualify — a declared priority, not an accident of if ordering.
Verified exhaustively (§15): against an independent reference — "the last THRESH observations were all qualifying" — across every good/bad sequence up to length 10, for thresholds 1 through 4. Zero mismatches. And the boundary checks hold for thresholds 1 through 8: all-good asserts on exactly the THRESH-th observation, and THRESH − 1 observations never assert.
Failure — five. >= THRESH-1 asserts one observation early — and is invisible at THRESH = 1, which is why §15 requires testing at 2 or more. > THRESH never asserts once the counter saturates. Counting up without resetting on a bad observation qualifies a marginal lane. Resetting to one instead of zero on a bad observation still over-counts. And wrapping de-asserts a threshold that had been met.
12. RTL — Polling Progress Controller
import polling_pkg::*;
// SYNTHESIZABLE. Progress within Polling only.
// THIS IS NOT THE LTSSM and is not Polling's normative substate encoding
// (Chapter 18.1 section 9). It models the digital progression: wait for
// lock and alignment, qualify observations, report done or failed.
// The timeout limit is a PARAMETER -- no PCIe constant appears here.
module polling_progress #(
parameter int THRESH = 4,
parameter int CNT_W = 24
) (
input logic clk,
input logic rst_n,
input logic state_entry, // entering Polling
// ---- Preconditions, from the receive path -----------------------------
// TWO SEPARATE SIGNALS, deliberately (section 4). Collapsing them into
// one ready bit destroys the ability to say which failed.
input logic cdr_lock,
input logic rx_aligned,
// ---- Observations ------------------------------------------------------
input training_obs_t obs,
output logic obs_ready,
// ---- Timeout -----------------------------------------------------------
input logic [CNT_W-1:0] timeout_limit,
input logic timeout_elapsed,
// ---- Results ------------------------------------------------------------
output logic polling_done,
output logic polling_fail,
output logic waiting_for_lock,
output logic qualifying_now
);
typedef enum logic [1:0] { S_WAIT_LOCK, S_QUALIFY, S_DONE, S_FAIL } st_e;
st_e st_q;
wire lock_ok = cdr_lock && rx_aligned;
assign waiting_for_lock = (st_q == S_WAIT_LOCK);
assign qualifying_now = (st_q == S_QUALIFY);
assign polling_done = (st_q == S_DONE);
assign polling_fail = (st_q == S_FAIL);
// Observations are consumed only while qualifying. In any other state
// they are not evidence, and accepting them would let a stale stream
// contribute to a qualification that has not started.
assign obs_ready = (st_q == S_QUALIFY);
wire qualifying = (st_q == S_QUALIFY) && obs_qualifies(obs);
wire disqualifying = (st_q == S_QUALIFY) && obs_disqualifies(obs);
logic threshold_met;
logic [$clog2(THRESH+1)-1:0] qcount;
obs_qualifier #(.THRESH(THRESH)) u_q (
.clk, .rst_n,
// LOSING LOCK INVALIDATES THE HISTORY. Observations qualified under a
// lock that has since been lost are not evidence about the stream now.
.restart (state_entry || !lock_ok),
.qualifying, .disqualifying,
.count (qcount),
.threshold_met(threshold_met)
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
st_q <= S_WAIT_LOCK;
end else if (state_entry) begin
// Entry has highest priority: a fresh attempt starts from scratch
// regardless of anything else happening this cycle.
st_q <= S_WAIT_LOCK;
end else begin
unique case (st_q)
S_WAIT_LOCK :
if (lock_ok) st_q <= S_QUALIFY;
else if (timeout_elapsed) st_q <= S_FAIL;
S_QUALIFY :
// ==========================================================
// SUCCESS BEATS TIMEOUT ON THE SAME CYCLE, declared.
//
// A threshold met in the same cycle a timeout elapses is a
// successful qualification -- the evidence exists. Failing
// instead would discard a completed qualification because a
// counter happened to reach its limit simultaneously
// (section 15, mutation 9).
// ==========================================================
if (threshold_met) st_q <= S_DONE;
else if (!lock_ok) st_q <= S_WAIT_LOCK;
else if (timeout_elapsed) st_q <= S_FAIL;
// Terminal. done and fail are STATES, so they cannot both assert.
S_DONE, S_FAIL : ;
default : st_q <= S_WAIT_LOCK;
endcase
end
end
endmoduleClassification: synthesizable (progress within Polling only — not the LTSSM).
Architecture. Four states, with lock and alignment kept as separate inputs and qualification history invalidated when lock is lost.
Three declared priorities, each with a worse alternative:
| Priority | The alternative, and why it is worse |
|---|---|
| entry beats everything | a fresh attempt inheriting old history qualifies on stale evidence |
| losing lock restarts qualification | observations counted under a lock that no longer holds |
| success beats same-cycle timeout | a completed qualification discarded because a counter coincided |
Failure — four. Merging lock and alignment into one input loses §4's diagnostic. Not restarting on lock loss accumulates evidence across an interruption. Accepting observations outside S_QUALIFY lets a stale stream contribute. And done and fail as flags rather than states permits both.
13. Assertions
// SVA over training_obs_capture, obs_qualifier and polling_progress.
// These assert the LOCAL progression contract. They assert NOTHING about
// the partner transmitting TS, about lock being achievable, or about
// Polling succeeding -- all of which depend on a partner and a channel
// this design does not control.
// ---- ENVIRONMENT ------------------------------------------------------
// A1: the parser's outputs are stable while parse_valid.
assume property (@(posedge clk) disable iff (!rst_n)
parse_valid |-> !$isunknown({parse_kind, parse_lane, parse_link}));
// A2: state_entry is a single-cycle pulse.
assume property (@(posedge clk) disable iff (!rst_n)
state_entry |=> !state_entry);
// ---- OBSERVATION OWNERSHIP --------------------------------------------
// P1: an observation's fields are stable while it waits.
property p_obs_stable;
@(posedge clk) disable iff (!rst_n)
(obs.valid && !obs_ready && !state_entry)
|=> (obs.valid && $stable(obs.kind) && $stable(obs.lane_num)
&& $stable(obs.link_num));
endproperty
a_stable : assert property (p_obs_stable);
// P2: STATE ENTRY DISCARDS a pending observation -- evidence from before
// this attempt must not contribute.
property p_entry_clears;
@(posedge clk) disable iff (!rst_n)
state_entry |=> !obs.valid;
endproperty
a_entry : assert property (p_entry_clears);
// P3: an observation that could not be held is REPORTED, never silent.
property p_overflow_reported;
@(posedge clk) disable iff (!rst_n)
(parse_valid && obs.valid && !obs_ready && !state_entry) |=> obs_overflow;
endproperty
a_ovf : assert property (p_overflow_reported);
// ---- QUALIFICATION ----------------------------------------------------
// P4: the count increments ONLY on a qualifying observation.
property p_count_only_on_qualify;
@(posedge clk) disable iff (!rst_n)
(count > $past(count)) |-> $past(qualifying);
endproperty
a_inc : assert property (p_count_only_on_qualify);
// P5: ONLY TS1 AND TS2 QUALIFY. An unrecognised or malformed training set
// must never contribute to progress.
property p_only_ts_qualifies;
@(posedge clk) disable iff (!rst_n)
(obs.valid && (obs.kind inside {OS_OTHER, OS_INVALID, OS_NONE}))
|-> !obs_qualifies(obs);
endproperty
a_kind : assert property (p_only_ts_qualifies);
// P6: A DISQUALIFYING OBSERVATION RESETS TO ZERO, not to one.
// Consecutive is the requirement (section 7).
property p_bad_resets_fully;
@(posedge clk) disable iff (!rst_n)
disqualifying |=> (count == '0);
endproperty
a_reset : assert property (p_bad_resets_fully);
// P7: THE THRESHOLD CANNOT ASSERT EARLY. Restated independently of the
// module's own comparison, so an off-by-one fails rather than agreeing
// with itself.
property p_no_early_threshold;
@(posedge clk) disable iff (!rst_n)
threshold_met |-> (count >= CNT_W'(THRESH));
endproperty
a_early : assert property (p_no_early_threshold);
// P7b: and it DOES assert once the count reaches the threshold -- the
// complementary half, which an off-by-one in the other direction fails.
property p_no_late_threshold;
@(posedge clk) disable iff (!rst_n)
(count >= CNT_W'(THRESH)) |-> threshold_met;
endproperty
a_late : assert property (p_no_late_threshold);
// P8: the counter SATURATES -- it never exceeds the threshold and never
// wraps below it once reached.
property p_saturates;
@(posedge clk) disable iff (!rst_n)
count <= CNT_W'(THRESH);
endproperty
a_sat : assert property (p_saturates);
// P9: LOSING LOCK INVALIDATES the qualification history.
property p_lock_loss_restarts;
@(posedge clk) disable iff (!rst_n)
(!cdr_lock || !rx_aligned) |=> (count == '0);
endproperty
a_lock : assert property (p_lock_loss_restarts);
// ---- PROGRESSION ------------------------------------------------------
// P10: POLLING CANNOT COMPLETE WITHOUT BOTH lock/alignment AND
// qualification. The chapter's central progression property.
property p_done_needs_both;
@(posedge clk) disable iff (!rst_n)
$rose(polling_done) |-> ($past(cdr_lock) && $past(rx_aligned)
&& $past(threshold_met));
endproperty
a_done : assert property (p_done_needs_both);
// P11: done and fail are mutually exclusive.
property p_done_xor_fail;
@(posedge clk) disable iff (!rst_n)
!(polling_done && polling_fail);
endproperty
a_excl : assert property (p_done_xor_fail);
// P12: SUCCESS BEATS A SAME-CYCLE TIMEOUT -- the declared priority.
property p_success_beats_timeout;
@(posedge clk) disable iff (!rst_n)
(qualifying_now && threshold_met && timeout_elapsed) |=> polling_done;
endproperty
a_priority : assert property (p_success_beats_timeout);
// P13: observations are consumed only while qualifying -- a stale stream
// outside that window is not evidence.
property p_obs_only_when_qualifying;
@(posedge clk) disable iff (!rst_n)
obs_ready |-> qualifying_now;
endproperty
a_window : assert property (p_obs_only_when_qualifying);
// ---- SCOPE ------------------------------------------------------------
// P14: NO NORMAL TRAFFIC IN POLLING. Bound against Chapter 18.1's gate.
property p_no_traffic;
@(posedge clk) disable iff (!rst_n)
(dut_ltssm.cur_state == ST_POLLING) |-> !dut_gate.traffic_enabled;
endproperty
a_no_traffic : assert property (p_no_traffic);
// P15: reset and entry clear the qualification history.
property p_reset;
@(posedge clk)
!rst_n |=> ((count == '0) && !polling_done && !polling_fail && !obs.valid);
endproperty
a_reset : assert property (p_reset);P7 and P7b are the off-by-one pair, and both are needed. P7 forbids early assertion; P7b forbids late. A >= THRESH-1 implementation fails P7; a > THRESH implementation fails P7b. Either alone would pass one of the two classic mutations.
P6 is the consecutive-versus-cumulative property, and it asserts reset to zero specifically — a design that reset to one would satisfy a looser property and still over-count.
P10 is the progression property, and it requires both preconditions explicitly rather than a single merged ready — which is §4's separation made checkable.
No liveness is asserted. "The partner eventually transmits TS1" is an environment property, and asserting it would assume away exactly the failure this state exists to detect.
14. Verification and Fault Injection
The scoreboard runs its own observation sequence and its own consecutive-count model, and never reads cnt_q or calls obs_qualifies.
Qualification — the boundary set
THRESH= 1, 2, 3, 4, 8.THRESH ≥ 2is required, because the>= THRESH-1off-by-one is invisible at 1 (§11).- Exactly
THRESHconsecutive qualifying observations — verify assertion on theTHRESH-th, not earlier (P7). THRESH − 1observations — verify no assertion.THRESH + 1— verify saturation, no wrap (P8).- A disqualifying observation at count
THRESH − 1— verify reset to zero (P6). Required. - Lock lost mid-qualification — verify restart (P9).
- TS1 and TS2 both qualifying;
OS_OTHERandOS_INVALIDnot (P5).
Observation capture
- Consumer stalled across observations — verify stability, then overflow reported (P1, P3).
state_entrywith a pending observation — verify it is discarded (P2). Required, and the mutation-6 test.- An observation in the same cycle as
state_entry— verify it does not count toward the new attempt.
Progression
- Lock, then alignment, then qualification — verify
polling_donerequires all three (P10). - Alignment without lock, and lock without alignment — verify neither progresses.
- Timeout while waiting for lock — verify
polling_fail. - Threshold met and timeout in the same cycle — verify done (P12). Required.
- Reset mid-qualification (P15).
Mutations
| # | Mutation | Caught by | Lab symptom |
|---|---|---|---|
| 1 | count increments on every ordered set | P4, P5 | qualifies on a stream carrying no valid training |
| 2 | TS2 classified as TS1 | scoreboard kind check | progress on the wrong evidence; Configuration entered early |
| 3 | bad observation does not reset the count | P6 | marginal lane qualifies; fails later in Configuration |
| 4 | threshold >= THRESH-1 | P7 at THRESH ≥ 2 | Configuration entered one TS too early |
| 5 | threshold > THRESH | P7b | never completes; looks like a dead partner |
| 6 | pre-entry observation counted | P2 | qualification succeeds on evidence from another state |
| 7 | counter wraps | P8 | threshold_met de-asserts after being reached |
| 8 | lock ignored in the completion condition | P10 | Polling completes on an unlocked lane |
| 9 | timeout beats same-cycle success | P12 | intermittent failure at exactly the timeout boundary |
| 10 | normal traffic enabled in Polling | P14 | packets into a Link that is still training |
| 11 | OS_INVALID accepted as qualifying | P5 | malformed sets count as progress |
| 12 | reset leaves the count | P15 | second attempt qualifies immediately |
| 13 | observation fields re-read at consume | P1 | lane and link fields from a different observation |
| 14 | lock loss does not restart qualification | P9 | evidence accumulated across an interruption |
15. Model Verification
The qualifier was executed before publication, not reasoned about.
Exhaustive equivalence. Every good/bad observation sequence up to length 10, for thresholds 1 through 4 — 4,092 sequences — compared against an independent reference defined as "the first index at which the previous THRESH observations were all qualifying." Zero mismatches.
Boundary checks for thresholds 1 through 8: a run of qualifying observations asserts on exactly the THRESH-th, and a run of THRESH − 1 never asserts.
And both classic off-by-ones were executed to produce §14's table rather than described from memory — including the finding that >= THRESH-1 is indistinguishable from correct at THRESH = 1, which is why the required test set starts at 2.
16. Debugging
Symptom → which of the three failures → signal → distinguishing experiment.
Detect succeeds but the Link sticks or oscillates in Polling
This is the physical-quality stage (§2). The card is there and the channel is the question.
Read three signals, in this order — they correspond to §4's three failures:
cdr_lock— if it never asserts, timing cannot be recovered. Signal level, channel loss, equalization (Chapter 17.4).rx_aligned— if lock is high and alignment is not, the receiver cannot find unit boundaries (Chapter 17.2 §5).qual_count— if both are high and the count never accumulates, training sets are arriving unrecognised or intermittently.
Three readings, three different investigations. A merged "polling failed" signal would leave all three open.
CDR lock is high but there is no progress
Alignment or recognition, and the count tells you which (§4).
If rx_aligned is low: the boundary search is failing — and note this is generation-specific (§3), so a design that assumed comma alignment on a Gen3+ Link is looking for the wrong evidence entirely.
If alignment is high and qual_count oscillates: training sets are being recognised and interrupted. That pattern — starting and resetting — is the signature of marginal signal quality, not of a logic fault, because a logic fault would produce zero recognition rather than intermittent recognition.
If qual_count is flat at zero with alignment high: the content is not recognised at all. The partner may be in Polling.Compliance (§1), may be transmitting at a different rate, or the classifier may be wrong.
Works at the base rate and fails at a higher one
Margin, and the comparison is the diagnostic (Chapter 17.4 §2).
The distinguishing experiment: change the channel — shorter cable, different slot. If the higher rate then polls successfully, the logic is fine and the channel was the limit.
And do not carry Gen1 assumptions forward (§3). The alignment mechanism is different from Gen3 onward, and a design or a debug expectation built around commas is looking for something that is not there.
Qualification reaches the threshold and Configuration fails immediately
Suspect the threshold arithmetic (§14's counterexample) before suspecting Configuration.
The distinguishing experiment: raise the threshold parameter and re-run. If the Link becomes stable, Polling was completing on insufficient evidence — and the real fault is here, one state earlier than the symptom.
17. Common Misconceptions
- "Polling means software polls the device." A name collision with a software idiom (Chapter 18.1 §4).
- "Polling starts before receiver presence is known." Detect precedes it, and for good reason (Chapter 18.2 §6).
- "CDR lock alone means Polling is complete." Lock is one of three conditions; alignment and repeated recognition are the others (§4, P10).
- "One valid training set is enough." Confidence comes from consecutive repetition (§7).
- "TS1 and TS2 are TLPs." Physical-layer ordered sets, existing precisely because no packet layer works yet (§6).
- "A Switch forwards training ordered sets." Each Link trains independently (Chapter 17.3 §15).
- "Comma alignment describes all PCIe generations." 8b/10b symbol alignment and Gen3+ block alignment are different mechanisms (§3).
- "Polling negotiates the bus number." Software assigns bus numbers, much later (Chapter 7.4).
- "Link number means bus number." Different layers entirely — Chapter 18.4 §5.
- "Training sequence numbers are DLL sequence numbers." No relationship (Chapter 14.5).
- "Polling success means traffic can flow." Configuration comes next, and L0 after that (P14).
- "A Polling timeout is a transaction timeout." Different layer, different mechanism (Chapter 12.1).
- "A bad observation should decrement the count." It resets it — consecutive is the requirement (§7, P6).
18. Understanding Check
19. What's Next
Polling is where "somebody is there" becomes "we can read each other" — and it is the first stage a marginal channel fails.
Two independent problems, three distinct failures. Lock and alignment must both succeed, and the recovered stream must carry consecutively recognised training information. Keeping those as separate signals is what makes "stuck in Polling" a three-way diagnosis rather than a dead end.
And confidence comes from repetition, because nothing here can be acknowledged — with a threshold comparison that must be exactly right, and an off-by-one that hides at the one parameter value most likely to be tested.
Chapter 18.4 — Configuration takes the next question. Trained lanes are not yet a Link. Which of them belong together, what their logical identities are, how wide the result is, and — the part that decides whether everything above works — when a discovery becomes a commitment.
The idea to carry forward: evidence gathered under conditions that no longer hold is not evidence.