Skip to content

PCIe · Module 9

What BARs Are — A Window's Address, Not Its Contents

A Base Address Register lives in configuration space and describes where a resource window should be reached; it is not the storage behind that window. How configuration-time programming feeds runtime address decode, and why a correct BAR still guarantees nothing.

Chapter 8.6 placed Base Address Registers in the header and said nothing about them beyond where they sit and how many each role has.

Module 9 is about what they do. It has to start by correcting what most people think they are.

What problem does a Base Address Register solve, and why is a BAR not the device's memory?

1. The Model to Discard First

The common wrong belief is direct:

"The BAR contains the device's registers."

It does not. A BAR is 32 bits of configuration space. A device's register block may be kilobytes or megabytes. They are not the same object and never could be.

What actually happens is a chain, and every link is a separate thing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
BAR (configuration space)
   → an address range assigned by the host
   → the Function's address decoder
   → a resource-window hit
   → the internal registers, memory, or queues behind that window

The BAR sits at the start of that chain and participates in where the window lives. The registers sit at the end and are what the window contains.

2. What Problem It Solves

Chapter 7.8 established the allocation problem: several Functions each need address space, the space is finite, and placement must be non-overlapping, aligned, and inside every parent window.

It also established the division of labour — a Function states what it needs; the host decides where it goes.

That division needs a mechanism at the Function's end. Something must let the host tell a Function where its window has been placed, and let the Function decode accesses to that placement afterwards.

The BAR is that mechanism: a configuration-space control point through which a resource window's placement is communicated and retained.

Without it, a Function's address would have to be fixed at design time — which would make two identical cards in one system impossible, and is exactly the problem the whole configuration model exists to avoid.

3. Where This Sits in the Lifecycle

The sequence, at the level this chapter needs:

  1. The Function is discovered and made accessible (Chapters 7.1–7.7).
  2. The host determines the Function's resource requirement.
  3. The host chooses an address satisfying the constraints of Chapter 7.8.
  4. The host programs the BAR with that placement.
  5. The host enables the relevant address space in the Command Register (Chapter 8.4).
  6. Operational traffic can now reach the resource.

Steps 4 and 5 are independent, and that is worth restating because it is Chapter 8.4's most common failure. Programming a BAR does not enable anything. Enabling the address space does not place anything. Both must happen, in either order, and a system with one and not the other has a Function that is silent for a completely explicable reason.

4. The Two Paths

Two separate paths in a Function. The configuration path runs from a configuration access through the BAR in configuration space to programmed window state. The operational path runs from a host memory access through the address decoder, which consumes the programmed window state and the Memory Space Enable permission, to a resource hit and then to the internal register block. The configuration path never reaches the internal register block.Configurationaccessidentity + offset (Ch7.7)BAR — configuration spaceBAR —configuration…a control point, notstorageProgrammed windowstatewhere the windowlivesHost memoryaccessan assigned addressAddress decoderconsumes the windowstateResource hitgated by Memory SpaceEnableInternal resourceregisters, memory,queues12
Figure 1 — the configuration path that programs a window and the operational path that uses it. They meet only at the address decoder, which consumes the programmed value. A configuration write changes where the window is; it never reaches the resource behind it.

Trace the two paths and notice they never join except at the decoder.

The configuration path ends at window state. The operational path ends at the resource. The decoder is the only place the first influences the second, and it does so by supplying a comparison operand — not by carrying data.

That is the diagram's whole argument. There is no arrow from the BAR to the internal resource, because there is no such path. A configuration write changes the decoder's operand and nothing else.

5. Alignment, and Why Decoders Care

A resource window generally cannot be placed at an arbitrary address. Windows carry alignment constraints, and Chapter 7.8 §6 showed what those cost an allocator.

Here is what they buy. With a window whose size is a power of two and whose base is aligned to that size, the decode becomes a masked comparison:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
hit  ⟺  (address & mask) == (base & mask)      where mask = ~(size − 1)

No subtraction, no range comparison, no carry chain across the full address width — one masked equality. For a decoder that must evaluate on every inbound access, that difference is significant in both area and timing.

6. What a Window Does Not Cover

A window is a claim over part of the address space. Everything outside it is equally part of the Function's contract, and it is the half that gets forgotten.

A decoder answers two questions, not one. Is this address mine? and — implicitly — what happens if it is not? The second has to be answered, because the first will be false far more often than it is true.

The dangerous failure is the opposite one: a window that claims too much. A decoder that hits on addresses it was not assigned does not fail quietly. It answers accesses intended for a different device — and the symptom then appears at that other device, which is behaving correctly.

That is why §8 refuses to decode on an illegal window rather than doing something approximate with it. A mask derived from a non-power-of-two size, or a base that is not aligned to its size, describes a region nobody allocated. Claiming it would be worse than claiming nothing:

Decoder behaviour on a bad windowConsequence
Claims nothing (window_legal low)This Function is silent — visible, local, and traceable to this Function
Claims an approximate regionAnother device's accesses are absorbed — the symptom appears somewhere else entirely

Silence is a debuggable failure. Over-claiming is a system-level one, and P3 exists to make the choice structural rather than incidental.

7. Microarchitecture — Configuration Time and Run Time

The clean way to hold §4's separation in a design is to notice that the two paths run at different times as well as through different logic.

Configuration time. A configuration access reaches the header decode (Chapter 8.6), updates the window state, and completes. This happens rarely — during enumeration, and occasionally afterwards.

Run time. An inbound access arrives, the decoder compares it against the window state, and a hit routes it to the resource. This happens constantly.

They share exactly one thing: the window state. Configuration writes it; the decoder reads it. Nothing else crosses.

Illustrative Endpoint organisation — not a required partitioning. What generalises is the shape: a small amount of configuration-owned state, consumed by a datapath that never writes it.

8. RTL — A Programmable Address Window

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Masked-comparison decode for one programmable address
// window. Generic window arithmetic — NOT PCIe BAR-format logic.
module address_window_decode #(
  parameter int ADDR_W = 64
) (
  // Window state, owned by the configuration path (§9).
  input  logic [ADDR_W-1:0] win_base,
  input  logic [ADDR_W-1:0] win_size,   // expected to be a power of two
  // win_valid means: the configuration sequence has committed a base AFTER
  // the current size value — i.e. the two fields belong together. It does NOT
  // by itself mean the window is usable; window_legal below still requires a
  // nonzero power-of-two size and an aligned base.
  input  logic              win_valid,
 
  // Permission, from the Command Register (Chapter 8.4). The window can be
  // programmed and the Function still not permitted to serve it.
  input  logic              space_enable,
 
  // Runtime access.
  input  logic [ADDR_W-1:0] req_addr,
 
  output logic              window_legal,
  output logic              hit
);
 
  // A power-of-two size is a PRECONDITION of the mask trick, not an
  // assumption to make silently. With a non-power-of-two size, ~(size-1) is
  // not a contiguous mask and the comparison below would produce a
  // well-formed, meaningless answer.
  wire size_is_pow2 = (win_size != '0) && ((win_size & (win_size - 1'b1)) == '0);
 
  // The base must also be aligned to the size, or the masked comparison
  // matches a window that starts somewhere other than where it was placed.
  wire base_aligned = ((win_base & (win_size - 1'b1)) == '0);
 
  assign window_legal = win_valid && size_is_pow2 && base_aligned;
 
  // MASKED COMPARISON (§5). One equality across the significant bits — no
  // subtraction, no range compare, no carry chain, and no end-address
  // arithmetic to overflow.
  wire [ADDR_W-1:0] win_mask = ~(win_size - 1'b1);
 
  assign hit = window_legal
            && space_enable
            && ((req_addr & win_mask) == (win_base & win_mask));
 
endmodule

Classification: synthesizable.

What it teaches — three things:

  1. The masked comparison is why alignment matters. With size a power of two and base aligned, membership is one equality. The preconditions are checked, not assumed, because ~(size-1) on a non-power-of-two produces a mask with holes and a confidently wrong answer.
  2. Overflow cannot arise. There is no end address to compute, so the base + size hazard Chapter 7.8 §6 spends a section on does not exist in this formulation. That is a real advantage of masked decode over range decode, and it is why hardware decoders use it.
  3. Permission and placement are separate operands. space_enable and window_legal are independent inputs to hit. A programmed window with the space disabled produces no hit, and so does an enabled space with no programmed window — Chapter 8.4's independence made structural.

Deliberately simplified: one window; power-of-two sizes only; no address-space kind; no BAR encoding; and win_size is a plain value rather than something derived from a Function's requirement.

Production implication: a real Function decodes several windows, derives each window's mask from whatever its BAR implementation actually encodes (Chapter 9.2), handles the address-space kinds separately, and must define behaviour for an access that matches no window.

9. RTL — Configuration State the Datapath Only Reads

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. The window state, written by the configuration path and
// read by the decoder. Shown separately to make the ownership boundary of
// §7 explicit in the module structure rather than only in prose.
// The configuration interface is GENERIC — not a BAR write's real semantics.
module window_program_state #(
  parameter int ADDR_W = 64
) (
  input  logic              clk,
  input  logic              rst_n,
 
  // Configuration path. Writes here happen rarely.
  //
  // INTERFACE CONTRACT: cfg_wr_base and cfg_wr_size are MUTUALLY EXCLUSIVE.
  // They share one cfg_wdata bus, so a cycle asserting both would ask for a
  // base and a size that are the same number — which is not a request this
  // interface can express. Behaviour under a violation is UNDEFINED and is
  // not part of the contract; P9 checks the requester instead of the design
  // inventing a resolution. (Illustrative configuration interface — this is
  // not how a BAR is really written.)
  input  logic              cfg_wr_base,
  input  logic              cfg_wr_size,
  input  logic [ADDR_W-1:0] cfg_wdata,
 
  // Runtime path. Reads here happen constantly — and ONLY reads. There is no
  // write port from the operational side, which is what makes "a resource
  // access cannot change the mapping" structural rather than a promise.
  output logic [ADDR_W-1:0] win_base,
  output logic [ADDR_W-1:0] win_size,
  output logic              win_valid
);
 
  logic [ADDR_W-1:0] base_q, size_q;
  logic              valid_q;
 
  assign win_base  = base_q;
  assign win_size  = size_q;
  assign win_valid = valid_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      base_q  <= '0;
      size_q  <= '0;
      // Not valid until programmed. A window that decoded on its reset value
      // would claim an address range nobody assigned it — the same hazard
      // Chapter 7.4 guards with range_valid.
      valid_q <= 1'b0;
    end else begin
      // Mutually exclusive by contract (see the port declarations), so these
      // two branches never both fire in the same cycle.
      if (cfg_wr_size) begin
        size_q  <= cfg_wdata;
        valid_q <= 1'b0;      // a size change invalidates until re-based
      end
      if (cfg_wr_base) begin
        base_q  <= cfg_wdata;
        valid_q <= 1'b1;      // base committed AFTER the current size
      end
    end
  end
 
endmodule

Classification: synthesizable.

Register semantics:

DimensionBehaviour
Resetbase and size cleared; win_valid low, so nothing decodes
Read (runtime)the decoder samples the current values continuously
Writeconfiguration path only
Write strobescfg_wr_base and cfg_wr_size are mutually exclusive by contract — they share one data bus
Byte enablesnot modelled here; a real BAR write composes Chapter 8.1's merge
Side effectschanging the size invalidates the window until a base is written
Hardware may updateno
Software may updateyes, through configuration

What it teaches: that the resource path has no write port into the mapping. The separation in §4's diagram is not a drawing convention — it is the absence of a wire. An operational access physically cannot alter where the window is, and a configuration write physically cannot alter what the window contains.

On the simultaneous case that is legal. A configuration write and a runtime access can occur in the same cycle. This model's contract is the simplest defensible one: the decoder samples the current registered values, so an access in the write cycle sees the old window and an access in the following cycle sees the new one. The transition is atomic per field but not across fields — which is why valid_q clears on a size write, so a half-reprogrammed window never decodes. A production design that must reprogram while traffic is in flight needs an explicit quiesce or a staged commit, exactly as Chapter 7.4 §9 built for bus ranges.

Deliberately simplified: a generic configuration interface rather than BAR write semantics; one window; two mutually exclusive field strobes on a shared data bus; no byte enables; and no protection against reprogramming under active traffic beyond the validity rule.

Production implication: a real implementation applies whatever write semantics its BAR encoding defines, handles several windows, composes byte-enable merging, and defines behaviour for reprogramming while accesses are outstanding.

10. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over address_window_decode and window_program_state. Implementation
// invariants for THESE designs — not PCIe protocol requirements. Every
// property refers to explicit RTL state or inputs.
 
// SAFETY — P1: an unprogrammed window never hits. A decoder that matched on
// its reset value would claim an address range nobody assigned it.
property p_unprogrammed_never_hits;
  @(posedge clk) disable iff (!rst_n)
  !win_valid |-> !hit;
endproperty
a_no_hit_unprogrammed : assert property (p_unprogrammed_never_hits);
 
// SAFETY — P2: a hit requires the address space to be permitted. Composes
// this module with Chapter 8.4's Command Register enable.
property p_hit_requires_permission;
  @(posedge clk) disable iff (!rst_n)
  hit |-> space_enable;
endproperty
a_hit_needs_enable : assert property (p_hit_requires_permission);
 
// LEGALITY — P3: an illegal window never hits. The masked comparison is only
// meaningful for a power-of-two size with an aligned base; without this the
// decoder returns a well-formed wrong answer.
property p_illegal_window_never_hits;
  @(posedge clk) disable iff (!rst_n)
  (!size_is_pow2 || !base_aligned) |-> !hit;
endproperty
a_illegal_no_hit : assert property (p_illegal_window_never_hits);
 
// CORRECTNESS — P4: hit agrees exactly with window membership. Catches an
// inverted or off-by-one mask, which produces a window shifted or doubled in
// size while every individual access still looks plausible.
property p_hit_matches_membership;
  @(posedge clk) disable iff (!rst_n)
  (window_legal && space_enable)
    |-> (hit == ((req_addr & win_mask) == (win_base & win_mask)));
endproperty
a_hit_exact : assert property (p_hit_matches_membership);
 
// SAFETY — P5: a runtime access never modifies the window state. The
// structural claim of §9 made checkable — BAR is not storage, and a resource
// access cannot change the mapping.
property p_access_does_not_reprogram;
  @(posedge clk) disable iff (!rst_n)
  (!cfg_wr_base && !cfg_wr_size) |=> ($stable(win_base) && $stable(win_size)
                                      && $stable(win_valid));
endproperty
a_no_reprogram_by_access : assert property (p_access_does_not_reprogram);
 
// SAFETY — P6: a half-reprogrammed window does not decode. A size written
// without a matching base leaves the window invalid until re-based, so an
// access never matches a mask and base that were never intended together.
property p_size_write_invalidates;
  @(posedge clk) disable iff (!rst_n)
  (cfg_wr_size && !cfg_wr_base) |=> !win_valid;
endproperty
a_partial_reprogram_safe : assert property (p_size_write_invalidates);
 
// CORRECTNESS — P7: programming takes effect as written. Catches a capture
// path that transposes or drops a field.
property p_base_programmed_correctly;
  @(posedge clk) disable iff (!rst_n)
  cfg_wr_base |=> (win_base == $past(cfg_wdata) && win_valid);
endproperty
a_base_correct : assert property (p_base_programmed_correctly);
 
// SAFETY — P8: no decode output is ever unknown. An X on `hit` makes
// acceptance undefined in simulation and arbitrary in silicon.
property p_outputs_never_unknown;
  @(posedge clk) disable iff (!rst_n)
  !$isunknown({hit, window_legal});
endproperty
a_no_x : assert property (p_outputs_never_unknown);
 
// INTERFACE CONTRACT — P9: the two configuration write strobes are never
// asserted together. They share one cfg_wdata bus, so the combination cannot
// express a legal request and the design defines no behaviour for it.
// Checked as an ASSERT on the requester rather than resolved in the design:
// a silent priority rule would make an illegal stimulus look legal.
property p_cfg_writes_mutually_exclusive;
  @(posedge clk) disable iff (!rst_n)
  !(cfg_wr_base && cfg_wr_size);
endproperty
a_cfg_writes_exclusive : assert property (p_cfg_writes_mutually_exclusive);

P5 is the chapter's thesis expressed as a property. "A BAR is not storage" is a claim about structure, and P5 is what makes it checkable: nothing on the operational path can change the mapping. In the design shown it holds because there is no write port from that side — so the property is asserting the absence of a wire, which is precisely the kind of thing that survives until someone adds one.

P3 catches the failure mode masked decode introduces. Range comparison degrades gracefully on a strange size; masked comparison does not. ~(size-1) on a non-power-of-two produces a mask with holes, and the resulting hit is not merely wrong but wrong in a scattered, hard-to-characterise way — matching some addresses inside the intended window and some outside it. Checking the precondition costs two comparisons.

P4 catches an off-by-one in the mask. ~(size-1) versus ~size differ by one bit, and the wrong one produces a window of double or half the intended size, correctly aligned, decoding plausibly for most accesses. Comparing against the membership definition catches it on the first address near a boundary.

P6 is the sequential simultaneous-case property. Reprogramming touches two fields, and between them the window describes a placement nobody chose — exactly the transient Chapter 7.4 §9 addressed for bus ranges. Clearing validity on a size write means the intermediate state decodes nothing rather than decoding something wrong.

P9 is the concurrent simultaneous-case property, and it is a different kind of property from the rest. P1–P8 check what the design does. P9 checks what the design is allowed to be asked — it is an assumption on the requester expressed as an assertion.

Why the interface needs it at all. Splitting a window into two fields written over one shared data bus creates an input combination that carries no meaning: both strobes high asks for a base and a size equal to the same number. The design must do something electrically, and in the shown RTL it would write both registers from the same word and end with valid_q high — a plausible-looking, entirely wrong window.

Why not resolve it in the design instead. A priority rule (base wins, say) would make the illegal stimulus produce a well-formed result, and the requester bug that generated it would never surface. Asserting the contract keeps the fault where it belongs and keeps the design smaller. The rule generalises: when an input combination has no meaning, constrain it and check the constraint; do not invent a behaviour for it.

11. Verification

Monitors observe: the configuration writes and their data, the window state, the permission input, every runtime address presented, and the decode outputs.

The scoreboard independently computes expected membership from base and size directlybase <= addr < base + size in the testbench's own arithmetic — rather than by replicating the mask. Using the mask formulation in the checker would agree with the design about an off-by-one in ~(size-1), which is P4's bug.

Scenarios:

  • First address in the window. Exactly base. Must hit.
  • Last address in the window. base + size − 1. Must hit. The boundary a mask error moves.
  • One below the window and one above it. Must not hit. A doubled mask fails the second of these and nothing else.
  • Window disabled. win_valid low with a plausible base and size programmed. Verify no hit at any address (P1).
  • Space disabled. win_valid high, space_enable low. Verify no hit (P2) — Chapter 8.4's independence, tested here.
  • Both disabled, then each enabled in turn. Verify a hit requires both.
  • Non-power-of-two size. Verify window_legal clears and no address hits (P3).
  • Misaligned base. A legal size with a base that is not a multiple of it. Verify window_legal clears.
  • Zero size. Verify illegal and no hit.
  • Window at the top of the address space. Base and size placing the window's end at the maximum address. Verify the boundary still decodes — the masked form has no overflow, and this test proves it rather than assuming it.
  • Reprogram while idle. Change base, then size, then both. Verify the new window decodes and the old one does not.
  • Size written without a base. Verify the window becomes invalid and decodes nothing until re-based (P6).
  • Both write strobes asserted in one cycle — a deliberate contract violation. If the environment can check assertions, drive cfg_wr_base and cfg_wr_size high together and verify P9 fires. This is a negative test of the interface contract, so it belongs in its own check: the pass criterion is that the assertion reports, not that the design produces some particular window. Run it with assertions enabled and outside the functional regression's pass/fail accounting, since the stimulus is illegal by construction and the resulting window state is undefined.
  • Runtime access during a configuration write. Present an address in the same cycle as cfg_wr_base. Verify the outcome matches the stated contract — the access sees the old window — and that the access does not disturb the programming (P5).
  • Sustained runtime traffic with no configuration writes. Verify the window state never changes (P5), which is the executable form of "a resource access cannot alter the mapping."

Coverage should include: addresses at both window boundaries and one beyond each; window sizes across the legal power-of-two range including the smallest and one near the address-space maximum; legal and illegal sizes; aligned and misaligned bases; both permission inputs in all four combinations; reprogramming from each field; and the three legal write-strobe combinations — neither strobe, size alone, base alone. The fourth combination is excluded by contract, so it belongs in the negative test above rather than in the coverage goal; a coverage model that asks for it would be asking the environment to violate its own constraint.

12. Debugging

Symptom: the BAR reads back the expected value and Memory Space Enable is set, yet accesses to the assigned address never reach the register block

What is already established, and it is a lot. Configuration access works (Chapter 7.7). The Function is identified and its header decodes (Chapter 8.6). The BAR holds what software wrote. Permission is granted (Chapter 8.4).

So the fault is between the programmed value and the resource — which is a short list.

The ladder:

  1. Does the access reach the Function at all? Parent windows must forward it (Chapter 7.8 §5). An access that never arrives cannot hit, and the Function is blameless.
  2. Is the decoder using the programmed value? Observe win_base and win_size at the decoder, not just the BAR readback. A configuration path that updates the readback but not the decoder's operand produces exactly this symptom.
  3. Is the window legal? Check window_legal. A misaligned base or a non-power-of-two size makes the decode refuse everything (P3), and the BAR readback looks fine because the value stored is the value written.
  4. Does the address actually fall in the window? Compute membership by hand from base and size. Software may have programmed one placement and be accessing another.
  5. Does hit assert? If the address is in range, the window is legal, and permission is granted, hit must assert. If it does not, the fault is in the decode (P4) — most likely the mask.
  6. Does the resource respond to a hit? Only now is the register block a candidate.

The observation that resolves most of these in one step: compare the BAR readback against the decoder's actual operands. They should be the same value. If they differ, the configuration path and the datapath disagree about where the window is — and every downstream observation is explained by that, with no need to investigate the resource at all.

Symptom: software writes the BAR and expects the device's data to change

This is not a hardware fault. It is the §1 misconception in operational form, and it is worth naming because it produces confident bug reports.

A configuration write to a BAR changes where the resource window is reached. It has no path to the payload behind the window — §9's module has no write port from configuration into the resource, and P5 asserts that nothing on the operational side can reach the mapping either.

What the writer probably intended was an operational write: an ordinary memory access to an address inside the assigned window, which reaches the resource through the decoder. That is a different mechanism, in a different address space, using a different addressing scheme.

The diagnostic value of recognising this quickly. A report of the form "I wrote the BAR and the device didn't change" describes correct behaviour. The productive response is not to investigate the BAR path but to establish which address space the writer meant to target — and the answer is nearly always that they wanted MMIO and reached for configuration space.

13. Common Misconceptions

  • "A BAR contains the device's registers." A BAR is 32 bits of configuration space describing where a window is reached. The registers are behind that window and may be orders of magnitude larger. Different objects, different address spaces.
  • "A BAR is MMIO space." The BAR lives in configuration space and is reached by identity plus offset. The resource it describes lives in memory or I/O space and is reached by an assigned address.
  • "Writing a BAR writes device data." It changes a mapping. There is no path from a configuration write to the payload behind the window, and §9's structure makes that absence explicit.
  • "Assigning a BAR enables MMIO." Programming and permission are independent steps (Chapter 8.4). A programmed window with the space disabled serves nothing, and an enabled space with no programmed window has nothing to serve.
  • "Endpoint RTL chooses its own BAR address during enumeration." The Function states a requirement; the host chooses the placement and programs it (Chapter 7.8). A Function that chose its own address could not coexist with an identical second card.
  • "A BAR value is meaningful without system allocation." The value means something only because the host assigned it after considering everything else in the system. Read in isolation it is a number.
  • "One BAR means one register." A BAR describes a window, which may contain a large register block, a memory region, queues, or anything else the Function implements.
  • "All BARs are memory BARs." More than one address-space kind exists, which is why Chapters 9.2 and 9.3 are separate chapters. This chapter deliberately treats the kind as out of scope.
  • "BAR sizing and BAR programming are the same operation." Discovering how large a window must be and telling a Function where it has been placed are different exchanges, covered by Chapters 9.4 and 9.5.
  • "BAR decode and configuration-space decode are the same path." Configuration accesses are decoded by identity and register offset (Chapters 7.7, 8.6). Operational accesses are decoded by address against the programmed window. Two decoders, two address spaces, one shared piece of state between them.

14. Understanding Check

15. What's Next

This chapter established what a BAR is and what it becomes: a configuration-space control point whose programmed value ends up as an operand in an address decoder.

It deliberately showed none of the BAR itself.

Chapter 9.2 — Memory BARs opens the encoding: what a memory BAR's bits actually mean, how the 32-bit and 64-bit forms differ, and what a Function must implement for each. Chapter 9.3 covers the I/O-space form. Chapter 9.4 covers how software discovers how large a window needs to be — the exchange this chapter referred to as "the host determines the requirement" without describing it. Chapter 9.5 covers the programming itself, and Chapter 9.6 follows a CPU access all the way through to the resource.

The mental model built here is what keeps all of that straight: a BAR describes a window's placement; it is never the window's contents.