Skip to content

PCIe · Module 25

Enumeration Failures — Where Did the Conversation Stop?

Seven of eight distinct root causes produce the same sentence: no device in the list. A milestone register turns that one symptom back into eight different findings.

The Link is up. The operating system shows nothing.

Chapter 25.1 §7 routed this symptom here, and it is worth stating why it needs its own chapter: "no device in the list" is produced by at least eight different faults, at four different layers, and the symptom is byte-identical for all of them.

1. Sources, Scope, and What Module 7 Already Owns

2. Enumeration Is a Conversation

A Function does not "appear." Software asks a series of questions and the device answers them; the Function becomes usable when enough of those exchanges have succeeded.

So every enumeration failure has the same shape: the conversation stopped. The diagnostic work is identifying where, and §4's milestone chain is that list.

Two consequences shape the whole chapter.

The symptom carries no information about the location. Software reports "nothing found" whether the request never arrived, the completion never returned, the identity was wrong, or the resource allocation failed. §14 measured this: 7 of 8 root causes produce the identical OS-visible symptom.

And the device can only see its half. A request that never reaches the endpoint leaves no trace at the endpoint — which is why §5's first counter is "did any configuration request arrive at all", and why a zero there points at the host or the topology rather than at the device (§8).

3. Prove L0 First

4. The Milestone Chain

Eight observable milestones, in dependency order. Each is something the device can observe about itself — not an assumption about what software did.

#MilestoneProvable fromIf this is the first missing one
1Link operationalLTSSM state25.3 / 25.4 (§3)
2a configuration request arrivedrequest counterthe request never reached the device — host or topology (§8)
3a completion was returnedcompletion counterthe device's config response path
4identity registers read correctlytrace of offset and returned dataidentity registers or their reset values
5BAR probing occurredtrace of BAR-offset accesses25.5 (§9)
6capability accesses occurredtrace of capability-offset accessescapability list structure
7the Command register was writtenwrite tracesoftware declined to enable — check earlier stages
8bus mastering enabledCommand register bitnot an enumeration failure (§10)

A crucial caveat stated in the chapter and in the RTL (§12): the order in which software accesses configuration space is platform-dependent and is not a PCIe rule. Firmware and operating systems differ, and a rescan differs from a cold boot.

So the milestones are a dependency order, not an observed order. Milestone 5 genuinely cannot succeed before milestone 3 — you cannot probe a BAR without a working config path. But whether software reads capabilities before or after BARs is its business. §12 labels the register DEBUG HEURISTIC for exactly this reason, and §15's mutation 13 is treating it as normative.

The value is unchanged by that caveat. Even as a heuristic, eight sticky bits convert one symptom into eight findings — and §14 measured that as the difference between a useless report and a localized one.

5. The Configuration Access Trace

Counters answer "did it happen"; a trace answers "what exactly happened." The chapter uses both, and the counters come first because they are cheap and they route.

Six counters that distinguish the first three milestones:

CounterZero meansNon-zero means
cfg_req_seenno configuration traffic reached the device (§8)the path works to this point
cfg_req_acceptedrequests arrive and are not takenback-pressure or a stalled front end
cfg_cpl_returnedrequests taken, nothing answeredthe response path (§6)
cfg_cpl_urrequests targeting something unimplemented (21.1 §6)
cfg_cpl_errorthe device answered with an error status (13.2)
cfg_req_droppeda device-side fault worth its own investigation

Read them in that order and the first non-obvious one localizes the failure. cfg_req_seen == 0 with the Link in L0 is the strongest single result in this chapter: the device is fine and nothing is asking it anything — which points at the host, the bridge configuration or the bus-number hierarchy (§8), and takes the device off the suspect list entirely.

Then the trace adds the detail counters cannot. §12's ring records, per access: the target identification, the register offset, the request type, the Tag, whether it was accepted, the completion status and the returned data. Six entries are usually enough — the failure is almost always within a few accesses of the last successful one.

6. The Identity Read — Four Outcomes

The first configuration read of a device is an identity read, and its four possible outcomes point at four different classes.

OutcomeMeansNext
a completion with the expected valuesthe config path works end to endcontinue to milestone 5
an Unsupported Request completionnothing claimed the request (21.1 §6)wrong target, or the Function is not implemented (§7)
a completion with an error statusthe device answered and refused (13.2)the device's config logic
no completion at allthe request was accepted and never answeredthe response path — the worst case, because it also stalls software

The fourth row is the one that produces the longest hangs, because software is typically waiting on it. The device-side evidence is cfg_req_accepted incrementing while cfg_cpl_returned does not — which §12's watchdog turns into a positive report rather than a silence.

One thing this chapter deliberately does not assert. A common platform behaviour is that a read of a non-existent device returns all-ones to software. Whether that value comes from the host's error handling, a bridge, or somewhere else is platform-dependent, and this chapter does not present it as a PCIe rule — §15's mutation 21. What the device can prove is which of the four outcomes above it produced, and that is what §12 records.

7. Function Range Safety

8. Bus Numbers and the Switch Hierarchy

When cfg_req_seen is zero, the device is not the suspect. The question becomes whether the request could have reached it.

Configuration requests route by ID (21.1 §1), and a switch forwards them only if the target bus falls within a downstream port's Secondary/Subordinate range. Chapter 21.3 §5 measured what happens when that containment fails: buses that are locally routable and globally unreachable, with every device individually correct.

So the diagnostic question is a comparison, not an inspection:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
does every bridge between the host and this device have a
Secondary/Subordinate range that CONTAINS the device's bus number?

And the distinguishing experiment is topological. Connect the device directly rather than behind the switch. Works direct, fails behind the switch → the hierarchy's numbering, not the device. Fails both ways → the device or the host, and §5's counters already told you which.

Chapter 21.3 owns the routing rules and this chapter owns the experiment — and §16's case 7 is exactly this pair.

9. The BAR Stage Boundary

Enumeration includes BAR probing and resource allocation (9.4, 7.8).

This chapter's job ends when the evidence says the config path works and the BAR stage is the first bad one. That is milestone 5, and it is a routing decision rather than a diagnosis:

EvidenceConclusion
identity reads succeed, BAR accesses occur, results are wrongthe BAR stage25.5
identity reads succeed, BAR accesses never occursoftware stopped earlier — check milestone 4's data
BAR accesses succeed, device still not usablemilestones 6–8, or 25.5's reachability question

Why the boundary is drawn here. BAR diagnosis has its own substantial content — mis-sized windows, mask bugs, 64-bit pairing, placement and readback analysis — and Chapter 23.2 §14 already measured two of its failure modes (a lower-32-only compare claiming addresses 4 GiB away in 50.1% of probes; a misaligned mask compare wrong in 33.3%). A chapter that absorbed all of that would leave 25.5 hollow.

10. A Visible Function With DMA Disabled Is Not an Enumeration Failure

11. The Waveform

Identity read succeeds, BAR read never completes

10 cycles
Ten cycles of a configuration path. Link L0 is asserted throughout. A configuration request is accepted at cycle 1 and a successful completion returns at cycle 2, setting the identity milestone. A second configuration request is asserted from cycle 3 and accepted at cycle 3, after which request ready falls and no completion is returned for the rest of the window. The BAR milestone never sets.identity read completes — milestone setidentity read completes —milestone setBAR-offset read acceptedBAR-offset read acceptedno completion — BAR stage is first missingno completion — BAR stageis first missingclklink_l0cfg_req_validcfg_req_readycfg_cpl_validcfg_cpl_okms_identityms_bart0t1t2t3t4t5t6t7t8t9
Figure 1 — an enumeration that stops at the BAR stage. The Link is operational throughout. An identity read is accepted and completes successfully, setting the identity milestone. A BAR-offset read is then accepted and never completes: the request-ready signal falls and no completion is returned, so the BAR milestone never sets. The first missing milestone, not the symptom, is what names the failing stage.

Four things to read out of the figure.

link_l0 is high throughout, which discharges §3 in one signal and makes everything below it meaningful.

ms_identity sets at cycle 2 and ms_bar never sets. That pair is the diagnosis — the first missing milestone (§4), readable without a trace.

cfg_req_ready falls at cycle 4 and stays low. The device stopped accepting, which distinguishes "accepted and never answered" from "never accepted" — two different faults (§5).

And the figure does not say why the BAR stage failed. That is 25.5's question (§9); this chapter's job was to name the stage.

12. RTL — The Enumeration Instruments

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Configuration-path observation types.
// INTERNAL DEBUG FORMAT. The register OFFSETS and their meanings belong to
// Module 7 and Chapter 9.4; this package only classifies what was seen.
package cfg_dbg_pkg;
 
  parameter int OFF_W    = 12;
  parameter int TAG_W    = 8;
  parameter int FUNC_W   = 3;
  parameter int NUM_FUNC = 4;                 // this device's implemented count
  parameter int TRACE_D  = 8;
  parameter int TRACE_W  = (TRACE_D <= 1) ? 1 : $clog2(TRACE_D);
 
  typedef enum logic [2:0] {
    CFG_RD = 3'd0, CFG_WR = 3'd1, CFG_OTHER = 3'd2
  } cfg_kind_e;
 
  typedef enum logic [2:0] {
    CS_NONE = 3'd0, CS_OK = 3'd1, CS_UR = 3'd2, CS_ERR = 3'd3, CS_NO_RESPONSE = 3'd4
  } cfg_status_e;
 
  // Which stage an offset belongs to. LOCAL CLASSIFICATION -- the register
  // map is Module 7's; this is only how the tracer bins what it saw.
  typedef enum logic [2:0] {
    STG_IDENTITY = 3'd0, STG_COMMAND = 3'd1, STG_BAR = 3'd2,
    STG_CAP      = 3'd3, STG_OTHER   = 3'd4
  } cfg_stage_e;
 
  typedef struct packed {
    logic             valid;
    cfg_kind_e        kind;
    cfg_stage_e       stage;
    logic [FUNC_W-1:0] func;
    logic [OFF_W-1:0]  offset;
    logic [TAG_W-1:0]  tag;
    logic              accepted;
    cfg_status_e       status;
    logic [31:0]       data;
    logic [3:0]        link_state;     // §3: a milestone reached in Recovery
    logic [31:0]       cycle;          //     means something different
  } cfg_trace_t;
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import cfg_dbg_pkg::*;
 
// SYNTHESIZABLE. Configuration access counters (§5).
// Read in order, the FIRST non-obvious counter localizes the failure.
// `cfg_req_seen == 0` with the Link in L0 is the strongest single result
// in this chapter: nothing is asking the device anything.
module cfg_access_counters (
  input  logic clk,
  input  logic rst_n,
  input  logic req_valid,
  input  logic req_ready,
  input  logic cpl_valid,
  input  logic cpl_ready,
  input  cfg_status_e cpl_status,
  input  logic req_dropped,
  input  logic dbg_clear,
 
  output logic [31:0] cfg_req_seen,
  output logic [31:0] cfg_req_accepted,
  output logic [31:0] cfg_cpl_returned,
  output logic [31:0] cfg_cpl_ur,
  output logic [31:0] cfg_cpl_error,
  output logic [31:0] cfg_req_dropped_cnt,
  output logic [31:0] cfg_req_stalled_cycles,
  output logic        saturated
);
  logic [31:0] seen_q, acc_q, cpl_q, ur_q, err_q, drop_q, stall_q;
  logic sat_q;
 
  assign cfg_req_seen=seen_q; assign cfg_req_accepted=acc_q;
  assign cfg_cpl_returned=cpl_q; assign cfg_cpl_ur=ur_q;
  assign cfg_cpl_error=err_q; assign cfg_req_dropped_cnt=drop_q;
  assign cfg_req_stalled_cycles=stall_q; assign saturated=sat_q;
 
  function automatic logic [31:0] bump(input logic [31:0] v, input logic e);
    if (!e) return v;
    return (&v) ? v : (v + 32'd1);
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin
      seen_q<='0; acc_q<='0; cpl_q<='0; ur_q<='0; err_q<='0;
      drop_q<='0; stall_q<='0; sat_q<=1'b0;
    end else begin
      // SEEN counts offers -- deliberately, because "a request arrived and
      // was never accepted" is a distinct finding from "none arrived" (§5).
      seen_q  <= bump(seen_q,  req_valid && !$past(req_valid));
      acc_q   <= bump(acc_q,   req_valid && req_ready);          // the TRANSFER
      cpl_q   <= bump(cpl_q,   cpl_valid && cpl_ready);
      ur_q    <= bump(ur_q,    cpl_valid && cpl_ready && (cpl_status == CS_UR));
      err_q   <= bump(err_q,   cpl_valid && cpl_ready && (cpl_status == CS_ERR));
      drop_q  <= bump(drop_q,  req_dropped);
      stall_q <= bump(stall_q, req_valid && !req_ready);
      if (&seen_q || &acc_q || &cpl_q) sat_q <= 1'b1;
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import cfg_dbg_pkg::*;
 
// SYNTHESIZABLE. Configuration trace ring (§5).
// Six to eight entries is enough: the failure is almost always within a
// few accesses of the last successful one. Freezes so the window survives.
module cfg_trace_ring (
  input  logic clk,
  input  logic rst_n,
  input  logic       push,               // one ACCEPTED configuration access
  input  cfg_trace_t rec,
  input  logic       freeze,
  input  logic       dbg_clear,
 
  output cfg_trace_t trace [TRACE_D],
  output logic [TRACE_W-1:0] wr_ptr,
  output logic               wrapped,
  output logic [31:0]        dropped_while_frozen
);
  cfg_trace_t t_q [TRACE_D];
  logic [TRACE_W-1:0] wr_q;
  logic wrap_q; logic [31:0] drop_q;
 
  always_comb for (int i=0;i<TRACE_D;i++) trace[i]=t_q[i];
  assign wr_ptr=wr_q; assign wrapped=wrap_q; assign dropped_while_frozen=drop_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin
      wr_q<='0; wrap_q<=1'b0; drop_q<='0;
      for (int i=0;i<TRACE_D;i++) t_q[i]<='0;
    end else if (push) begin
      if (freeze) begin
        if (!(&drop_q)) drop_q <= drop_q + 32'd1;   // count, never silent
      end else begin
        t_q[wr_q] <= rec;
        // Guarded for TRACE_D = 1.
        if (TRACE_D == 1) wrap_q <= 1'b1;
        else if (wr_q == TRACE_W'(TRACE_D-1)) begin wr_q<='0; wrap_q<=1'b1; end
        else wr_q <= wr_q + TRACE_W'(1);
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import cfg_dbg_pkg::*;
 
// SYNTHESIZABLE. Function range check (§7).
// §14: with a 3-bit Function field and 4 implemented Functions, 50.1% of
// randomly targeted requests name a Function that does not exist. Aliased,
// every one of them reads Function 0's configuration space.
module cfg_function_decode (
  input  logic                clk,
  input  logic                rst_n,
  input  logic                req_valid,
  input  logic [FUNC_W-1:0]   req_func,
  input  logic                dbg_clear,
 
  output logic                func_implemented,
  output logic [FUNC_W-1:0]   func_index,        // valid ONLY when implemented
  output logic                claim,             // may this device respond?
  output logic                unsupported,       // -> UR (21.1 §6)
  output logic [31:0]         out_of_range_count
);
  logic [31:0] oor_q;
 
  // A THREE-OUTCOME decode, exactly as 23.2 §5 does for addresses:
  // implemented, not implemented, and never a silent alias to zero.
  assign func_implemented   = (req_func < FUNC_W'(NUM_FUNC));
  assign func_index         = func_implemented ? req_func : '0;
  assign claim              = req_valid && func_implemented;
  assign unsupported        = req_valid && !func_implemented;
  assign out_of_range_count = oor_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) oor_q <= '0;
    else if (unsupported && !(&oor_q)) oor_q <= oor_q + 32'd1;
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import cfg_dbg_pkg::*;
 
// SYNTHESIZABLE. THE MILESTONE REGISTER (§4). The highest-value block in
// the chapter: eight sticky bits turn one symptom into eight findings.
//
// DEBUG HEURISTIC. These record what THIS DEVICE OBSERVED. The order in
// which software accesses configuration space is platform-dependent and
// is NOT a PCIe rule (§4, §15 mutation 13).
module enum_milestones (
  input  logic clk,
  input  logic rst_n,
  input  logic       link_operational,
  input  logic       cfg_req_accepted,
  input  logic       cfg_cpl_returned,
  input  cfg_stage_e access_stage,
  input  logic       access_ok,
  input  logic       command_write_seen,
  input  logic       bus_master_enabled,
  input  logic [3:0] link_state,
  input  logic       dbg_clear,
 
  output logic [7:0] milestones,             // one bit per §4 row
  output logic [3:0] first_missing,          // index of the first unset bit
  output logic [3:0] link_state_at_stall,    // §3: WHERE it stopped mattered
  output logic       all_reached
);
  logic [7:0] ms_q;
  logic [3:0] ls_q;
 
  assign milestones  = ms_q;
  assign all_reached = (&ms_q);
  assign link_state_at_stall = ls_q;
 
  // The FIRST unset bit is the whole diagnosis (§4).
  always_comb begin
    first_missing = 4'd8;                     // 8 == none missing
    for (int i = 7; i >= 0; i--) if (!ms_q[i]) first_missing = 4'(i);
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || dbg_clear) begin ms_q <= '0; ls_q <= '0; end
    else begin
      if (link_operational)                          ms_q[0] <= 1'b1;
      if (cfg_req_accepted)                          ms_q[1] <= 1'b1;
      if (cfg_cpl_returned)                          ms_q[2] <= 1'b1;
      if (access_ok && (access_stage == STG_IDENTITY)) ms_q[3] <= 1'b1;
      if (access_ok && (access_stage == STG_BAR))      ms_q[4] <= 1'b1;
      if (access_ok && (access_stage == STG_CAP))      ms_q[5] <= 1'b1;
      if (command_write_seen)                        ms_q[6] <= 1'b1;
      if (bus_master_enabled)                        ms_q[7] <= 1'b1;
      // Capture the link state the first time progress stalls, so §3's
      // "reached L0 then dropped" case is distinguishable afterwards.
      if (!(&ms_q) && link_operational && (ls_q == '0)) ls_q <= link_state;
    end
  end
endmodule

Classification: all five synthesizable debug hooks.

Failure — six. Aliasing an unimplemented Function to 0 (50.1%, §14). Counting completions on valid rather than the handshake. A trace that keeps writing after freeze. Milestones presented as a normative software order. Not recording the link state when progress stalls (§3). And clearing the counters before reading them (25.1 §12, mutation 26).

13. Same-Cycle Audit and Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---- CONFIGURATION PATH (§5) ------------------------------------
// P1: a request is counted as SEEN once per arrival, not per stalled cycle.
property p_seen_once_per_request;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && $past(req_valid)) |=> $stable(cfg_req_seen);
endproperty
 
// P2: acceptance is counted on the HANDSHAKE.
property p_accepted_on_transfer;
  @(posedge clk) disable iff (!rst_n)
    (cfg_req_accepted != $past(cfg_req_accepted)) |-> $past(req_valid && req_ready);
endproperty
 
// P3: a completion is counted on the handshake, never on `valid` alone.
property p_completion_on_transfer;
  @(posedge clk) disable iff (!rst_n)
    (cfg_cpl_returned != $past(cfg_cpl_returned)) |-> $past(cpl_valid && cpl_ready);
endproperty
 
// P4: status counters are mutually exclusive per completion.
property p_status_counters_exclusive;
  @(posedge clk) disable iff (!rst_n)
    (cpl_valid && cpl_ready) |=>
      ($countones({cfg_cpl_ur != $past(cfg_cpl_ur),
                   cfg_cpl_error != $past(cfg_cpl_error)}) <= 1);
endproperty
 
// P5: completions never exceed accepted requests -- a completion with no
// request is a device-side fault, not an accounting rounding.
property p_completions_le_requests;
  @(posedge clk) disable iff (!rst_n)
    (cfg_cpl_returned <= cfg_req_accepted);
endproperty
 
// ---- FUNCTION DECODE (§7) ---------------------------------------
// P6: an unimplemented Function is NEVER claimed. §14: 50.1% of a 3-bit
// field targets Functions a 4-Function device does not implement.
property p_unimplemented_not_claimed;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && (req_func >= FUNC_W'(NUM_FUNC))) |-> (unsupported && !claim);
endproperty
 
// P7: the Function index is never aliased to zero for an out-of-range value.
property p_no_function_aliasing;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && !func_implemented) |-> !claim;
endproperty
 
// P8: exactly one decode outcome per request.
property p_decode_outcome_total;
  @(posedge clk) disable iff (!rst_n)
    req_valid |-> $onehot({claim, unsupported});
endproperty
 
// P9: the out-of-range count advances only on an out-of-range request.
property p_oor_count_qualified;
  @(posedge clk) disable iff (!rst_n)
    (out_of_range_count != $past(out_of_range_count)) |-> $past(unsupported);
endproperty
 
// ---- RESPONSE INTEGRITY -----------------------------------------
// P10: the completion's Tag matches the request it answers.
property p_response_tag_matches;
  @(posedge clk) disable iff (!rst_n)
    (cpl_valid && cpl_ready) |-> (cpl_tag == pending_req_tag);
endproperty
 
// P11: the response payload is STABLE while the completion is stalled.
property p_response_stable_under_stall;
  @(posedge clk) disable iff (!rst_n)
    (cpl_valid && !cpl_ready) |=> (cpl_valid && $stable(cpl_data)
                                              && $stable(cpl_status));
endproperty
 
// P12: one response per accepted request -- no duplicates, none lost.
property p_one_response_per_request;
  @(posedge clk) disable iff (!rst_n)
    (cpl_valid && cpl_ready) |=> !cpl_valid until_with (req_valid && req_ready);
endproperty
 
// ---- TRACE AND MILESTONES (§4, §12) -----------------------------
// P13: the trace records ACCEPTED accesses only.
property p_trace_on_accepted;
  @(posedge clk) disable iff (!rst_n)
    push |-> rec.accepted;
endproperty
 
// P14: a frozen trace is immutable, and drops are counted.
property p_frozen_trace_immutable;
  @(posedge clk) disable iff (!rst_n)
    freeze |=> $stable(trace[0]);
endproperty
 
// P15: the write pointer is in range -- TRACE_D = 1 included.
property p_trace_ptr_in_range;
  @(posedge clk) disable iff (!rst_n) (wr_ptr < TRACE_W'(TRACE_D));
endproperty
 
// P16: milestones are STICKY -- a later failure does not unset an earlier
// success, or the "first missing" index becomes meaningless.
property p_milestones_sticky;
  @(posedge clk) disable iff (!rst_n)
    (!dbg_clear) |=> ((milestones & $past(milestones)) == $past(milestones));
endproperty
 
// P17: reset clears every milestone and counter.
property p_reset_clears_all;
  @(posedge clk)
    (!rst_n) |=> ((milestones == '0) && (cfg_req_seen == '0));
endproperty
 
// P18: `first_missing` names the lowest unset milestone.
property p_first_missing_correct;
  @(posedge clk) disable iff (!rst_n)
    (first_missing < 4'd8) |-> !milestones[first_missing];
endproperty
 
// P19: a milestone only sets when its evidence was observed -- never
// inferred from a later one.
property p_milestone_requires_evidence;
  @(posedge clk) disable iff (!rst_n)
    ($rose(milestones[2])) |-> $past(cfg_cpl_returned);
endproperty
 
// P20: the link state at stall is captured once and held (§3).
property p_link_state_at_stall_sticky;
  @(posedge clk) disable iff (!rst_n)
    (link_state_at_stall != '0) |=> $stable(link_state_at_stall);
endproperty
 
// P21: counters saturate rather than wrap.
property p_counters_saturate;
  @(posedge clk) disable iff (!rst_n)
    (cfg_req_seen == 32'hFFFF_FFFF) |=> (cfg_req_seen == 32'hFFFF_FFFF);
endproperty
 
// P22: the instruments never drive the configuration path.
property p_instruments_non_functional;
  @(posedge clk) disable iff (!rst_n)
    $stable({req_ready, cpl_valid}) or !$stable({cfg_req_seen, milestones});
endproperty
 
// P23: EVIDENCE -- a configuration request was actually observed.
property c_cfg_traffic_reached_device;
  @(posedge clk) disable iff (!rst_n) (req_valid && req_ready);
endproperty

Twenty-three properties. P6–P9 are §7's three-outcome decode, and P16 and P18 are what make the milestone register a diagnosis rather than a status dump — a milestone that can unset makes "first missing" a lie.

14. Measured Behaviour

15. Verification — Mutations

#MutationSymptomCaught by
1Debug identity registers before proving L0every observation is vacuous (§3)review
2An unimplemented Function aliases Function 0phantom Functions with identical identity (§14)P6, P7
3Function index truncatedtwo Functions aliasP7
4Completions counted on validstalled completions counted repeatedlyP3
5Requests counted per stalled cyclecfg_req_seen meaninglessP1
6Completions exceed accepted requestsa response with no request, unnoticedP5
7Response Tag not checked against the requestthe wrong answer acceptedP10
8Response data changes while stalledsoftware reads a different value than was decodedP11
9Two completions for one requestsoftware double-counts a register readP12
10A milestone can unsetfirst_missing becomes a lie (§12)P16
11A milestone inferred from a later onea stage marked reached that never happenedP19
12Link state at stall not captured"reached L0 then dropped" indistinguishable (§3)P20
13Milestones presented as a normative software ordera platform difference reported as a device bug (§4)review
14Trace keeps writing after freezethe window around the failure is overwrittenP14
15Trace depth 1 breaks the pointer widthout-of-range writeP15
16Counters cleared before being readthe evidence is destroyed by the toolingP17
17Counters wrapa busy path reports a small numberP21
18Debug register gates the configuration paththe measured device is not the shipping oneP22
19cfg_req_seen == 0 blamed on the devicenothing was asking it anything (§5)review
20A switch hierarchy fault blamed on the endpointthe request never reached it (§8)review
21An all-ones read presented as a PCIe rulea platform behaviour asserted as protocol (§6)review
22A BAR-stage failure diagnosed as an enumeration bugwrong chapter; config path is fine (§9)review
23Bus mastering disabled reported as an enumeration failurethe Function did enumerate (§10)review
24Identity registers uninitialized after resetplausible garbage read as validP19
25One global "current config request" with pipeliningresponses attributed to the wrong requestP10, P12
26Function 1 shares Function 0's configuration statetwo Functions report identical valuesP6
27A stale request survives a reset epocha post-reset completion matches a pre-reset requestP17
28Configuration response lost under backpressuresoftware hangs on a read that was answeredP11
29Register offset truncatedthe wrong register answeredP10
30Only cold boot exercised; rescan never testeda reset-path fault ships (§16)review
31The trace records offers rather than accepted accessesaccesses that never happened in the recordP13
32first_missing computed from the highest unset bitthe last gap reported instead of the firstP18

Two counterexamples worth stating explicitly.

Mutation 2 is silent and it manufactures devices. A configuration path that indexes cfg_regs[req_func] without a range check answers every request in the encodable Function space. §14 measured 50.1% of randomly targeted requests naming an unimplemented Function — all of which return Function 0's identity. Software then enumerates eight identical Functions where four exist, and the symptom is not "missing device" but "too many devices, all the same" — which is rarely recognized as a decode bug. P6's three-outcome decode is Chapter 23.2 §5's rule applied to Function numbers.

Mutation 10 destroys the chapter's main instrument without breaking anything visible. If a milestone bit can clear — say, because it is driven combinationally from a current condition rather than latched — then first_missing reports whichever stage is currently not true, not the first one that never happened. A device that enumerated fully and later dropped its Link would report first_missing = 0, sending the investigation to link training when the config conversation completed successfully minutes earlier. P16 asserts monotonicity, and it is one line.

16. Debugging

Symptom — no device in the OS list, and the Link is in L0. Read the milestone register first (§12). first_missing names the stage in one read, and §14 shows why the symptom cannot: 7 of 8 root causes produce it. If there is no milestone register, read §5's counters in order — the first non-obvious one localizes it.

Symptom — cfg_req_seen is zero. The device is not the suspect (§5). Nothing asked it anything. Check the bus-number hierarchy (§8) and whether the host enumerated past the bridge at all. The distinguishing experiment is topological: connect directly rather than behind the switch.

Symptom — requests arrive, no completions return. The response path (§6, row 4). cfg_req_accepted incrementing with cfg_cpl_returned flat is the signature, and it is the case that hangs software. Check whether the Function decode claimed the request at all — an unimplemented Function should produce a UR completion, not silence (§7).

Symptom — identity reads work, the BAR stage fails. The configuration path is healthy (§9). Route to 25.5. The evidence to carry across is the trace: which BAR offset, what was written, what read back.

Symptom — the device enumerates and DMA never starts. Not an enumeration failure (§10). Read the Command register's bus-mastering bit and the descriptor counter. Chapter 22.1 §6's no_work category is the other candidate — and it has nothing to do with PCIe.

Symptom — one Function appears, another does not. Check whether the missing Function's requests reach the device at all (§5) and whether the decode claims them (§7). Two Functions reporting identical identity values is mutation 2 — aliasing, not a missing Function.

Symptom — works when connected directly, fails behind a switch. Configuration routing (§8). Compare each bridge's Secondary/Subordinate range against the device's bus number — Chapter 21.3 §5 measured that 92.9% of random range pairs violate containment, so it is a narrow property that is easy to disturb.

Symptom — a reset produces a duplicate or stale configuration response. A request in flight across the reset boundary (mutation 27). The epoch discipline of 24.3 §6 applies to hardware too: a response to a pre-reset request must not be matched to a post-reset one. P17 asserts the clear.

Symptom — cold boot fails, warm rescan works. Something is uninitialized at power-on that a rescan happens to fix — identity register reset values (mutation 24), or a milestone that depends on an initialization sequence only the warm path performs. §12's link_state_at_stall distinguishes a cold-boot link problem from a cold-boot configuration problem in one read.

17. Misconceptions

"It doesn't enumerate" names a fault. It names a symptom shared by 7 of 8 root causes (§14).

"Read the Vendor ID first." Prove L0 first (§3) — otherwise the read is vacuous.

"No completion means the device is dead." It means the request was accepted and not answered; a UR is a different finding (§6).

"All-ones means no device, per the spec." That is a platform behaviour, not a PCIe rule this chapter asserts (§6).

"An unimplemented Function can just return Function 0." 50.1% of the encodable space, all answering identically (§7, §14).

"Enumeration failed" for a visible Function with DMA off. It enumerated; the Command register is the finding (§10).

"BAR problems are enumeration problems." They are the stage after; 25.5 owns them (§9).

"The milestone order is the PCIe order." Software's access order is platform-dependent (§4).

"Clear the counters and reproduce." That destroys the first evidence (25.1 §12).

"It works behind a switch or it doesn't — same thing." Direct-versus-switched is the distinguishing experiment (§8).

"A milestone can be recomputed from current state." Then first_missing reports the present, not the failure (mutation 10).

"Cold and warm boot are equivalent tests." They exercise different initialization paths (§16).

18. Understanding Check

Q1. The OS shows no device. What do you read first, and why not the Vendor ID? The LTSSM state (§3). If the Link never reached L0, the device cannot have received a configuration request, so every enumeration observation is vacuous — and the value software read came from the host's error path, not from the device. Only after L0 is proven does §12's milestone register become meaningful.

Q2. cfg_req_seen is zero and the Link is in L0. What does that tell you, and what is the next experiment? The device is functioning and nothing is asking it anything (§5) — the strongest single result in the chapter, because it removes the device from the suspect list entirely. The next experiment is topological: connect directly instead of behind the switch. Works direct → the bus-number hierarchy (§8, and 21.3 §5's containment rule). Fails both ways → the host or the root port.

Q3. A 4-Function device answers configuration reads for Functions 0 through 7 with identical identity values. What is wrong? The Function decode is aliasing (§7, mutation 2). With a 3-bit field, 50.1% of the encodable space names Functions that do not exist (§14), and an unguarded index returns Function 0's registers for all of them. The correct behaviour is an Unsupported Request (21.1 §6), and P6 asserts the device never claims an unimplemented Function.

Q4. Identity reads succeed and BAR reads return wrong values. Which chapter, and what do you hand over? 25.5 (§9). This chapter's job ended when the evidence showed the configuration path healthy and the BAR stage first-bad. Hand over the trace: which BAR offset was written, what value, and what read back — because Chapter 23.2 §14 measured two distinct decode faults (a 4 GiB alias at 50.1%, a misaligned mask at 33.3%) that this evidence distinguishes immediately.

Q5. Why does §12's milestone register also capture the link state? Because a milestone reached in L0 means something different from one attempted during Recovery (§3). A Link that drops mid-enumeration produces a partial conversation that looks identical to a device that stopped responding. link_state_at_stall separates them in one read, and without it the investigation goes to configuration space on what is actually a link-stability problem → 25.4.

Q6. The device is visible, its BARs are accessible, and DMA never starts. Is this an enumeration failure? No (§10). Every enumeration milestone succeeded. The candidates are bus mastering not enabled (9.5 §8) or no work being issued — and 22.1 §6 measured a real system whose largest blocking category was no_work, which has nothing to do with PCIe. Filing this as an enumeration bug sends the investigation to configuration space, where everything is correct.

19. What's Next

This chapter assumed the Link was operational and asked where the conversation stopped. §3 routed the other case away twice — and those are the next two chapters.

Chapter 25.3 Link Training Failures takes the first: the Link never reaches L0. The question becomes which state's exit condition is missing, and the instruments change from configuration counters to state residency and transition history.

Chapter 25.4 LTSSM Issues takes the second: the Link reaches L0 and does not stay, or loops, or takes a transition it should not. That is a question about the state machine's contracts — entry actions, guards, timers and the priority between simultaneous exits — and it is the deepest chapter in Module 25.

And the rest of §1's table remains. 25.5 takes the BAR stage §9 handed it, 25.6 the DMA path §10 routed to, and 25.7 through 25.9 the transaction, credit and analyzer classes. Every one of them starts from 25.1 §5's question, which is why the method came first.