PCIe · Module 8
Configuration Mechanism — The Standardised Per-Function Interface
What configuration space is as an architectural mechanism, why it is not MMIO, why every function has its own context, and how register-access behaviour — read-only, read-write, write-one-to-clear, byte enables, side effects — is actually built in RTL.
Module 7 used configuration space in every chapter and never said what it is. Discovery read it to find functions. Numbering wrote it to establish bus ranges. Multifunction discovery read it to learn whether to keep probing. Resource allocation wrote it to program an assignment.
Eight chapters of mechanism resting on something never described.
What is PCIe configuration space as an architectural mechanism, and how does each function expose standardised control and status state to software?
1. Configuration Space Is Not MMIO
This distinction causes more confusion than anything else in this area, so it comes first.
| Configuration space | MMIO / resource space | |
|---|---|---|
| What it is for | identifying, configuring, enabling, and inspecting a function | the function's actual operational registers and data |
| How it is reached | configuration accesses, addressed by identity + register offset (Chapter 7.7) | memory accesses, addressed by an assigned address range |
| When it works | as soon as the function is accessible, before any allocation | only after resources are assigned (Chapter 7.8) |
| Layout | standardised across every function from every vendor | entirely device-specific |
| Who defines the content | the specification | the device designer |
The ordering dependency is the point. Configuration space must work before resource allocation, because resource allocation is performed through configuration space. If configuration space required an assigned address, nothing could ever be configured — the system would need an address to assign an address.
That is why the two mechanisms exist separately, and why a function's configuration space reading perfectly tells you nothing about whether its MMIO works (Chapter 7.8 §11).
2. One Context Per Function
Chapter 7.6 established that a device position may present several software-visible functions. Configuration space is where that becomes concrete:
Each function has its own configuration space. A device presenting three functions exposes three configuration contexts.
Not three copies of a device's state — three independent contexts, each identified separately, each configured separately, each bound to its own driver.
And the hardware underneath is shared. One Link, one physical layer, one configuration front end receiving accesses before anything knows which function they are for. The separation software relies on is produced by the decode point and the write enables it drives, exactly as Chapter 7.6 §5 described.
This chapter's §11 asserts that separation over register-access behaviour, which is where it is most easily broken: a shared register bank whose addressing aliases between functions has separate storage on paper and none in practice.
3. What Configuration Space Contains
At the level this chapter needs — categories, not fields:
Identity. What this function reports itself to be, which is how software matches a driver to it.
Control. Enables and settings that determine what the function is permitted to do.
Status. Reported conditions, including errors that have occurred.
Resource description. What the function requires and where it has been assigned.
Capabilities. A means of exposing optional and extended features beyond the base structure.
4. Standardised Interface, Varied Implementation
The specification defines software-visible behaviour: what software sees when it reads, what happens when it writes, and what the structure means.
It does not define how that behaviour is produced. An implementation may use:
- a conventional register bank with a decode,
- state distributed across the modules that own it, with a read multiplexer gathering it,
- generated decode from a machine-readable description,
- shared common logic with per-function state, as Chapter 7.6 described,
- or any mixture.
5. Microarchitecture
Illustrative configuration-register architecture — not a normative register-map layout and not a required partitioning.
The structural observations worth carrying:
Decode and policy are separate steps. Decode says which location. Policy says what this access is permitted to do to it — and those are different questions with different answers per location.
The write path fans out; the read path fans in. They are not mirror images, which is why building them as one bidirectional structure tends to go wrong.
Side effects are a first-class path. A write that acts rather than stores does not flow through the state block at all, and treating every write as a store is §9's misconception.
6. RTL — Register Access Policy
// COMPILE-TIME. Generic register access behaviours — NOT a claim about which
// PCIe configuration fields use which behaviour.
package cfg_reg_pkg;
typedef enum logic [1:0] {
REG_RO = 2'd0, // writes ignored; value sourced from elsewhere
REG_RW = 2'd1, // writes store, subject to byte enables
REG_W1C = 2'd2, // writing 1 clears; hardware sets
REG_RSVD = 2'd3 // writes ignored, reads return zero
} reg_policy_e;
endpackage// SYNTHESIZABLE. One register location with a configurable access behaviour.
// Generic register-bank design — NOT a PCIe field model.
module cfg_reg_slice
import cfg_reg_pkg::*;
#(
parameter int DATA_W = 32,
parameter logic [31:0] RESET_VAL = 32'h0
) (
input logic clk,
input logic rst_n,
input reg_policy_e policy,
// Software-side access.
input logic wr_en,
input logic [DATA_W-1:0] wr_data,
input logic [DATA_W/8-1:0] wr_be,
// Hardware-side condition, used only by W1C locations.
input logic [DATA_W-1:0] hw_set,
// Value software observes on a read.
output logic [DATA_W-1:0] rd_data,
// Stored value, for locations that store.
output logic [DATA_W-1:0] value
);
// Byte-enable merge: replace only the enabled bytes, keep the rest.
// A write that ignores byte enables silently overwrites neighbouring fields
// that happen to share the same word — a very common configuration bug.
function automatic logic [DATA_W-1:0] apply_be(
input logic [DATA_W-1:0] old_v,
input logic [DATA_W-1:0] new_v,
input logic [DATA_W/8-1:0] be);
apply_be = old_v;
for (int b = 0; b < DATA_W/8; b++)
if (be[b]) apply_be[b*8 +: 8] = new_v[b*8 +: 8];
endfunction
logic [DATA_W-1:0] value_q;
assign value = value_q;
// Reserved locations read as zero regardless of what is stored.
assign rd_data = (policy == REG_RSVD) ? '0 : value_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
value_q <= DATA_W'(RESET_VAL);
end else begin
logic [DATA_W-1:0] next;
logic [DATA_W-1:0] clear_mask;
next = value_q;
if (wr_en) begin
case (policy)
REG_RW: begin
next = apply_be(value_q, wr_data, wr_be);
end
REG_W1C: begin
// Bits written as 1, in byte-enabled bytes only, are cleared.
// Building the mask through apply_be against zero means a byte
// that is not enabled contributes no clear — which is the whole
// reason byte enables matter for W1C.
clear_mask = apply_be('0, wr_data, wr_be);
next = value_q & ~clear_mask;
end
default: begin
// REG_RO and REG_RSVD: the write is ignored. Ignoring is a
// DEFINED behaviour here, not an omission — see §8.
next = value_q;
end
endcase
end
// PRECEDENCE, STATED EXPLICITLY: for W1C locations a hardware set is
// applied AFTER the software clear, so hardware wins when both occur in
// the same cycle. The reasoning: the condition genuinely happened, and
// losing it would report a clean status for a real event. The opposite
// choice is defensible for other purposes — what is not defensible is
// leaving the precedence undefined, because then the behaviour depends
// on synthesis rather than on design.
if (policy == REG_W1C)
next = next | hw_set;
value_q <= next;
end
end
endmoduleClassification: synthesizable, with a compile-time enumeration.
What it models: the per-location access behaviour that the write path must consult, and the byte-granular merge that a partial write requires.
What it teaches — four things:
- Byte enables are not optional. A write that ignores them overwrites every field sharing the word. Several unrelated fields commonly share one word, so this bug corrupts things the write had nothing to do with.
- W1C needs the byte enables too. A byte that is not enabled must contribute no clear. Building the clear mask through the same merge function makes that automatic instead of a separate case to remember.
- Ignoring a write is a defined behaviour. Read-only and reserved locations discard writes and return a normal completion. That is not an error path — §8's misconception is exactly that a "successful" write must have changed something.
- Precedence must be decided, not discovered. The comment states which side wins when a hardware set and a software clear coincide, and why. An implementation without an explicit rule has one anyway — chosen by whichever assignment happens to come last.
Deliberately simplified: one location; three behaviours plus reserved, when real interfaces use many more; hw_set is a generic condition input; and there is no modelling of fields narrower than a byte sharing a byte, which real layouts contain and which makes the merge more complex.
Production implication: a real configuration block supports many more access behaviours, handles fields at bit granularity within shared bytes, sources read-only values from wherever the state actually lives rather than from a local flop, and must define behaviour for every location in the space including unimplemented ones.
7. RTL — Assembling a Bank
// SYNTHESIZABLE. A small illustrative bank showing decode, policy lookup, and
// the separate read and write paths of §5.
// NOT a PCIe configuration-space layout — offsets and behaviours here are
// invented for the example.
module cfg_reg_bank
import cfg_reg_pkg::*;
#(
parameter int NUM_REGS = 4,
parameter int DATA_W = 32
) (
input logic clk,
input logic rst_n,
// Access, after identity has already been matched (Chapters 7.5-7.6).
input logic req_valid,
output logic req_ready,
input logic req_write,
input logic [IDX_W-1:0] req_index, // decoded location index
input logic [DATA_W-1:0] req_wdata,
input logic [DATA_W/8-1:0] req_be,
input logic req_in_range, // decode said this exists
input logic [DATA_W-1:0] hw_set [NUM_REGS],
output logic rsp_valid,
input logic rsp_ready,
output logic [DATA_W-1:0] rsp_rdata,
output logic rsp_unsupported
);
localparam int IDX_W = (NUM_REGS <= 1) ? 1 : $clog2(NUM_REGS);
// Per-location behaviour. In a real design this table is generated from the
// same description that produces the documentation, so the two cannot drift.
localparam reg_policy_e POLICY [NUM_REGS] = '{
REG_RO, REG_RW, REG_W1C, REG_RSVD
};
logic [DATA_W-1:0] rd_data [NUM_REGS];
// Ready 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;
// WRITE PATH: one enable per location, and only the addressed one asserts.
// An out-of-range access enables nothing at all, so an access to a location
// that does not exist has no side effect anywhere (§11 P5).
logic [NUM_REGS-1:0] wr_sel;
always_comb begin
wr_sel = '0; // no latch: assigned first
if (accept && req_write && req_in_range)
wr_sel[req_index] = 1'b1;
end
generate
for (genvar r = 0; r < NUM_REGS; r++) begin : g_reg
cfg_reg_slice #(.DATA_W(DATA_W)) u_reg (
.clk (clk), .rst_n (rst_n),
.policy (POLICY[r]),
.wr_en (wr_sel[r]),
.wr_data (req_wdata),
.wr_be (req_be),
.hw_set (hw_set[r]),
.rd_data (rd_data[r]),
.value ()
);
end
endgenerate
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
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 <= !req_in_range;
// READ PATH: gather from the addressed location. Reads are taken
// BEFORE this cycle's write lands, because rd_data reflects the
// registered value — a design choice that must be stated, since
// read-after-write ordering within one access is otherwise ambiguous.
rsp_rdata <= req_in_range ? rd_data[req_index] : '0;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: the two paths of §5 built explicitly — a fan-out of enables where only the addressed location asserts, and a fan-in multiplexer for reads — plus the read-after-write ordering choice, which is stated rather than left to whatever the code happens to do.
Deliberately simplified: four locations with invented behaviours; the offset-to-index decode is an input rather than modelled; and no side-effect path, which §8 discusses but does not build.
Production implication: the policy table should be generated from the same source as the documentation so they cannot diverge; unimplemented locations across the whole space need defined behaviour, not only the ones in a small table; and read-after-write ordering must match what the specification requires rather than what the implementation happened to do.
8. Writes That Do Not Store
Not every configuration write means "store this value."
Some store. The read-write case.
Some clear. Writing a particular value clears a condition rather than setting the register to that value — the write-one-to-clear behaviour above, where writing 1 produces 0.
Some trigger. A write initiates an action, and there may be nothing to read back.
Some are ignored. Read-only and reserved locations discard the write and complete normally.
9. Reset Values
Software reads configuration space before it writes anything, so what a function exposes at reset is part of its interface rather than an internal detail.
Every software-visible location must have a defined value from the moment it is readable. Not a value that settles shortly afterwards — one that is correct at the first access.
This connects directly to Chapter 7.2: a function that becomes accessible before its configuration state has reached its defined values answers with a snapshot of a transition, and the host believes it.
The specific hazard for RTL: uninitialised or X state must never reach a configuration response. In simulation an X propagates and is at least visible; in silicon it is whatever the flop powered up as, and the host reads it as a real value. A read multiplexer that can select an uninitialised source has created a software-visible undefined value.
What this chapter does not do is list reset values for any PCIe field. Those are specification detail belonging to the field-specific chapters. The architectural requirement — defined before exposed — is what generalises.
10. Function Isolation Applies Here Too
Chapter 7.6 asserted that a configuration write to one function must not modify another's state. Everything in this chapter sits behind that decode point.
The bank in §7 is one function's context. In a multifunction device there is one such context per function, and the write-enable fan-out has an outer layer: the function selection of Chapter 7.6 §6, then the location selection of §7 here.
Where the two can go wrong together is the register bank's addressing. If contexts are implemented as one array indexed by a combination of function and location, an arithmetic error in that index aliases between functions — separate storage in the source, shared storage in effect. That failure passes every single-function test and is Chapter 7.6's P4 scenario, reached through this chapter's addressing rather than through its decode.
11. Assertions
// SVA over cfg_reg_slice and cfg_reg_bank. Implementation invariants for
// THESE designs — not PCIe protocol requirements.
// SAFETY — P1: a read-only location never changes because of a write. The
// defining property of the behaviour.
property p_ro_never_written;
@(posedge clk) disable iff (!rst_n)
(policy == REG_RO && wr_en) |=> $stable(value);
endproperty
a_ro_stable : assert property (p_ro_never_written);
// CORRECTNESS — P2: a read-write location changes only where byte-enabled.
// Catches a write path ignoring byte enables, which corrupts every other
// field sharing the word.
generate
for (genvar b = 0; b < DATA_W/8; b++) begin : g_be
property p_unenabled_byte_stable;
@(posedge clk) disable iff (!rst_n)
(policy == REG_RW && wr_en && !wr_be[b]) |=> $stable(value[b*8 +: 8]);
endproperty
a_be_respected : assert property (p_unenabled_byte_stable);
end
endgenerate
// CORRECTNESS — P3: a W1C bit clears only where a 1 was written in an enabled
// byte, and only when hardware is not setting it in the same cycle. This is
// the documented precedence made checkable.
property p_w1c_clears_correctly;
@(posedge clk) disable iff (!rst_n)
(policy == REG_W1C && wr_en)
|=> ((value & ~$past(hw_set))
== ($past(value) & ~$past(apply_be('0, wr_data, wr_be)) & ~$past(hw_set)));
endproperty
a_w1c_correct : assert property (p_w1c_clears_correctly);
// SAFETY — P4: a hardware set is never lost to a coincident software clear.
// The precedence rule of §6. Without it, a condition that occurred in the
// same cycle as a clear is silently discarded and status reads clean.
property p_hw_set_wins;
@(posedge clk) disable iff (!rst_n)
(policy == REG_W1C) |=> ((value & $past(hw_set)) == $past(hw_set));
endproperty
a_hw_set_wins : assert property (p_hw_set_wins);
// SAFETY — P5: an out-of-range access produces no write anywhere. Software
// probes locations that may not exist, so those probes must be inert.
property p_out_of_range_inert;
@(posedge clk) disable iff (!rst_n)
(accept && !req_in_range) |-> (wr_sel == '0);
endproperty
a_oor_inert : assert property (p_out_of_range_inert);
// SAFETY — P6: at most one location is written per access.
property p_single_write_target;
@(posedge clk) disable iff (!rst_n)
$onehot0(wr_sel);
endproperty
a_one_target : assert property (p_single_write_target);
// SAFETY — P7: a read never writes. Catches an enable derived from `accept`
// without qualifying on req_write, which turns every read into a write of
// whatever happened to be on the data bus.
property p_read_has_no_write;
@(posedge clk) disable iff (!rst_n)
(accept && !req_write) |-> (wr_sel == '0);
endproperty
a_read_no_write : assert property (p_read_has_no_write);
// CORRECTNESS — P8: the response carries the addressed location's value.
generate
for (genvar r = 0; r < NUM_REGS; r++) begin : g_rd
property p_response_from_addressed;
@(posedge clk) disable iff (!rst_n)
(accept && !req_write && req_in_range && req_index == IDX_W'(r))
|=> (rsp_rdata == $past(rd_data[r]));
endproperty
a_read_correct : assert property (p_response_from_addressed);
end
endgenerate
// CONSERVATION — P9: one accepted access produces one response, held until
// taken.
property p_one_response_held;
@(posedge clk) disable iff (!rst_n)
(rsp_valid && !rsp_ready) |=> (rsp_valid && $stable(rsp_rdata)
&& $stable(rsp_unsupported));
endproperty
a_response_held : assert property (p_one_response_held);
// SAFETY — P10: reset establishes a defined exposed value. §9's requirement,
// asserted at the interface where software would observe a violation.
property p_reset_defines_exposed;
@(posedge clk)
!rst_n |=> (!rsp_valid && !$isunknown(rd_data[0]));
endproperty
a_reset_defined : assert property (p_reset_defines_exposed);P7 catches a bug with an alarming blast radius and a trivial cause. Deriving the write enable from accept alone — forgetting && req_write — turns every configuration read into a write of whatever the data bus carries. Since enumeration reads configuration space extensively before writing anything, the damage happens during discovery, to locations nobody intended to touch, and the symptom appears much later as inexplicable configuration state.
P4 is the precedence rule as an assertion, and it matters because the failure is silent. If a software clear beats a coincident hardware set, a condition that genuinely occurred is discarded and status reads clean. Nothing reports the loss. The window is one cycle wide, so it happens rarely and irreproducibly — which is the worst combination for debugging.
P2 is generated per byte deliberately. An aggregate property over the whole word would report that something changed without saying which byte, and byte-enable bugs are usually specific to particular byte positions — an off-by-one in the loop, or a reversed bit order in the enable vector.
P10 asserts §9's requirement where it can be observed. An X reaching a read path is visible in simulation and becomes an arbitrary real value in silicon, which software then treats as meaningful.
12. Verification
Monitors observe: the access handshake with its index, write flag, data and byte enables; the in-range indication; every location's stored value and read value; the hardware set inputs; and the response.
The scoreboard independently models each location's behaviour from its own policy table and its own byte-enable merge — not by instantiating or calling the design's apply_be. A checker sharing the design's merge function agrees with the design about a byte-enable bug, and P2 becomes the only thing catching it.
Scenarios:
- Aligned read and write to a read-write location. Baseline.
- Every byte-enable pattern. All 16 combinations for a 32-bit word. Verify enabled bytes update and unenabled bytes hold (P2). Not sampled — enumerated, because the space is small and byte-enable bugs are position-specific.
- Write to a read-only location. Verify the value is unchanged and the access still completes normally — §8's point made executable.
- Write to a reserved location. Verify the write is ignored and reads return zero.
- W1C: write 1 to a set bit. Verify it clears.
- W1C: write 0 to a set bit. Verify it does not clear — the case that catches a W1C implemented as an ordinary write.
- W1C: write 1 to an already-clear bit. Verify no change and no error.
- W1C with byte enables. Write 1s across the word with only some bytes enabled. Verify only enabled bytes clear.
- W1C clear coincident with a hardware set. Drive
hw_setin the same cycle as the clearing write. Verify the hardware set wins (P4) — this must be constructed deliberately; it will not occur by chance. - Read of every location. Verify each returns its own value (P8), which catches a read multiplexer ignoring the index.
- Out-of-range access, read and write. Verify the unsupported indication, and verify no location changed anywhere (P5).
- A read while the data bus carries a plausible value. Verify nothing is written (P7) — the scenario that catches the missing
req_writequalifier. - Back-to-back accesses. A new access presented in the cycle the previous response is taken.
- Response backpressure. Hold
rsp_readylow. Verify stability (P9) and that no new access is accepted. - Reset with every location written. Verify all return to their defined values and nothing reads as unknown (P10).
- Multifunction isolation. With one bank per function, write each function's locations in turn and read back every function after each write. This is Chapter 7.6's P4 reached through this chapter's addressing — the aliasing failure §10 describes.
Coverage should include: every policy class crossed with read and write; all byte-enable patterns; W1C with each bit set and clear, with and without a coincident hardware set; every location index as the read and write target; in-range and out-of-range; and reset from each distinct state.
13. Debugging
Symptom: a configuration write completes, but the field does not change
The first thing to establish is whether this is a fault at all, because several correct behaviours produce it (§8).
Candidates, cheapest first:
- The location is read-only or reserved. Writes are ignored by definition, and the completion is correct. Check the location's defined behaviour before anything else.
- The byte enables did not cover the field. A write whose enables miss the field's byte changes nothing there, correctly. This is common when software writes a whole word but the field sits in a byte the access did not enable.
- The behaviour is write-one-to-clear and a 0 was written. Writing 0 to a W1C bit is defined to leave it alone. Expecting it to set the bit is a misreading of the behaviour.
- The access reached a different function. Chapter 7.6's decode. The write landed correctly — on another function's context.
- The access reached a different location. An offset decode error within the right function.
- The write path is genuinely broken. The enable is not reaching the location, or is qualified by something wrong.
The measurement that separates them. Read back the whole word, not the field. If a neighbouring field changed and the target did not, the byte enables are inverted or misaligned. If nothing in the word changed, and the location is read-write, the enable is not arriving — candidates 4, 5, or 6. If a different location changed, the decode is wrong.
Why candidates 1–3 come first. They cost one lookup each and account for a large share of these reports. Investigating the write path before checking the location's defined behaviour is the expensive path.
Symptom: a write to function 1 changes function 0's state
This is Chapter 7.6's isolation failure, and this chapter adds a candidate that chapter did not have.
The candidate set:
- Function decode is missing or not one-hot — Chapter 7.6 §6, caught by its P1 and P4.
- The register bank's addressing aliases between functions — §10's failure. Contexts implemented as one array indexed by a combination of function and location, with an arithmetic error making two functions map to overlapping entries. Storage is separate in the source and shared in effect.
- State intended to be per-function is genuinely shared — an architectural error rather than a coding one, and the hardest to see in review because the code is consistent with itself.
How to distinguish the first from the second. Observe the function selection and the location index together on the failing write. If the function selection is correct and the wrong context changed, the fault is in the addressing, not the decode — and the two live in different modules.
Why this needs assertions rather than tests. The check is negative: nothing else changed. A test that writes function 1 and reads function 1 back passes. Only checking every other context after every write finds it, which is what Chapter 7.6's pairwise P4 does automatically.
14. Common Misconceptions
- "Configuration space is the same as BAR or MMIO space." They are different address spaces reached by different mechanisms at different times. Configuration space is reached by identity and works before any allocation; MMIO is reached by an assigned address and works only after (Chapter 7.8).
- "Every configuration register is ordinary read-write storage." Some store, some clear on a write of one, some trigger an action, and some ignore writes entirely. What a location does on access is a per-location property that the write path must consult.
- "One physical device has one configuration space." Each function has its own. A device presenting three functions exposes three independent contexts, separately identified and separately configured (Chapter 7.6).
- "An unsupported write should cause a fatal error." Ignoring a write to a read-only or reserved location is defined behaviour, and the access completes normally. Treating every ignored write as fatal would make enumeration itself an error, since software writes to locations whose behaviour it does not yet know.
- "A register offset alone identifies the target." The offset identifies a location within one function's configuration space. Reaching that function requires bus, device, and function first (Chapters 7.4–7.6). Offset without identity names a location in an unspecified context.
- "A successful write means the software-visible value must change." Success means the access was accepted and acted upon according to the location's behaviour. For a read-only location, ignoring the write is acting on it. Write-then-read-unchanged is expected in several correct cases.
- "The configuration mechanism requires one literal register file." The specification defines software-visible behaviour, not implementation. Read paths commonly gather values from wherever state already lives, and many locations have no storage behind them at all.
- "Reset values are an implementation-private detail." Software reads configuration space before writing anything, so what a function exposes at reset is part of its interface. Values must be defined from the first access, and undefined state must never reach a response.
- "Byte enables do not matter for configuration writes." A write ignoring them overwrites every field sharing the word, and several unrelated fields commonly share one. For write-one-to-clear locations the enables also determine which bytes are eligible to clear.
15. Understanding Check
16. What's Next
Configuration space is a standardised interface with defined access behaviour. This chapter deliberately showed no field of it.
Chapter 8.2 — Device IDs and Chapter 8.3 — Vendor IDs open the identity fields, and how software uses them to match a driver to a function. Chapter 8.4 — Command Register and Chapter 8.5 — Status Register cover the control and status categories §3 named. Chapter 8.6 — Configuration Header assembles the layout, including how the structure differs between the function kinds Module 7 has been distinguishing all along.
Then Module 9 takes up the resource description that Chapter 7.8 treated as given: how a function states what it requires, how software discovers a region's size, and how an assignment is programmed.