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
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:
- 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.
- Carry a correlation token so a response identifies which probe it belongs to, and discard non-matching ones.
- 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
// 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
endmoduleClassification: synthesizable.
What it models: ownership of one probe from issue to terminal result, including both races.
What it teaches — four things:
- Three outcomes, not two.
result_present,result_error, andresult_timeoutare 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. - The response/timeout race is decided in the code's structure.
rsp_matchis tested beforetmo_firein the sameifchain, so exactly one branch writes state. The priority is not a comment; it is the only reachable behaviour. - The token makes stale attribution impossible rather than unlikely.
tag_qadvances 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. - 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
// 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.
// 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
endclassClassification: 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, correctresult_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_errorwithoutresult_presentorresult_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.