PCIe · Module 7
Function Number Assignment — Many Functions, One Device Position
A physical device is not necessarily one software-visible function. Why one device position may present several independently addressable functions, how shared transport coexists with function-local state, and why config-write isolation is an RTL invariant worth asserting.
Chapter 7.4 named the region. Chapter 7.5 named the position within it. Both chapters ended by saying that a position is not yet a complete target.
This is why.
Why can one PCIe device position expose multiple independently addressable functions, and how does the function coordinate distinguish them?
1. Physical Device Versus Software-Visible Function
A physical component — a card, a package, a block of silicon integrated into a larger chip — connects to the hierarchy at one device position. What software sees at that position is a different question.
That component may present:
- One function. The common case, and the one the previous two chapters implicitly assumed.
- Several functions. Each with its own configuration state, its own resources, its own driver relationship, and potentially a distinct role.
A network controller that also presents a management interface, or an accelerator that presents its compute engine and an auxiliary control path separately, has reason to appear as more than one thing to software — because the operating system will bind different drivers to them, configure them independently, and may assign them to different owners.
2. The Field
Under the conventional Routing ID interpretation, the function coordinate is 3 bits wide, giving eight function numbers, 0 through 7, at any one device position.
That completes the identifier introduced across the last two chapters: 8 bits of bus, 5 bits of device, 3 bits of function — sixteen bits in total, written bus:device.function and abbreviated BDF.
Two rules govern how the space is used:
Function 0 must be present. A device position that presents any function presents function 0. There is no arrangement in which functions exist at a position but function 0 does not.
The remaining functions need not be contiguous. A device presenting three functions may use 0, 2, and 5, leaving 1, 3, 4, 6, and 7 absent. Function numbers are not required to be packed.
3. Learning That More Functions May Exist
Probing all eight positions at every device in a hierarchy would be wasteful, since most device positions present only function 0.
Software therefore has a way to know whether looking further is worthwhile: standard configuration information at function 0 indicates whether the device position presents more than one function. If it does not, function 0 is the whole story and software moves on. If it does, the remaining candidate function numbers are worth probing.
The field carrying that indication, its location, and its encoding are Module 8's subject — specifically Chapter 8.6, which covers configuration-header layout. This chapter needs only the architectural fact: the information required to decide whether to keep probing is available from the function that is guaranteed to exist.
That is a neat piece of design worth noticing. The mandatory function is also the one that tells you whether there are others, so a single probe at a known-present location answers the question.
4. A Multifunction Device
The roles shown are representative, not prescriptive. Nothing requires a multifunction device to combine those particular things, and the arrangement varies entirely by product. What the figure establishes is structural: one device position, one Link, several independently addressable functions.
The identifiers in the figure:
| Identifier | What it names |
|---|---|
4:00.0 | The network interface function |
4:00.1 | The management function |
4:00.2 | The auxiliary function |
4:01.0 | The entire single-function device at position 1 |
Note that 4:00.0 and 4:01.0 share a bus and differ in device, while 4:00.0 and 4:00.1 share both bus and device and differ only in function. Each coordinate narrows the scope of the one after it — the property established in Chapter 2.6 and now visible in all three positions.
5. Shared Transport, Function-Local State
This is the section that matters most to an RTL engineer, because it is where the software model meets the hardware.
The organisation, conceptually:
Shared: the Link and physical layer, the transport machinery that carries accesses to and from the device position, and the front end that receives a configuration access before anything knows which function it is for.
A decode point: where the function coordinate selects which context the access belongs to.
Function-local: each function's configuration state, its resources, and its control.
Then application logic, which may be entirely separate per function, entirely shared, or anything between — that partition is a product decision with no protocol answer.
Protocol identity can be function-specific even when substantial hardware is shared.
6. RTL — Function Decode
// SYNTHESIZABLE. Selects one function's context from an already-extracted
// function coordinate, honouring a presence mask.
// NOT a packet decoder and NOT a configuration-space model.
module func_decode #(
parameter int NUM_FUNCS = 8 // conventional Routing ID: 3-bit function field, 0-7
) (
input logic req_valid,
input logic [FN_W-1:0] req_function,
// Which function numbers this device position actually presents. Need NOT
// be contiguous — a device may present functions 0, 2 and 5 only.
input logic [NUM_FUNCS-1:0] func_present,
output logic [NUM_FUNCS-1:0] func_sel, // one-hot, or all-zero
output logic func_absent
);
localparam int FN_W = (NUM_FUNCS <= 1) ? 1 : $clog2(NUM_FUNCS);
initial begin
if (NUM_FUNCS < 1 || NUM_FUNCS > 8)
$fatal(1, "NUM_FUNCS must be 1..8 under the conventional 3-bit function field");
if (NUM_FUNCS & (NUM_FUNCS - 1))
$fatal(1, "NUM_FUNCS must be a power of two so req_function cannot overrun");
// Function 0 is always present at a device position that presents any
// function. A presence mask without bit 0 describes nothing real.
end
always_comb begin
func_sel = '0; // assigned unconditionally: no latch
// Selection requires BOTH a valid request and the function being present.
// Selecting an absent function would expose an uninitialised context and,
// for a write, would modify state that does not correspond to anything
// software can see.
if (req_valid && func_present[req_function])
func_sel[req_function] = 1'b1;
end
assign func_absent = req_valid && !func_present[req_function];
endmoduleClassification: synthesizable.
What it models: the decode point of §5 — the moment a shared access becomes a function-specific one.
What it teaches — three things:
- One-hot by construction.
func_selis cleared and then at most one bit is set. There is no path that sets two, which makes the "exactly one context selected" invariant structural rather than something the surrounding logic must maintain. - Presence is checked at decode, not later. An absent function must not select a context at all. Checking downstream — letting the access reach a context and then suppressing the effect — leaves a window in which the wrong thing is enabled.
- The parameter is bounded to the field. The elaboration check refuses anything above 8 because the conventional coordinate is 3 bits, and refuses non-powers-of-two because
req_functionwould otherwise be able to index past the mask. A design supporting ARI would need a wider coordinate and a correspondingly larger context array.
Deliberately simplified: presence is a static input rather than modelled as configured; no access-type distinction; and no behaviour defined for func_absent beyond signalling it.
Production implication: a real decoder must produce whatever response the protocol requires for an access to an absent function, integrate with the bus and device matching of Chapter 7.5 so all three coordinates are checked, and coordinate with per-function readiness — a function may become accessible later than its siblings, which is §11's third scenario.
7. RTL — Function Context Array
// SYNTHESIZABLE. Per-function state behind a shared configuration front end.
// The state fields are ILLUSTRATIVE internal state — not a PCIe
// configuration-space layout. Module 8 owns what configuration space contains.
typedef struct packed {
logic enabled;
logic error_seen;
logic [15:0] local_status;
} function_state_t;
module function_context #(
parameter int NUM_FUNCS = 8
) (
input logic clk,
input logic rst_n,
// Shared configuration front end — one access at a time.
input logic req_valid,
output logic req_ready,
input logic req_write,
input logic [FN_W-1:0] req_function,
input logic [17:0] req_wdata, // packs the illustrative fields
input logic [NUM_FUNCS-1:0] func_present,
output logic rsp_valid,
input logic rsp_ready,
output logic [17:0] rsp_rdata,
output logic rsp_unsupported
);
localparam int FN_W = (NUM_FUNCS <= 1) ? 1 : $clog2(NUM_FUNCS);
// One state context per function. SHARED front end, SEPARATE state — the
// §5 partition made structural.
function_state_t fn_state [NUM_FUNCS];
logic [NUM_FUNCS-1:0] func_sel;
logic func_absent;
func_decode #(.NUM_FUNCS(NUM_FUNCS)) u_decode (
.req_valid (req_valid && req_ready), // decode only an ACCEPTED access
.req_function (req_function),
.func_present (func_present),
.func_sel (func_sel),
.func_absent (func_absent)
);
// Ready only when no response is outstanding. Depends on state, never on
// req_valid — no combinational path from a requester's valid to its ready.
assign req_ready = !rsp_valid || rsp_ready;
wire accept = req_valid && req_ready;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int f = 0; f < NUM_FUNCS; f++) begin
fn_state[f].enabled <= 1'b0;
fn_state[f].error_seen <= 1'b0;
fn_state[f].local_status <= '0;
end
rsp_valid <= 1'b0;
rsp_rdata <= '0;
rsp_unsupported <= 1'b0;
end else begin
if (rsp_valid && rsp_ready) rsp_valid <= 1'b0;
if (accept) begin
rsp_valid <= 1'b1;
rsp_unsupported <= func_absent;
// THE ISOLATION LOOP. The write enable for context f is func_sel[f]
// and nothing else. Because func_sel is one-hot by construction
// (func_decode), at most one context is ever written — and an absent
// function selects none, so an access to it has no side effect at all.
for (int f = 0; f < NUM_FUNCS; f++) begin
if (func_sel[f] && req_write) begin
fn_state[f].enabled <= req_wdata[0];
fn_state[f].error_seen <= req_wdata[1];
fn_state[f].local_status <= req_wdata[17:2];
end
end
// Read data comes from the selected context, or zero when none is
// selected. Returning another function's content on an absent access
// would leak state across the isolation boundary.
rsp_rdata <= '0;
for (int f = 0; f < NUM_FUNCS; f++)
if (func_sel[f]) rsp_rdata <= fn_state[f];
end
end
end
endmoduleClassification: synthesizable, with a conceptual typedef for the illustrative state.
What it models: the shared-front-end, separate-state organisation of §5, with the decode point as the only thing standing between them.
What it teaches: that isolation is a property of the write-enable expression, not of having separate registers. Declaring fn_state[NUM_FUNCS] creates separate storage; what keeps function 1's write out of function 0's state is that func_sel[f] is the only enable, and that it is one-hot. A design with per-function registers and a broken enable has separate storage and no isolation.
Deliberately simplified: a single 18-bit state per function standing in for what would be a substantial register file; one access at a time, with the deeper request/response ownership belonging to Chapter 7.1's front end; presence static; and no per-function reset domain, which §11's third scenario shows is a real production concern.
Production implication: a real multifunction device needs each function's full configuration space, per-function resource state, defined behaviour for accesses to absent functions, per-function readiness and possibly per-function reset, and — the part most easily underestimated — a register-file structure whose addressing cannot alias between functions.
8. Assertions
// SVA over func_decode and function_context. Implementation invariants for
// THESE designs — not PCIe protocol requirements.
// SAFETY — P1: at most one function context is ever selected. The structural
// guarantee that everything else rests on.
property p_select_is_onehot0;
@(posedge clk) disable iff (!rst_n)
$onehot0(func_sel);
endproperty
a_onehot : assert property (p_select_is_onehot0);
// SAFETY — P2: a selected function must be present. Selecting an absent
// function exposes an uninitialised context to software as though it were a
// real one.
property p_selected_is_present;
@(posedge clk) disable iff (!rst_n)
(func_sel != '0) |-> ((func_sel & func_present) == func_sel);
endproperty
a_present_selected : assert property (p_selected_is_present);
// SAFETY — P3: an access to an absent function has no write side effect. The
// property that makes probing safe: discovery probes absent functions by
// design, and those probes must not alter anything.
property p_absent_no_side_effect;
@(posedge clk) disable iff (!rst_n)
(accept && func_absent) |=> (func_sel == '0);
endproperty
a_absent_inert : assert property (p_absent_no_side_effect);
// ISOLATION — P4: a write to one function does not modify any other function's
// state. THE property of this chapter. Generated per pair so a violation names
// both the written function and the corrupted one.
generate
for (genvar w = 0; w < NUM_FUNCS; w++) begin : g_writer
for (genvar v = 0; v < NUM_FUNCS; v++) begin : g_victim
if (w != v) begin : g_pair
property p_write_isolated;
@(posedge clk) disable iff (!rst_n)
(accept && req_write && func_sel[w]) |=> $stable(fn_state[v]);
endproperty
a_isolated : assert property (p_write_isolated);
end
end
end
endgenerate
// CORRECTNESS — P5: read data comes from the selected function. Catches a
// read mux that ignores the selection, which returns one function's content
// for every function's read — plausible and completely wrong.
//
// Generated per function rather than written with a computed index: $clog2 is
// a constant function and cannot decode a signal, so "the selected one" has to
// be expressed as one property per candidate.
generate
for (genvar r = 0; r < NUM_FUNCS; r++) begin : g_read
property p_read_from_selected;
@(posedge clk) disable iff (!rst_n)
(accept && !req_write && func_sel[r]) |=> (rsp_rdata == $past(fn_state[r]));
endproperty
a_read_selected : assert property (p_read_from_selected);
end
endgenerate
// STABILITY — P6: the function selector is stable while a request is
// outstanding. A selector that changes mid-access can write one context and
// read another.
property p_selector_stable;
@(posedge clk) disable iff (!rst_n)
(req_valid && !req_ready) |=> (req_valid && $stable(req_function));
endproperty
a_selector_stable : assert property (p_selector_stable);
// CONSERVATION — P7: one accepted access produces one response.
property p_one_response_per_access;
@(posedge clk) disable iff (!rst_n)
accept |=> rsp_valid;
endproperty
a_one_response : assert property (p_one_response_per_access);
// STABILITY — P8: the response is stable while the requester stalls.
property p_response_stable;
@(posedge clk) disable iff (!rst_n)
(rsp_valid && !rsp_ready) |=> (rsp_valid && $stable(rsp_rdata)
&& $stable(rsp_unsupported));
endproperty
a_response_stable : assert property (p_response_stable);
// SAFETY — P9: reset establishes a defined state for every function context,
// including ones that are not present. An undefined context can be exposed if
// the presence mask is ever wrong.
property p_reset_defines_all;
@(posedge clk)
!rst_n |=> (fn_state[0].enabled == 1'b0 && !rsp_valid);
endproperty
a_reset_defined : assert property (p_reset_defines_all);P4 is the chapter's property, and generating it per pair is deliberate. A single aggregate property saying "no other function changed" would report a violation without saying which function corrupted which — and in a device with eight functions that is most of the diagnosis. The pairwise form names both immediately.
Why ordinary testing misses P4's failure. A directed test writes function 1's configuration and reads it back. It passes: function 1 holds what was written. Nothing in that test reads function 0 afterwards, so a write that also landed on function 0 goes unnoticed. Catching it requires either checking every other function after every write — which nobody does by hand — or an assertion that does it automatically. The bug then surfaces in a system where two drivers configure two functions and one keeps losing its settings.
P3 is what makes discovery safe. Software probes absent functions on purpose (Chapter 7.3), so probes at absent function numbers are normal traffic rather than errors. If those probes had side effects, the act of enumerating a device would change its state — and would do so differently depending on how thoroughly the host probed.
9. Verification
Monitors observe: the shared front-end handshake with its function coordinate, the presence mask, the decode output, every function context, and the response.
The scoreboard independently models: which BDF identities are populated and what each function's state should contain after each access. Now that all three coordinates have been introduced, a complete identity model is appropriate:
// VERIFICATION-ONLY. NOT synthesizable, NOT a PCIe packet format.
// The environment's independent statement of what identities are populated
// and what each function's state should be.
typedef struct {
int bus;
int device;
int fn; // `function` is a SystemVerilog keyword
bit present;
int expected_state;
} bdf_expect_t;
class bdf_scoreboard;
protected bdf_expect_t entries [$];
function void populate(int bus, int device, int fn, bit present);
bdf_expect_t e;
e.bus = bus; e.device = device; e.fn = fn;
e.present = present; e.expected_state = 0;
entries.push_back(e);
endfunction
protected function int find(int bus, int device, int fn);
foreach (entries[i])
if (entries[i].bus == bus && entries[i].device == device && entries[i].fn == fn)
return i;
return -1;
endfunction
// A write must change exactly the addressed function and nothing else.
// The model updates one entry; the checker then compares EVERY entry
// against the design, which is what catches a leak into a sibling.
function void observe_write(int bus, int device, int fn, int data);
int i = find(bus, device, fn);
if (i < 0 || !entries[i].present) return; // absent: no side effect (P3)
entries[i].expected_state = data;
endfunction
function bit expect_present(int bus, int device, int fn);
int i = find(bus, device, fn);
return (i >= 0) && entries[i].present;
endfunction
function int expect_state(int bus, int device, int fn);
int i = find(bus, device, fn);
return (i >= 0) ? entries[i].expected_state : 0;
endfunction
endclassClassification: verification-only.
What it teaches: that the checker must compare every identity after every write, not only the one written. A model that updates one entry and checks one entry cannot detect a leak — the leak is by definition somewhere the test was not looking.
Deliberately simplified: one integer standing for a function's state; static population; and no modelling of access ordering.
Production implication: a real environment would model each function's full configuration space, handle identities appearing as enumeration proceeds, and check ordering and access-size rules.
Scenarios:
- Single-function device. Only function 0 present. Verify function 0 responds and every other function number reports absent with no side effect.
- Multifunction device, contiguous. Functions 0, 1, 2 present. Verify each responds independently.
- Multifunction device, sparse. Functions 0, 2, 5 present; 1, 3, 4, 6, 7 absent. The scenario that catches software or hardware assuming contiguity, and the one §2's rule makes necessary.
- Per-function write isolation. Write a distinct value to each present function in turn, and after each write read back every function. This is P4's scenario and it must be exhaustive across pairs, not sampled.
- Serial accesses to different functions. Alternate between functions on consecutive accesses. Verify the selector settles correctly each time and no state carries over.
- Absent-function probe. Direct accesses at each absent function number, both reads and writes. Verify no state anywhere changes (P3) and the unsupported indication is returned.
- Presence mask without function 0. An illegal configuration. Verify the design's behaviour is defined — this is a check that the elaboration assumption is not silently relied on at runtime.
- Reset with all contexts written. Verify every context returns to its defined value, including absent ones (P9).
- Requester stalls the response. Verify stability (P8) and that no new access is accepted.
Coverage should include: each function number as the target, present and absent; every present/absent pattern actually modelled; every ordered pair of functions for the write-isolation check; reads and writes at each function; and reset with each context holding a non-reset value.
10. The Complete BDF Model
Three chapters, three coordinates. Putting them together on Chapter 7.4's hierarchy:
| Identifier | What it names | Which coordinate did the work |
|---|---|---|
0:00.0 | The Root Port | Bus 0 is the Root Complex's region |
1:00.0 | The switch upstream port | Bus 1 is the region the Root Port created; device 0 because the Link is point-to-point |
2:00.0 | Switch downstream port A | Bus 2 is the switch's internal region; device 0 distinguishes it from port B |
2:01.0 | Switch downstream port B | Same region, different device position |
3:00.0 | Endpoint A | Bus 3, the region port A created; device 0 |
4:00.0 | A multifunction device's first function | Bus 4; device 0; function 0 |
4:00.1 | The same device's second function | Same bus, same device — only the function differs |
Read down the table and each coordinate earns its place. Bus distinguishes regions and is what lets an access be forwarded (Chapter 7.4). Device distinguishes positions within a region, which matters mainly on switch-internal buses (Chapter 7.5). Function distinguishes contexts at one position, which matters wherever a component presents more than one thing to software.
What is deliberately still missing. Nothing here says how an access carrying 4:00.1 actually reaches that function — how the identity becomes an addressed operation the hardware can act on. That is Chapter 7.7's subject and it is the natural next question.
The distinction to carry forward: this batch established identity. The next chapter establishes access. Knowing what a thing is called is not the same as knowing how to reach it.
11. Debugging
Symptom: the device appears but one function is missing
What the sibling's success establishes — and it is a great deal:
- The Link works and the device position is reachable.
- Bus-level forwarding is correct (Chapter 7.4).
- Device-level matching is correct (Chapter 7.5).
- The shared front end accepts accesses and produces responses.
- Enumeration is running and reaching this device position.
Everything shared between the functions is exonerated by the working function. This is the smallest-unique-boundary method from Chapter 4.4, applied at the finest granularity Module 7 offers.
What remains — only what is specific to the missing function:
- The presence indication. Does the device report itself as multifunction at all? If not, software has no reason to probe beyond function 0, and the function is missing because nobody looked.
- Probe generation. Was an access aimed at that function number actually issued?
- The decoder. Does
func_presentinclude that function's bit, and does the decode select it? - Function-local initialisation. The function may be present in the mask and not yet ready — Chapter 7.2's question at function granularity.
- Function-local response. The context may be selected and failing to answer.
- Function-local state. The context may be answering with content that software reads as absence.
The observation that splits these fastest. Check whether a probe for that function number was issued at all. If it was not, the fault is in the multifunction indication — and no amount of investigation inside the device will help. If it was, follow it to the decoder and see whether it selected anything.
Symptom: a configuration write intended for function 1 changes function 0
This is the isolation failure, and it deserves recognising immediately because the symptom does not look like a decode problem.
How it presents. A driver configures its function; another driver's function loses or changes settings. From a system view it looks like driver interference, a race, or spontaneous configuration loss. Nothing points at the decode path.
Prime suspects, in order:
- The decode is missing or wrong. The write enable is not gated by the function selection, so every write reaches every context. P4 catches this on the first write.
- The selector is not one-hot. Two bits set means two contexts written. P1 catches this.
- Register-file addressing aliases between functions. Each function has separate storage in the source, and the address arithmetic maps two functions to overlapping locations. The isolation is nominal, not actual.
- State that should be per-function is shared. A field that was made common as an optimisation, or was never split when the design went from one function to several. This one is architectural rather than a coding error, and it is the hardest to see in a review.
- The selector changes mid-access. P6's failure: the access writes using one selection and completes using another.
Why this class needs assertions rather than tests. A test that writes function 1 and reads function 1 back passes. Detecting the leak requires reading every other function after every write, which is exactly what P4 does automatically and what a hand-written test almost never does.
Symptom: function 0 enumerates every boot, function 1 appears intermittently
What intermittency indicates. A deterministic logic fault — a wrong mask, a broken decoder — would fail consistently. Something is racing.
This is Chapter 7.2's problem at function granularity, and the hypotheses are that chapter's, narrowed:
- Function-local initialisation timing. Function 1's context becomes ready later than function 0's. If the host probes during the gap, it finds function 0 and not function 1 — and whether it does depends on host timing that varies between boots.
- The presence indication is not stable when first read. If the multifunction indication is sampled before it settles, software may not learn to probe further.
- Reset synchronisation. A per-function reset released asynchronously with respect to the logic that samples readiness.
- Discovery scan timing. The host's probe of function 1 arrives at a different point relative to initialisation on different boots.
What makes it specifically function-level. Function 0 working every time means the shared path, the Link, and the device-level identity are all stable. Only something with per-function timing can produce a per-function intermittency.
The measurement. Capture whether function 1's context reported ready at the moment its probe arrived — the two-observation method from Chapter 7.2 §10, applied per function. If it was not ready, the race is confirmed and the fix is on the device side: a function must not be advertised as present before it can answer.
12. Failure Locality — Module 7's Synthesis
The three numbering chapters produce one practical tool. The scope of what is missing selects which layer to inspect first:
| Symptom | First inspection target |
|---|---|
| A whole downstream branch is absent | Bus and path — range configuration, forwarding, the parent port (7.4) |
| One device position on an otherwise healthy bus is absent | Device — probe generation, target decode, that device's Link and initialisation (7.5) |
| One function within a working device is absent | Function — presence indication, function decode, function-local init (this chapter) |
| A configuration write modifies the wrong function | Function decode and isolation — one-hot selection, write enables, register-file aliasing |
| Devices appear under the wrong parent region | Bus assignment — overlapping or stale ranges (7.4) |
13. Common Misconceptions
- "One physical device equals one function." Many devices present one; many present several. The addressable unit is the function, which is why the identifier has a third coordinate at all.
- "The function number is just a software label." It selects which configuration context an access reaches. A decoder that ignores it sends every access to the same context, which is §11's second scenario.
- "Each function needs its own physical Link." All functions at a device position share one Link and one physical layer. What is separate is configuration state, resources, and software identity — not the transport.
- "Multifunction means several endpoints behind a switch." Those are separate devices at separate positions, each with its own Link and its own bus. A multifunction device is one component at one position presenting several functions over one Link.
- "All functions must be contiguous." Function 0 must be present; the rest need not be packed. A device may present 0, 2, and 5. Software that stops probing at the first absence misses the rest.
- "All functions must have identical resources." Each function requests and receives its own resources. What those are is a per-function property, and Chapter 7.8 covers how they are assigned.
- "A write to one function may freely modify shared state." State that software believes belongs to a function must be modified only by accesses to that function. Sharing underneath is fine; sharing that is visible as one function altering another is an isolation bug.
- "If function 0 works, every function works." Function 0 working exonerates the shared path — which is genuinely useful — and says nothing about function-local decode, initialisation, or state. That is exactly why a single missing function is such a well-localised symptom.
- "One function missing means the device is absent." The device is demonstrably present, since its other functions responded. The absence is scoped to one function, and §12's table says where to look.
- "BDF identifies a physical card." It identifies a software-visible function. One card may hold several BDF identities, and reasoning from a BDF to a physical object is the mistake this chapter's first section exists to prevent.
- "Multifunction and SR-IOV are the same thing." They are different mechanisms. This chapter covers conventional multifunction devices — several functions presented at one device position. SR-IOV is a distinct virtualisation mechanism with its own identity model — it builds on ARI rather than on the conventional interpretation taught here — and it is outside this curriculum's current scope rather than covered elsewhere in it. Treating the two as interchangeable will produce wrong conclusions about both.
14. Understanding Check
15. What's Next
Three coordinates, one identifier, and a complete answer to "what is this thing called."
Chapter 7.7 — Configuration Access answers the question that has been deferred since Chapter 7.1: how an identity becomes an actual access. How a host turns 4:00.1 into an operation the hardware performs, what mechanism carries it, how it is delivered through the hierarchy the last three chapters numbered, and what a component does with it on arrival.
Chapter 7.8 — Resource Allocation then closes Module 7 with the stage that turns a discovered, identified, numbered function into a usable one: determining what each function requires and assigning it.