Skip to content

PCIe · Module 7

Device Discovery — Probing a Hierarchy That Cannot Announce Itself

Discovery is directed probe-and-response, not broadcast. How a probe engine handles absence, timeout, and the stale-response race, why a late answer must never be attributed to the next candidate, and how failure shape localises the fault.

Chapter 7.2 left a function ready to answer configuration accesses. Chapter 7.1 established that the host has no description of the topology and must build one.

The two meet here.

How does the host determine whether a PCIe function exists at a location in the hierarchy?

1. Discovery Is Not Broadcast

The intuitive model is a roll call: the host asks "who is out there?" and everything present answers.

That is not what happens, and the difference matters.

There is no broadcast query. No mechanism exists for the host to ask the hierarchy at large what it contains, and no device announces itself. A function that is powered, ready, and perfectly healthy sits silent until something is directed at it.

Probes are addressed at specific locations. The host chooses a candidate hierarchy location, directs a configuration access at it, and interprets the result. Then it chooses the next.

Absence is an inference, not a message. Nothing reports "nothing here." The host concludes absence from the character of what came back — or did not.

2. The Probe Model

For a single candidate location, the host issues a configuration read and gets one of two broad outcomes.

Present. A valid response returns, carrying defined configuration content. The location holds a function; it is recorded, and — depending on what it turns out to be — may expand the set of locations still to probe.

Absent. The system determines that no function is present at that location. The host records absence and moves on.

3. Why Identity Reads Matter

A probe that merely established existence would be of limited use. What makes discovery productive is that the response carries standardised configuration information — content laid out the same way for every function, so the host can interpret it without knowing anything about the device in advance.

That gives software three things at once:

  • Existence. Something is here.
  • Identity. What it reports itself to be, which is how a driver is eventually matched to it.
  • Structural information. Whether this location expands the hierarchy, and therefore whether more probing is required beyond it.

The third is what makes discovery recursive rather than a flat sweep.

The fields themselves — what they are called, where they sit, how they are laid out — are Module 8's subject. What matters here is the architectural property: the response format is standardised, which is what allows a host to explore an unknown topology at all. A hierarchy in which every device reported itself differently could not be enumerated by generic software.

4. Tree Expansion

When a probe finds something that expands the hierarchy — a switch path, a bridge — the locations beyond it become reachable, and they must be probed too.

This is why discovery is recursive, and it produces the structural fact that dominates §9's debugging:

A location that is never probed is indistinguishable from a location that is empty.

If the element expanding a subtree is not discovered, nothing beneath it is ever examined. Those devices may be powered, ready, and answering correctly — and they will not appear, because no probe was ever directed at them.

Establishing the identity of newly reachable locations as the walk proceeds is Chapter 7.4's subject and is not addressed here.

5. The Probe Sequence

Two discovery probes. For candidate A, the host issues a probe, the Root Complex forwards a configuration read through the switch path to the candidate, which responds; the response returns through the path and the Root Complex to the host, which records the function present and continues exploring. For candidate B, the host issues a probe, the read is forwarded, no function responds, and the host records the location as absent and moves on.Host softwareRoot ComplexSwitch pathCandidateprobe candidate Aconfiguration readforwardeddelivered to thelocationvalid responseresponse returnedpresent — explorefurtherprobe candidate Bconfiguration readforwardedno function respondsabsent — move on
Figure 1 — two probes: one finding a function and one not. Solid arrows are the outbound configuration read; dashed arrows are what returns. The second probe's return is what the host must interpret as absence, and interpreting it correctly is the whole difficulty.

The two halves of the figure look symmetric and are not. The first is straightforward: something answered. The second requires the system to conclude something from an absence of evidence, within some bounded time, and act on that conclusion. The rest of this chapter is about doing that safely.

6. Microarchitecture — A Hardware-Assisted Probe Engine

Enumeration is orchestrated by software. That does not mean no hardware is involved in issuing configuration accesses.

The engine's responsibilities:

  • Issue exactly one configuration access per probe, holding the request until accepted.
  • Wait for a response for a bounded time.
  • Classify the outcome as present, absent-or-error, or unresolved.
  • Never attribute a response to the wrong probe.
  • Report a stable result and hold it until acknowledged.

The last two are the interesting ones.

7. The Two Races

Before the code, the two problems it exists to solve.

The response/timeout race

A probe waits for a response with a bounded timeout. If the response arrives in the same cycle the timeout would expire, both terminal paths are eligible — and if both write state, the result is whatever the RTL happens to do, which is not a design.

The contract chosen here: the response wins. A response that arrived is real evidence and a timeout is only the absence of evidence, so preferring the response is the choice that discards less information.

What matters more than which choice is made is that it is decided, documented, asserted, and tested. An implementation where both paths can fire is not ambiguous in silicon — it does something specific and unintended, once, under a condition nobody reproduced.

The stale-response race

This one is more dangerous because its symptom is a wrong answer rather than a hang.

Probe A is issued to candidate A. It does not respond within the timeout, so the engine gives up and reports unresolved. Probe B is then issued to candidate B. A's response now arrives.

If the engine attaches it to B, the host learns something about candidate B that is actually true of candidate A — a present device recorded at the wrong location, or an absent one recorded as present. Nothing errors. The topology description is simply wrong, and every later stage builds on it.

Three ways to prevent it:

  1. Wait long enough that a stale response is impossible before starting the next probe — simple, and it costs the worst-case latency on every timeout.
  2. Carry a correlation token so a response identifies which probe it belongs to, and discard non-matching ones.
  3. Quiesce the path between probes so nothing can be outstanding.

The engine below uses (2), because it is the one that generalises and because it makes the invariant checkable.

8. RTL — The Probe Engine

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. One-probe-at-a-time discovery engine.
// NOT a PCIe requirement and NOT a model of host enumeration software. It
// owns a single probe's lifetime: issue, wait, classify, report.
module discovery_probe #(
  parameter int CAND_W         = 16,   // opaque candidate identifier
  parameter int TAG_W          = 4,    // correlation token — see below
  parameter int TIMEOUT_CYCLES = 1024
) (
  input  logic                clk,
  input  logic                rst_n,
 
  // Control. `start` is ignored unless the engine is idle.
  input  logic                start,
  input  logic [CAND_W-1:0]   candidate_id,
  input  logic                result_ack,
 
  // Configuration-access request, toward the fabric.
  output logic                cfg_req_valid,
  input  logic                cfg_req_ready,
  output logic [CAND_W-1:0]   cfg_req_cand,
  output logic [TAG_W-1:0]    cfg_req_tag,
 
  // Response. Implementation metadata `tag` echoes the request's token.
  input  logic                cfg_rsp_valid,
  input  logic [TAG_W-1:0]    cfg_rsp_tag,
  input  logic                cfg_rsp_present,
  input  logic                cfg_rsp_error,
 
  // Status and terminal result.
  output logic                busy,
  output logic                done,
  output logic                result_present,
  output logic                result_error,
  output logic                result_timeout,
  output logic [CAND_W-1:0]   result_cand
);
 
  typedef enum logic [1:0] {
    PRB_IDLE  = 2'd0,
    PRB_ISSUE = 2'd1,   // request presented, awaiting acceptance
    PRB_WAIT  = 2'd2,   // request accepted, awaiting response or timeout
    PRB_DONE  = 2'd3    // terminal result held for the consumer
  } probe_state_e;
 
  // Holds TIMEOUT_CYCLES-1. $clog2(1) is 0, which would be a zero-width
  // signal, so the degenerate parameter is handled rather than left to
  // produce an obscure elaboration failure.
  localparam int TMO_W = (TIMEOUT_CYCLES <= 1) ? 1 : $clog2(TIMEOUT_CYCLES);
 
  initial begin
    if (TIMEOUT_CYCLES < 1) $fatal(1, "TIMEOUT_CYCLES must be at least 1");
    if (TAG_W < 1)          $fatal(1, "TAG_W must be at least 1");
  end
 
  probe_state_e       state_q;
  logic [CAND_W-1:0]  cand_q;
  logic [TAG_W-1:0]   tag_q;
  logic [TMO_W-1:0]   tmo_q;
  logic               present_q, error_q, timeout_q;
 
  // A response belongs to THIS probe only if we are waiting for one and its
  // token matches. Anything else — a late response from a probe that already
  // timed out, or a response arriving while idle — is silently discarded.
  wire rsp_match = (state_q == PRB_WAIT) && cfg_rsp_valid && (cfg_rsp_tag == tag_q);
  wire tmo_fire  = (state_q == PRB_WAIT) && (tmo_q == '0);
 
  // Request valid is derived from REGISTERED state and is held until accepted.
  // It never observes cfg_req_ready.
  assign cfg_req_valid  = (state_q == PRB_ISSUE);
  assign cfg_req_cand   = cand_q;
  assign cfg_req_tag    = tag_q;
 
  assign busy           = (state_q != PRB_IDLE);
  assign done           = (state_q == PRB_DONE);
  assign result_present = present_q;
  assign result_error   = error_q;
  assign result_timeout = timeout_q;
  assign result_cand    = cand_q;   // the result names the candidate it is about
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state_q   <= PRB_IDLE;
      cand_q    <= '0;
      tag_q     <= '0;
      tmo_q     <= '0;
      present_q <= 1'b0;
      error_q   <= 1'b0;
      timeout_q <= 1'b0;
    end else begin
      case (state_q)
        PRB_IDLE: begin
          if (start) begin
            cand_q    <= candidate_id;
            // NEW TOKEN per probe. Any response still in flight from an
            // earlier probe carries the old token and can no longer match.
            tag_q     <= tag_q + 1'b1;
            present_q <= 1'b0;
            error_q   <= 1'b0;
            timeout_q <= 1'b0;
            state_q   <= PRB_ISSUE;
          end
        end
 
        PRB_ISSUE: begin
          if (cfg_req_ready) begin
            // Timeout starts when the request is ACCEPTED, not when it is
            // presented — waiting for the fabric to take it is not the
            // device's latency and should not consume the device's budget.
            tmo_q   <= TMO_W'(TIMEOUT_CYCLES - 1);
            state_q <= PRB_WAIT;
          end
        end
 
        PRB_WAIT: begin
          if (rsp_match) begin
            // RESPONSE WINS the race with timeout. This branch is tested
            // first, so a response arriving in the expiry cycle is used and
            // the timeout is discarded. Only one branch ever writes state.
            present_q <= cfg_rsp_present;
            error_q   <= cfg_rsp_error;
            timeout_q <= 1'b0;
            state_q   <= PRB_DONE;
          end else if (tmo_fire) begin
            present_q <= 1'b0;
            error_q   <= 1'b0;
            timeout_q <= 1'b1;      // UNRESOLVED — not the same as absent
            state_q   <= PRB_DONE;
          end else begin
            tmo_q <= tmo_q - 1'b1;  // cannot wrap: tmo_fire catches zero
          end
        end
 
        PRB_DONE: begin
          // Result is sticky until acknowledged, so a consumer that is busy
          // cannot miss it.
          if (result_ack) state_q <= PRB_IDLE;
        end
 
        default: state_q <= PRB_IDLE;
      endcase
    end
  end
 
endmodule

Classification: synthesizable.

What it models: ownership of one probe from issue to terminal result, including both races.

What it teaches — four things:

  1. Three outcomes, not two. result_present, result_error, and result_timeout are separate outputs because they mean different things. A timeout says this probe did not resolve within this implementation's window — it does not say the location is empty.
  2. The response/timeout race is decided in the code's structure. rsp_match is tested before tmo_fire in the same if chain, so exactly one branch writes state. The priority is not a comment; it is the only reachable behaviour.
  3. The token makes stale attribution impossible rather than unlikely. tag_q advances on every probe, so a response from a timed-out probe cannot match. Nothing depends on timing assumptions the design cannot enforce — within the wrap limit stated in §7.
  4. Timeout accounting starts at acceptance. Waiting for the fabric to take the request is congestion, not device latency. Charging it to the device's budget produces spurious timeouts under load.

Deliberately simplified: one probe at a time, so there is no pipelining; the candidate identifier is opaque, since naming is Chapters 7.4–7.6; the request carries no read address, since what to read is Module 8; and the tag space is small enough that its wrap assumption must be checked against the real worst-case latency.

Production implication: a real implementation would use whatever request/response association the protocol provides rather than an invented tag, size timeouts against the platform's actual worst case rather than a parameter, likely support multiple outstanding probes for enumeration speed — which makes the stale-response problem harder, not easier — and distinguish among error classes rather than collapsing them into one bit.

9. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over discovery_probe. Implementation invariants for THIS design —
// not PCIe protocol requirements.
 
// STABILITY — P1: the request is held stable until accepted. A request whose
// candidate changed mid-handshake would probe a location nobody asked about.
property p_request_stable_until_ready;
  @(posedge clk) disable iff (!rst_n)
  (cfg_req_valid && !cfg_req_ready)
    |=> (cfg_req_valid && $stable(cfg_req_cand) && $stable(cfg_req_tag));
endproperty
a_req_stable : assert property (p_request_stable_until_ready);
 
// OWNERSHIP — P2: no probe starts while one is in flight. Two concurrent
// probes in a single-result engine would overwrite each other's outcome.
property p_no_start_while_busy;
  @(posedge clk) disable iff (!rst_n)
  (busy && start) |=> $stable(cand_q);
endproperty
a_no_restart : assert property (p_no_start_while_busy);
 
// SAFETY — P3: a response is consumed only while waiting for one. Catches a
// response accepted in IDLE or DONE, which would corrupt a held result.
property p_response_only_while_waiting;
  @(posedge clk) disable iff (!rst_n)
  (cfg_rsp_valid && state_q != PRB_WAIT) |=> $stable(state_q) || done;
endproperty
a_rsp_scoped : assert property (p_response_only_while_waiting);
 
// SAFETY — P4: a non-matching response never affects state. THE stale-response
// property. Without it, a late answer from a timed-out probe is attributed to
// the current candidate — a wrong topology entry with no error anywhere.
property p_stale_response_ignored;
  @(posedge clk) disable iff (!rst_n)
  (state_q == PRB_WAIT && cfg_rsp_valid && cfg_rsp_tag != tag_q && !tmo_fire)
    |=> (state_q == PRB_WAIT);
endproperty
a_stale_ignored : assert property (p_stale_response_ignored);
 
// CONSERVATION — P5: one accepted start produces at most one terminal result.
// `done` must not pulse twice for a single probe.
property p_single_done_per_probe;
  @(posedge clk) disable iff (!rst_n)
  (done && !result_ack) |=> done;
endproperty
a_done_sticky : assert property (p_single_done_per_probe);
 
// EXCLUSIVITY — P6: a timeout result carries no present or error indication.
// Catches a terminal path that writes some fields and leaves others stale
// from the previous probe.
property p_timeout_result_clean;
  @(posedge clk) disable iff (!rst_n)
  (done && result_timeout) |-> (!result_present && !result_error);
endproperty
a_timeout_clean : assert property (p_timeout_result_clean);
 
// RACE — P7: the response wins. If a matching response and the timeout expiry
// coincide, the result must be the response, not a timeout. This is the
// documented contract made checkable — an implementation where both paths can
// fire is not ambiguous, it is silently wrong once.
property p_response_beats_timeout;
  @(posedge clk) disable iff (!rst_n)
  (rsp_match && tmo_fire) |=> (done && !result_timeout);
endproperty
a_response_wins : assert property (p_response_beats_timeout);
 
// CORRECTNESS — P8: the result names the candidate that was probed. Catches
// a result reported against a candidate the engine was never asked about.
property p_result_matches_candidate;
  @(posedge clk) disable iff (!rst_n)
  (start && !busy) |=> (result_cand == $past(candidate_id)) throughout
                       (##[0:$] (done && result_ack));
endproperty
a_result_candidate : assert property (p_result_matches_candidate);
 
// SAFETY — P9: the timeout counter never wraps. A wrapped counter restarts
// the wait, turning a bounded probe into an unbounded one.
property p_timeout_no_wrap;
  @(posedge clk) disable iff (!rst_n)
  (state_q == PRB_WAIT && tmo_q == '0) |=> (state_q != PRB_WAIT);
endproperty
a_no_wrap : assert property (p_timeout_no_wrap);
 
// LIVENESS — P10: every started probe terminates.
// ASSUMPTION, stated explicitly: this holds only because the timeout provides
// a terminal path independent of the fabric. It additionally requires that the
// request is eventually accepted — a fabric that never asserts cfg_req_ready
// is not a bug in this module, so the environment must constrain it.
property p_probe_terminates;
  @(posedge clk) disable iff (!rst_n)
  (start && !busy) |-> s_eventually done;
endproperty
a_probe_terminates : assert property (p_probe_terminates);

P4 is the highest-value property in the chapter. A stale response attributed to the current candidate produces no error, no timeout, and no anomaly — just a topology entry that describes the wrong location. Every later enumeration stage builds on that entry. It is caught by one assertion and by essentially no amount of directed testing, because it requires a response later than the timeout and a probe already in flight.

P7 makes a decision auditable. The race is decided by branch order in a case arm. That is correct and it is also invisible to anyone reading the port list. The assertion states the contract where it can be checked.

P6 catches partial terminal writes. A timeout branch that sets timeout_q but forgets to clear present_q reports "unresolved, and also present" — with the present bit left over from the previous probe. That combination is meaningless and a consumer may act on either half.

10. Verification

The topology model

The environment needs its own statement of what should be found.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY. NOT synthesizable, NOT PCIe protocol state.
// The environment's independent statement of what the modelled hierarchy
// contains. Field names are generic: how locations are actually identified is
// Chapters 7.4-7.6, and this model must not pre-empt that.
typedef struct {
  int    parent;      // index of the location this one hangs beneath, -1 at root
  int    slot;        // opaque position under that parent
  bit    present;     // should a probe here find a function?
  bit    expands;     // does finding it reveal further locations?
  string name;        // human label for failure messages only
} discovered_node_t;
 
class topology_model;
  protected discovered_node_t nodes [$];
 
  function void add(int parent, int slot, bit present, bit expands, string name);
    discovered_node_t n;
    n.parent = parent; n.slot = slot;
    n.present = present; n.expands = expands; n.name = name;
    nodes.push_back(n);
  endfunction
 
  // What the DUT should report for a given candidate index.
  function bit expect_present(int idx);
    return (idx < nodes.size()) ? nodes[idx].present : 1'b0;
  endfunction
 
  // A location is only REACHABLE if every ancestor was found and expands.
  // This is the property that distinguishes "empty" from "never probed" —
  // and a scoreboard without it will report every device in an unreachable
  // subtree as a discovery failure, burying the one real fault.
  function bit is_reachable(int idx);
    int p = nodes[idx].parent;
    if (p < 0) return 1'b1;
    if (!nodes[p].present || !nodes[p].expands) return 1'b0;
    return is_reachable(p);
  endfunction
 
  function string name_of(int idx);
    return (idx < nodes.size()) ? nodes[idx].name : "<out of range>";
  endfunction
endclass

Classification: verification-only.

What it teaches: that a discovery scoreboard needs a reachability notion, not just a presence list. Without it, a single missing bridge produces one real failure and a cascade of false ones, and the real fault is the hardest of them to find.

Deliberately simplified: static topology, generic location naming, and no modelling of response latency per node.

Production implication: a real environment would model per-node response latency and error injection, support topologies that change during the run, and use whatever identity scheme the design actually implements once Chapters 7.4–7.6 have defined it.

Scenarios

  • Present function. Baseline: result_present, correct result_cand, no timeout.
  • Absent candidate. Verify the absent classification and that the engine returns to idle cleanly.
  • Delayed response. Sweep latency from one cycle up to TIMEOUT_CYCLES - 1. Verify the response is used at every latency.
  • Response exactly at the timeout boundary. Latency exactly TIMEOUT_CYCLES. The P7 scenario — verify the response wins and no timeout is reported.
  • Response one cycle past the boundary. Verify a timeout is reported and, critically, that the late response is then discarded rather than corrupting the held result.
  • Error response. Verify result_error without result_present or result_timeout.
  • Stale response after timeout. Time out probe A, start probe B, then deliver A's response with A's tag. Verify P4: B's result is unaffected. This scenario must be written deliberately — no random stimulus will produce it, and the bug it catches is silent.
  • Consecutive probes. A long sequence with mixed outcomes. Verify each result names its own candidate (P8) and that no state leaks between probes.
  • Reset during a probe. In each state. Verify a clean restart and no stale result presented afterwards.
  • Discovery through a one-level switch path. A present, expanding node with children. Verify the children are probed only after the parent is found, and that the reachability model agrees.
  • Mixed present and absent siblings. Several candidates under one parent with different outcomes — the pattern §11's first debug scenario depends on.

Coverage

Meaningful, not exhaustive:

  • Present, absent, error, and timeout as terminal outcomes.
  • Response-latency buckets: immediate, mid-window, boundary-minus-one, boundary, boundary-plus-one. The last three are where the race lives.
  • Each topology depth in the model — root-level and behind an expanding node.
  • Stale-response injection with a matching and a non-matching tag.
  • Reset asserted in each probe state.
  • Candidate transitions: present→absent, absent→present, timeout→present.
  • The expanding-node case: parent found and parent not found, so the reachability logic is exercised in both directions.

11. Debugging

The failure's shape selects which rungs of Chapter 7.1's ladder to check first. Three shapes, three different investigations.

One Endpoint missing, its siblings present

Symptom. Several devices under the same switch enumerate. One does not.

What the siblings' success establishes — this is the valuable part, and it is a lot:

  • The Root Complex is generating probes and interpreting responses.
  • The path to the shared parent is working in both directions.
  • The parent switch was discovered and is forwarding.
  • The shared upstream Link is operational.
  • The enumeration process is running and reaching this depth of the tree.

What remains. Only the smallest unique branch — everything on the path to this device that its siblings do not share:

  • Its own Link.
  • The downstream switch port serving it.
  • Its function reset and initialisation (Chapter 7.2).
  • Its configuration response logic.
  • Anything specific to addressing this particular location.

This is Module 4's smallest-shared-element method (Chapter 4.4) applied to enumeration: when some peers work and one does not, the fault lies in what is not shared. The siblings are the control experiment, and they were run for free.

Next observation. Check whether a probe is generated for that location at all. If it is not, the fault is in how the location came to be a candidate — and no amount of Endpoint investigation will help. If it is generated, follow it: does it arrive, is it answered, does the answer return.

An entire subtree missing

Symptom. Not one device but everything beneath some point.

What changed in the reasoning. The one-device case reasoned from what siblings prove. Here there are no working siblings at that level, so nothing below is proven — and, per §4, the devices below may be perfectly healthy and simply never probed.

What that makes likely:

  • The parent element expanding the hierarchy was not discovered, so nothing beneath it ever became a candidate.
  • The upstream Link to that parent is not usable.
  • The parent is present but not forwarding.
  • A shared reset or power domain covering the whole branch.
  • Path-level configuration that was never established.

The discriminating question, and it is one observation: is the parent present in the enumeration result?

  • Parent absent → the subtree was never reachable. Stop investigating the children entirely; they were never probed. The fault is at the parent.
  • Parent present but children missing → the parent was found and is not forwarding, or the children share something the parent does not.

Do not investigate the leaves. A whole-subtree absence with an absent parent means the leaves have not been tested by anything.

Intermittent after reset

Symptom. The device appears sometimes and not others, after otherwise identical resets.

What the intermittency itself says. A deterministic logic fault would fail consistently under identical conditions. Something is racing.

This connects directly back to Chapter 7.2, and the hypotheses are largely that chapter's:

  • Power-up sequencing — the function occasionally is not ready when the probe arrives.
  • Host probe timing — the interval between power-on and the first probe varies, and the design's margin is small.
  • Configuration visibility gating — accesses arriving in the window before readiness, handled inconsistently.
  • Reset synchronisation — a release landing differently between runs.
  • Link readiness — the Link established later on some runs, pushing the whole chain out.
  • A race between local initialisation and the first probe — the general form of all of the above.

The measurement that separates them. Capture, on a failing run, the initialisation sequencer state at the moment the first configuration access arrives — the two-observation method from Chapter 7.2 §10.

  • Not yet ready when the probe arrived → a genuine race. Whether the fix is on the device side or the platform side depends on what the design promised.
  • Ready, and the probe was answered, and the device still absent → power-up is exonerated. The fault is in response content, the return path, or interpretation, and it is intermittent for some other reason.

Why this is worth doing before anything else. Intermittent enumeration failures are commonly attributed to signal integrity, and sometimes that is right. But a power-up race produces exactly the same symptom, is far cheaper to check, and is fixed in RTL rather than in a board respin.

12. Common Misconceptions

  • "Software broadcasts 'identify yourself' and devices answer." There is no broadcast query and no device announces itself. The host directs a configuration access at one candidate location at a time and infers presence from the response.
  • "An absent device means the Link is down." Absence at a location means no function responded there. The Link may be entirely healthy — the location may be genuinely empty, or the probe may never have been generated, or the response may have been lost on the return path.
  • "Discovering one function means everything behind it is known." Finding an element that expands the hierarchy reveals that further locations exist to be probed. Each of them still requires its own probe, and a failure to continue leaves a healthy subtree entirely invisible.
  • "Discovery assigns addresses to devices." Discovery determines what exists. Assigning resources is a later stage requiring the full set of requirements to be known first — Chapter 7.8, with the underlying mechanics in Module 9.
  • "Discovery and hierarchy numbering are the same step." Discovery asks whether something is present. Numbering establishes how its position is represented — Chapters 7.4–7.6. They interleave during the tree walk and answer different questions.
  • "A timeout proves the device is absent." A timeout means this probe did not resolve within this implementation's window. A slow path, a congested fabric, or a device still initialising all produce timeouts with a device very much present. Collapsing unresolved into absent is why §8 reports three outcomes.
  • "If the siblings enumerate, the subtree is healthy." Siblings prove the shared path, the parent, and the process. They prove nothing about what is not shared — which is precisely where the fault must be when one sibling is missing.
  • "Reading an identifier is the whole of enumeration." It is one stage of six (Chapter 7.1). A function that is identified but not numbered, resourced, and enabled is not usable, and those failures look identical from a user's perspective.
  • "Endpoint RTL plays no role because enumeration is software-driven." The host cannot discover a function that does not respond, responds with undefined content, becomes visible before it is ready, or sits behind a component that does not forward. Every one of those is an RTL responsibility, and every one presents as "software cannot see the device."
  • "A late response can safely be used for the next probe." It carries information about a different location. Attributing it to the current candidate records a present device as absent or an absent one as present, silently — and every later stage builds on that entry. This is the failure P4 exists to prevent.

13. Understanding Check

14. What's Next

Discovery answers whether a function is present at a location. It has been carefully vague about what a "location" is, because that is a substantial subject in its own right.

Chapter 7.4 — Bus Number Assignment takes it up: how the hierarchy positions found during the tree walk are given identities, how the primary, secondary, and subordinate relationships of a hierarchy element are established, and how those numbers make later transactions routable to the functions this chapter merely found.

Chapters 7.5 and 7.6 complete the identity scheme, Chapter 7.7 covers the configuration-access mechanism every stage has been using, and Chapter 7.8 closes Module 7 with resource assignment — the stage that turns a discovered, identified, numbered function into a usable one.