Skip to content

PCIe · Module 9

BAR Sizing — Reading a Size That Is Not Stored Anywhere

A BAR contains no size field, yet software must learn how much address space a resource needs before placing it. Why writing all ones exposes a mask of implemented address bits, how to derive the size without an off-by-one, and how to build and verify that register behaviour in RTL.

Go back through Chapter 9.2 §2 and Chapter 9.3 §2 and look for a size field.

There is none. A Base Address Register holds an address and a few read-only attribute bits, and that is the whole encoding. Yet Chapter 7.8 established that a host cannot place anything until it knows how much space each Function needs, and every decoder in the last two chapters needed a mask it had to get from somewhere.

How does software discover how large a BAR-backed resource must be, before assigning its address?

1. The Question Software Cannot Answer Alone

Chapter 7.8 framed the allocation problem and split the responsibility: a Function states what it needs; the host decides where it goes. Chapter 9.1 built the mechanism for the second half — a BAR through which the host communicates the placement it chose.

The first half has been outstanding ever since.

What the host knows after enumeration. That a Function exists, what it is, and where its configuration space is. It knows nothing about how much address space the Function requires, and it cannot guess: two cards with the same Vendor and Device ID can be revisions with different resource footprints, and a generic allocator has no per-device table to consult.

What the Function knows. Exactly how large its resource is, because that was fixed when the silicon was designed. A 64 KB register block is 64 KB in every unit ever manufactured.

So there is a fact on one side of the link and a decision on the other, and they must meet. The host cannot decide where to put a window until it knows how big the window is; the Function cannot state a size through an encoding that has no size field.

BAR sizing is that exchange. It is the only mechanism by which a Function's resource requirement reaches the host allocator.

And it happens before anything else in Module 9's lifecycle. Chapter 9.1 §3 listed the steps: the Function is discovered, the host determines its requirement, the host chooses an address, the host programs the BAR, the host enables the space. This chapter is step 2, and nothing after it can proceed until it completes.

2. What the Hardware Actually Contains

The mechanism is unguessable from the outside and obvious from the inside, so start inside.

A resource window is a power of two in size and naturally aligned. Chapter 9.2 §2 recorded the normative statement: the design "implies that all address spaces used are a power of two in size and are naturally aligned." Call the size S = 2^n.

Its decoder therefore compares only the address bits above bit n. Bits n-1:0 of an inbound address are the offset within the window; they select something inside the resource and say nothing about whether the address belongs to it. This is Chapter 9.1 §5's masked comparison, and it is not an optimisation — it is what "aligned power-of-two window" means.

And the base's low bits are zero by definition of aligned. A window of size 2^n aligned to 2^n has a base whose bottom n bits are all zero. Not usually zero. Always.

Why this is a better design than a size field, which is worth a moment because it is not obvious:

A hypothetical size fieldThe implemented-bit mask
Extra state, per BAR, that nothing else usesCosts nothing — the decoder needed exactly these bits
Can disagree with the decoder that actually claims addressesCannot disagree; it is the same hardware
Needs its own encoding, its own reserved values, its own errataNeeds no encoding at all

The second row is the important one. A size field and a decoder are two descriptions of the same fact, and two descriptions can drift. A Function whose size field said 64 KB while its comparator claimed 128 KB would be consistently misallocated by every host in the world, and nothing about the configuration space would look wrong. The mask cannot have that bug, because it is the comparator's input.

3. The Verified Procedure

Two things in that list are easy to read past and change the design.

Steps 1, 2 and 7 belong to software, not to the Function. Saving the original value, disabling decode, and restoring afterwards are things the host does. The specification describes them as software behaviour, and this chapter does not claim the Function is responsible for any of them. §11's register model has no save buffer, no restore logic, and no notion that a probe occurred — and §15 is precise about who is at fault when the original value is lost.

No separate sizing "mode" is needed. Read step 3 again: software writes a value. That is an ordinary configuration write, and nothing in the protocol marks it as a probe. The Function is certainly involved — it must implement exactly the address bits its resource size requires, keep its attribute bits read-only, and return the resulting pattern on the following read — but that behaviour is the same behaviour it needs for ordinary programming. No probe detector, no mode bit, and no sizing state fall out of it, which is why §11 implements the whole mechanism with one masking term.

4. Why Writing All Ones Works

This is the section worth understanding rather than memorising, because everything else follows from it and nothing else needs to be remembered.

Set up the register. A Function with a resource of size S = 2^n implements address bits 31:n and hardwires bits n-1:0 of the address field to zero. Below the address field sit the read-only attribute bits — 3:0 for a Memory BAR, 1:0 for an I/O BAR.

Now write all ones.

  • Every implemented address bit takes the write and becomes 1. They are ordinary writable storage; nothing about the value 1 is special to them.
  • Every unimplemented address bit is unaffected. It cannot be set to 1, because it is not storage. It reads back 0.
  • Every attribute bit is read-only and keeps its implemented value, unchanged. The write does not disturb them either.

Read it back and look at what you have.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
readback  =  (implemented address bits, all 1)      ← the mask you want
          |  (unimplemented address bits, all 0)    ← the hole
          |  (attribute bits, at their fixed values)← noise, removable

The returned pattern is a map of which address bits exist. It is a property of the silicon, exposed by an ordinary write — not a value the Function computed, and not a firmware convention.

Why all ones specifically. Any probe value would reveal some structure, but only all-ones guarantees that every implemented bit reads back as 1 and therefore that every zero in the result means "unimplemented". Probing with, say, 0x8000_0000 tells you only about bit 31. All ones is the single write that interrogates every bit at once.

5. The Size Formula, Derived

The specification gives the recipe. Here is why it is arithmetic rather than magic.

Start from the masked readback. After clearing the attribute bits, what remains is

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mask = ~(S − 1)

restricted to the address field — the bits at or above n, all set; the bits below n, all clear. That is the same mask Chapter 9.1 §8 used for decode, which is not a coincidence.

Now invert it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
~mask = ~~(S − 1) = S − 1

Inverting a mask whose high bits are set and low bits are clear gives a value whose low n bits are set and everything above is clear — which is exactly S − 1, the largest offset inside the window.

And add one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
(S − 1) + 1 = S

That is the whole derivation. Three steps, each of which is an identity:

StepValueWhat it is
Read back, clear every non-address bit~(S − 1)the implemented-bit mask
InvertS − 1the largest offset inside the window
Add 1Sthe window size

And one case that is not arithmetic at all. A readback of all zeros means the register implements no address bits, which per §3 is how an unimplemented Base Address Register presents. Running the formula on it gives ~0 + 1, which overflows to zero in the register's own width — so the special case must be tested before the formula, not derived from it. Production enumeration software does exactly that: a zero mask short-circuits to "no resource here" before any size math runs.

6. Worked Examples

Every value below is computed from the encodings verified in Chapter 9.2 §2 and Chapter 9.3 §2. No attribute bit is invented, and each example uses attributes a real Function could legitimately implement.

Example 1 — a 1 MB, non-prefetchable, 32-bit Memory BAR

This is the specification's own example, carried through to a number.

The Function. Resource size S = 1 MB = 2^20. Per §2, it "would build the top 12 bits of the address register" — implemented address bits are 31:20.

Attributes. Bit 0 = 0 (memory). Bits 2:1 = 00 (32-bit form). Bit 3 = 0 (not prefetchable). Low nibble = 0h.

StepValue
Save the original BAR value(whatever the host had assigned)
WriteFFFF FFFFh
Read backFFF0 0000h
Clear bits 3:0FFF0 0000h
Invert (32-bit)000F FFFFh
Add 10010 0000h
Size0010 0000h = 1 048 576 = 1 MB
Restore the original value(before re-enabling decode)

Read the readback back to the hardware. Twelve F-nibble bits at the top are the twelve implemented address bits. Five zero nibbles below are the twenty bits the Function does not build. The register has a hole exactly 20 bits deep, and 2^20 is the size.

Example 2 — a 4 KB, prefetchable, 32-bit Memory BAR

A smaller resource, and a Function that sets an attribute bit — which is where the formula's first step earns its place.

The Function. S = 4 KB = 2^12; implemented address bits 31:12.

Attributes. Bit 0 = 0 (memory). Bits 2:1 = 00 (32-bit). Bit 3 = 1 (prefetchable). Low nibble = 8h.

StepValue
Read back after the probeFFFF F008h
Clear bits 3:0FFFF F000h
Invert0000 0FFFh
Add 10000 1000h
Size0000 1000h = 4096 = 4 KB

Now skip the first step and watch it break. Invert FFFF F008h directly and you get 0000 0FF7h; add one and you get 0000 0FF8h = 4088 bytes.

Four thousand and eighty-eight. Not a power of two, not a legal window size, and eight bytes short of the truth. An allocator that trusted it would reserve a range that cannot be expressed as an aligned power-of-two window at all — and the only reason the error is catchable is that the result fails the power-of-two test. That test is worth running on every size derived, precisely because this is what a masking bug looks like.

Example 3 — a 256-byte I/O BAR

The encoding differs, so the first step differs with it.

The Function. S = 256 bytes = 2^8; implemented address bits 31:8. Note this is the maximum an I/O BAR may consume (Chapter 9.3 §2).

Attributes. Bit 0 = 1 (I/O, hardwired). Bit 1 = 0 (reserved, must read 0).

StepValue
Read back after the probeFFFF FF01h
Clear the non-address bits — bits 1:0FFFF FF00h
Invert0000 00FFh
Add 10000 0100h
Size0000 0100h = 256 bytes

The step that differs, stated precisely. For a Memory BAR the non-address bits are 3:0; for an I/O BAR they are 1:0 — bit 0 the hardwired indicator and bit 1 a reserved field that must read 0. The Implementation Note names only bit 0 because bit 1 already reads 0 on conforming hardware, so both masks give FFFF FF00h here. Clear both anyway. The rule you want in your head and in your code is remove every non-address bit, not remove bit 0 — the second one happens to work and depends on a device honouring a reserved field, which is not a dependency worth having.

And the mask must not be shared across kinds. Applying the memory mask (~Fh) to an I/O BAR discards address bits 3:2, which are real address bits here, and reports a size four times too large. Chapter 9.3 §7 selects the mask from the decoded kind for exactly this reason; this is what it costs to get wrong.

Also note step 6 of the procedure. If bits 16–31 of an I/O readback are zero — a Function built for a 16-bit I/O system — the upper 16 bits of the result are ignored. Without that rule, a readback of 0000 FF01h would invert to FFFF 00FEh and produce a nonsensical size.

Example 4 — an 8 GB, prefetchable, 64-bit Memory BAR

The case where 32-bit arithmetic silently destroys the answer.

The Function. S = 8 GB = 2^33; implemented address bits 63:33.

Attributes, in the lower dword only. Bit 0 = 0 (memory). Bits 2:1 = 10 (64-bit form). Bit 3 = 1 (prefetchable). Low nibble = Ch.

The lower dword carries address bits 31:4 — every one of which is below bit 33 and therefore unimplemented. All read back as zero.

The upper dword carries address bits 63:32. Implemented bits start at 33, so the upper dword's bit 0 (address bit 32) is unimplemented and reads 0; its bits 31:1 (address bits 63:33) are implemented and read 1.

StepValue
Lower dword readback0000 000Ch
Upper dword readbackFFFF FFFEh
Combine into 64 bits (upper is bits 63:32)FFFF FFFE 0000 000Ch
Clear bits 3:0FFFF FFFE 0000 0000h
Invert (64-bit)0000 0001 FFFF FFFFh
Add 10000 0002 0000 0000h
Size2 0000 0000h = 8 589 934 592 = 8 GB

Now do it wrong, in 32 bits, on the lower dword alone. Clear bits 3:0 of 0000 000Ch and you have 0000 0000h. Invert to FFFF FFFFh. Add one — and in 32-bit arithmetic it wraps to 0000 0000h.

Size zero. Which is the encoding for unimplemented. Software concludes the BAR does not exist, never allocates it, never enables it, and the resource is simply absent from the system. The most consequential possible wrong answer, produced by the most ordinary possible mistake, and nothing about it looks like an error.

This is why the specification says "size calculation is done on the 64-bit value" rather than treating the two dwords as independent registers. They are one number, and the arithmetic has to be done at the width of that number.

Example 5 — an unimplemented BAR

The Function implements nothing at this slot. Per §3, "unimplemented Base Address registers are hardwired to zero."

StepValue
Read back after the probe0000 0000h
InterpretationNo address bits are implemented — no resource here

Do not run the formula. Clearing bits 3:0 leaves zero; inverting gives FFFF FFFFh; adding one wraps to zero. The formula produces a size of zero, which happens to be correct here, but only by accident of the wrap — and on a machine using wider intermediate arithmetic it would produce 1 0000 0000h, a 4 GB resource that does not exist.

So the zero test comes first, as a distinct branch, before any arithmetic. Production enumeration software structures it exactly that way, and §14's scoreboard must too.

7. The Sequence

BAR sizing sequence. Enumeration software first clears the Memory and I/O Space Enable bits in the Command register. It then reads the Base Address Register and saves the returned original value. It writes all ones to the Base Address Register, reads it back, and receives a mask of the implemented address bits. It passes that mask to the size calculation, which clears the encoding bits, inverts, and adds one, returning the required size. Software then writes the saved original value back to the Base Address Register and finally restores the Command register enables.Sizing a Base Address RegisterEnumeration SWCommand registerBAR registerSize calculationclear Memory / I/OSpace Enableread current valueoriginal value —software saves itwrite FFFF FFFFhread backimplemented-bit maskclear encoding bits,invert, add 1required sizewrite the savedoriginal valuerestore the enables
Figure 1 — the sizing exchange. Software disables decode, saves the current value, writes the all-ones probe, reads back the mask of implemented address bits, derives the size from that mask, restores the saved value, and re-enables decode. Steps outside the BAR register belong to software; the Function itself has no sizing state and no knowledge that a probe occurred.

Count the messages that reach the BAR register: four, and every one of them is an ordinary configuration read or write. There is no probe command, no mode entry, no mode exit. The Function cannot tell this sequence apart from any other four accesses, and it does not need to.

Everything the Function does not do is on software's lifeline. Disabling decode, holding the saved value, computing the size, restoring — all of it. §15's second scenario is what happens when software skips one of those steps and blames the device.

Why decode is disabled first. Between the probe write and the restore, the BAR holds a value that is not the assigned base. If decode were enabled during that interval, the Function would claim a window derived from the probe pattern — Chapter 9.1 §6's over-claiming failure, aimed at whatever address the mask happens to name. Clearing the Command Register enables removes the exposure completely, which is why the Implementation Note puts it first.

8. What Sizing Is Not

Three operations get conflated and they have different owners, different timing, and different failure modes.

OperationWho does itWhat changesChapter
SizingHost software, once per BAR at discoveryNothing permanent — the register is restoredthis chapter
AllocationHost software, after all sizes are knownNothing in the device — a decision is made7.8
ProgrammingHost software, once a decision existsThe BAR holds the assigned base9.5

Sizing changes nothing and decides nothing. It is a measurement. When it completes, the BAR holds exactly what it held before, and the host has learned one number.

Allocation is where the number is used — together with every other Function's number, the parent windows that must contain them, and the alignment constraints of Chapter 7.8 §6. A size on its own determines nothing about placement.

Programming is the write that commits the decision. It is an ordinary BAR write, indistinguishable at the register from the probe write and the restore write, and it is Chapter 9.5's subject.

Why the distinction matters in practice. "The BAR is sized" and "the BAR is programmed" are different states, and a Function that has been sized but not programmed has a BAR holding whatever it held before — often zero. An engineer who reads a zero BAR and concludes that sizing failed has confused a measurement with an assignment.

9. Microarchitecture — A Register Shaped by Its Resource

The design consequence of §2 is small and slightly surprising: the BAR register's write path is where the size lives.

Three regions in one 32-bit register:

The attribute bits — 3:0 for memory, 1:0 for I/O — are read-only constants fixed by what the Function implements.

The implemented address bits are ordinary writable storage. There are exactly as many of them as the resource size requires and not one more.

The unimplemented address bits are not storage. A write cannot reach them; a read returns zero.

10. Compile-Time — Deriving the Implementation Mask

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// COMPILE-TIME. Size legality and implementation-mask derivation.
// The relationship mask = ~(size - 1) is NORMATIVE in effect: it is what
// "implement only the upper bits, hardwire the rest to zero" produces for a
// power-of-two, naturally aligned window (section 2). The packaging is
// illustrative.
package bar_size_pkg;
 
  // A legal resource size is a nonzero power of two. All arithmetic is done
  // at 64 bits so that (s - 1) cannot wrap in a narrower context.
  function automatic bit size_is_pow2(input longint unsigned s);
    logic [63:0] v;
    v = 64'(s);
    return (v != 64'd0) && ((v & (v - 64'd1)) == 64'd0);
  endfunction
 
  // ENCODING FLOOR — the finest granularity a BAR kind can EXPRESS, derived
  // from the width of its non-address field: 16 bytes for a Memory BAR
  // (bits 3:0), 4 bytes for an I/O BAR (bits 1:0). This is a property of the
  // bit layout, NOT a size a PCI Express Endpoint may request.
  function automatic bit size_meets_encoding_floor(input longint unsigned s,
                                                   input bit             is_io);
    return 64'(s) >= (is_io ? 64'd4 : 64'd16);
  endfunction
 
  // ENDPOINT FLOOR — what a conventional PCI Express Endpoint may actually
  // request: a minimum memory resource of 128 bytes. Strictly stronger than
  // the encoding floor, and the one a modern Endpoint design must satisfy.
  // A Legacy PCI Express Endpoint keeps the older 16-byte minimum; that case
  // is out of scope for this chapter's model rather than parameterised.
  function automatic bit size_meets_pcie_endpoint_min(input longint unsigned s);
    return 64'(s) >= 64'd128;
  endfunction
 
  // NORMATIVE: a device must not consume more than 256 bytes per I/O BAR.
  function automatic bit size_within_kind_limit(input longint unsigned s,
                                                input bit             is_io);
    return !is_io || (64'(s) <= 64'd256);
  endfunction
 
  // The implemented-address-bit mask. Bits at or above log2(size) are
  // implemented; everything below is hardwired to zero. This is the same
  // value the decoder uses, which is section 4's whole argument.
  function automatic logic [63:0] impl_mask64(input longint unsigned s);
    return ~(64'(s) - 64'd1);
  endfunction
 
  // The 64-bit form stores that ONE mask across two dwords. A split, not two
  // independent masks — the pair describes one resource (Chapter 9.2).
  function automatic logic [31:0] impl_mask_lo(input longint unsigned s);
    logic [63:0] m;
    m = impl_mask64(s);
    return m[31:0];
  endfunction
 
  function automatic logic [31:0] impl_mask_hi(input longint unsigned s);
    logic [63:0] m;
    m = impl_mask64(s);
    return m[63:32];
  endfunction
 
endpackage

Classification: compile-time.

What it teaches — three things:

  1. The mask and the size are the same fact. ~(s − 1) appears here, in §11's write path, and in every decoder in Module 9. One expression, one meaning, no second source of truth.
  2. All arithmetic is 64-bit and explicit. 64'(s), 64'd1. Example 4's wrap happened because 32-bit arithmetic was applied to a 64-bit quantity; a helper that widens first cannot reproduce it.
  3. Two floors, two functions, no conflation. size_meets_encoding_floor derives what the bit layout can express — 2^4 for memory, 2^2 for I/O, one per non-address-field width. size_meets_pcie_endpoint_min states what a conventional PCI Express Endpoint may request: 128 bytes. Keeping them as separate functions is what stops the smaller number from leaking into a design rule it does not govern; §11 checks the Endpoint floor, because that is the design being built.

Deliberately simplified: no Expansion ROM form; no representation of a Function that deliberately over-consumes (§4's seam), which would take a second parameter separating resource size from claimed size.

11. RTL — A Memory BAR Register That Sizes Itself by Construction

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. One 32-bit Memory Base Address Register, shaped by the size
// of the resource behind it.
// Read-only attribute bits and zero-return of unimplemented address bits:
// NORMATIVE (PCI Local Bus Specification Rev 3.0, section 6.2.5.1).
// Interface, port names, and the storage strategy: illustrative.
import bar_size_pkg::*;
 
module mem_bar32_reg #(
  // The FIXED size of the resource behind this BAR. Everything about the
  // register's shape follows from it.
  parameter longint unsigned RESOURCE_SIZE = 64'h0000_0000_0001_0000, // 64 KiB
  parameter bit              PREFETCHABLE  = 1'b0
) (
  input  logic        clk,
  input  logic        rst_n,
 
  // One configuration write, already decoded to this BAR (Chapter 8.6).
  input  logic        cfg_wr,
  input  logic [31:0] cfg_wdata,
  input  logic [3:0]  cfg_be,        // byte enables — APPLIED
 
  // What software reads. This is the ONLY thing sizing observes.
  output logic [31:0] cfg_rdata,
  // Address bits only, for the decoder. Never carries attribute bits.
  output logic [31:0] resource_base
);
 
  // ---- Shape, derived at elaboration ---------------------------------
  // The implemented address bits, and nothing else. For every legal
  // RESOURCE_SIZE (>= 128 bytes, checked below) this also has bits 3:0 clear,
  // so the attribute nibble and the address field cannot overlap.
  localparam logic [31:0] IMPL_MASK = impl_mask_lo(RESOURCE_SIZE);
 
  // NORMATIVE: bits 3:0 are read-only. Bit 0 = 0 (memory), bits 2:1 = 00
  // (32-bit form — the 64-bit form is section 12), bit 3 = prefetchable.
  localparam logic [3:0]  ATTR_RO   = {PREFETCHABLE, 2'b00, 1'b0};
 
  // COMPILE-TIME legality. A bad size must fail elaboration; there is no
  // stimulus that could catch it later, because the shape is fixed by then.
  generate
    if (!size_is_pow2(RESOURCE_SIZE))
      $error("RESOURCE_SIZE must be a nonzero power of two");
    // The ENDPOINT floor, not the encoding floor. A conventional PCI Express
    // Endpoint's minimum memory resource is 128 bytes; the 16-byte figure
    // describes what the bit layout could express and what a LEGACY PCI
    // Express Endpoint may request, neither of which this module is.
    if (!size_meets_pcie_endpoint_min(RESOURCE_SIZE))
      $error("A PCI Express Endpoint memory resource must be >= 128 bytes");
    if (RESOURCE_SIZE > 64'h8000_0000)
      $error("A 32-bit Memory BAR supports at most 2 GB");
  endgenerate
 
  // ---- Storage: ONLY the implemented address bits are meaningful ------
  logic [31:0] addr_q;
 
  // Byte-enable merge, then the two normative constraints applied on the
  // write path. Order matters: the constraints come last, so no enabled byte
  // can defeat them.
  logic [31:0] merged;
  always_comb begin
    merged = addr_q;
    for (int b = 0; b < 4; b++) begin
      if (cfg_be[b]) merged[8*b +: 8] = cfg_wdata[8*b +: 8];
    end
    // THIS LINE IS THE SIZING MECHANISM. Address bits below the resource
    // size cannot be set by any write, so a write of all ones reads back as
    // exactly IMPL_MASK. Nothing recognises the probe; the hole does the work.
    merged = merged & IMPL_MASK;
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) addr_q <= 32'h0000_0000;
    else if (cfg_wr) addr_q <= merged;
  end
 
  // ---- Read path ------------------------------------------------------
  // Attribute bits always read their implemented value; address bits read
  // what survived the mask. Software's sizing arithmetic starts here.
  assign cfg_rdata     = addr_q | {28'h000_0000, ATTR_RO};
 
  // The decoder gets address bits ONLY — never the attribute nibble
  // (Chapter 9.2 section 9). addr_q already has bits 3:0 clear, because
  // IMPL_MASK does for every legal RESOURCE_SIZE.
  assign resource_base = addr_q;
 
endmodule

Classification: synthesizable.

Register semantics — every dimension, explicitly:

DimensionBehaviour
Resetaddress bits cleared; attribute nibble reads its implemented value
Readimplemented address bits, plus the fixed attribute nibble
Writeimplemented address bits only
Byte enablesapplied — an enabled byte merges, a disabled byte is preserved
Fixed bits3:0, read-only (normative)
Writable bitsthe implemented address bits, IMPL_MASK above bit 3
Unimplemented bitsnot writable; always read 0 (normative) — this is the sizing mechanism
Sizing behaviournone special; an all-ones write is an ordinary write
Probe detectionnone — the design cannot and must not recognise one
Save / restoresoftware's, not the Function's (§3)
Hardware may updateno
Software may updateyes, through configuration
Side effectsnone
32/64 relationshipthis module is the 32-bit form; §12 covers the pair

Trace the probe through the code once, because it is short. cfg_wdata = FFFF FFFFh, all byte enables set. merged becomes all ones, then & IMPL_MASK clears every unimplemented bit. addr_q takes that. cfg_rdata ORs the attribute nibble back in. The value software reads is IMPL_MASK | ATTR_RO — Example 2's FFFF F008h for a 4 KB prefetchable BAR, with no code anywhere that knows a probe happened.

Trace normal programming. A host assigns F7A3 4000h to that same 4 KB BAR: merged & IMPL_MASK = F7A3 4000h, unchanged, because the base is aligned and every set bit is implemented. resource_base is that value; cfg_rdata is F7A3 4008h.

Trace a misaligned write. Software writes F7A3 4ABCh. The low bits are not implemented, so merged & IMPL_MASK yields F7A3 4000h again. The window does not move, and the misconception that low address bits "fine-tune" a resource's position is not argued against here — it is structurally impossible.

What it teaches — four things:

  1. One & is the entire mechanism. Sizing is not a feature that was added; it is a consequence of implementing only the bits the decoder needs.
  2. The order in always_comb is load-bearing. Byte-enable merge first, normative constraints second. Reversing them lets an enabled byte 0 write the attribute nibble and lets an enabled low byte set unimplemented address bits — two normative violations from one transposition.
  3. Byte enables are applied, not accepted and ignored. A configuration write may carry fewer than four bytes, and a model that overwrote the whole dword would destroy bytes software never addressed. P9 checks it.
  4. Alignment is enforced, not assumed. The decoders of Chapters 9.2 and 9.3 check alignment because they receive an arbitrary base; this module guarantees it, because a misaligned value cannot be stored. That is the stronger position, and it is why real BARs cannot present a misaligned base.

Deliberately simplified: the 32-bit form only; no Expansion ROM; unimplemented bits are stored-and-masked rather than absent; and no representation of a Function deliberately consuming more than it uses.

Production implication: a real Function omits the unimplemented flip-flops entirely, instantiates one of these per BAR with per-BAR sizes and attributes, composes the header decode of Chapter 8.6, and — for a 64-bit resource — splits one mask across the pair as §12 describes.

12. The 64-Bit Case

The hardware change is smaller than the software change. A 64-bit resource has one implemented-bit mask, ~(S − 1) at 64 bits, and the pair stores it in two pieces: the lower dword holds bits 31:4 of it (bits 3:0 being attributes), and the upper dword holds bits 63:32. §10's impl_mask_lo and impl_mask_hi are that split, and each dword's write path applies its own half exactly as §11 applies the whole.

That is deliberately all the RTL this chapter shows for the pair. A second register module differing from §11 only in which slice of one mask it applies would add code and teach nothing, and Chapter 9.2 §10 already owns the genuinely different hardware problem the pair creates — coherence between two independently written dwords.

The software change is where the difficulty actually is, and §3 states it: write all ones to both registers, read both back, combine into a 64-bit value, and do the size calculation on the 64-bit value. Example 4 showed what the alternative produces.

13. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over mem_bar32_reg. Implementation invariants for THIS design plus the
// normative behaviours it implements — not a claim about PCIe beyond
// section 3. Every property refers to explicit RTL state or inputs.
 
// NORMATIVE SHAPE — P1: the attribute nibble is read-only. Bits 3:0 of a
// Memory BAR always read their implemented value, whatever software writes.
property p_attr_nibble_readonly;
  @(posedge clk) disable iff (!rst_n)
  cfg_rdata[3:0] == ATTR_RO;
endproperty
a_attrs_fixed : assert property (p_attr_nibble_readonly);
 
// NORMATIVE SHAPE — P2: unimplemented address bits always read zero. The
// property that makes sizing possible at all. Catches a write path that
// forgot the IMPL_MASK term, which would report the resource as 16 bytes —
// a size a PCI Express Endpoint may not even request.
property p_unimplemented_bits_read_zero;
  @(posedge clk) disable iff (!rst_n)
  (cfg_rdata & ~(IMPL_MASK | 32'(ATTR_RO))) == 32'h0000_0000;
endproperty
a_holes_read_zero : assert property (p_unimplemented_bits_read_zero);
 
// NORMATIVE SHAPE — P3: no write can set an unimplemented address bit. P2
// states the invariant; P3 pins it to the write path, so a design that
// masked only on read would fail here.
property p_write_cannot_set_unimplemented;
  @(posedge clk) disable iff (!rst_n)
  cfg_wr |=> ((addr_q & ~IMPL_MASK) == 32'h0000_0000);
endproperty
a_write_masked : assert property (p_write_cannot_set_unimplemented);
 
// SIZING — P4: the probe readback IS the implementation mask. The chapter's
// central claim as one property. A full-width all-ones write must read back
// as exactly the implemented bits plus the fixed attributes.
property p_probe_returns_mask;
  @(posedge clk) disable iff (!rst_n)
  (cfg_wr && (cfg_be == 4'hF) && (cfg_wdata == 32'hFFFF_FFFF))
    |=> (cfg_rdata == (IMPL_MASK | 32'(ATTR_RO)));
endproperty
a_probe_mask : assert property (p_probe_returns_mask);
 
// SIZING — P5: the mask the probe returns corresponds to the parameterised
// size. Runs the SPECIFICATION'S OWN arithmetic on the readback — clear bits
// 3:0, invert, add one — rather than repeating IMPL_MASK, so a mask derived
// with the wrong inversion or one bit position out fails here.
property p_mask_matches_size;
  @(posedge clk) disable iff (!rst_n)
  (cfg_wr && (cfg_be == 4'hF) && (cfg_wdata == 32'hFFFF_FFFF))
    |=> ((~(cfg_rdata & ~32'h0000_000F) + 32'd1) == 32'(RESOURCE_SIZE));
endproperty
a_size_recoverable : assert property (p_mask_matches_size);
 
// SIZING — P6: a sizing probe does not disturb the attribute bits. Catches a
// probe path that wrote the whole dword including the read-only nibble,
// which would make the size arithmetic produce a non-power-of-two.
property p_probe_preserves_attrs;
  @(posedge clk) disable iff (!rst_n)
  (cfg_wr && (cfg_wdata == 32'hFFFF_FFFF)) |=> (cfg_rdata[3:0] == ATTR_RO);
endproperty
a_probe_keeps_attrs : assert property (p_probe_preserves_attrs);
 
// SIZING — P7: a probe does not reach the resource. The BAR's stored value
// and the decoder's operand are the same register, so the probe changes the
// mapping — and nothing else. There is no path from here to payload.
property p_probe_does_not_touch_payload;
  @(posedge clk) disable iff (!rst_n)
  resource_base == addr_q;
endproperty
a_base_is_addr_only : assert property (p_probe_does_not_touch_payload);
 
// NO MODE — P8: probe then restore returns the register to exactly its
// previous readback. True because there is no sizing state to exit. A design
// that added a mode bit and cleared it on the wrong condition fails here.
property p_probe_restore_roundtrip;
  logic [31:0] saved;
  @(posedge clk) disable iff (!rst_n)
  (!cfg_wr, saved = cfg_rdata)
  ##1 (cfg_wr && (cfg_be == 4'hF) && (cfg_wdata == 32'hFFFF_FFFF))
  ##1 (cfg_wr && (cfg_be == 4'hF) && (cfg_wdata == saved))
  |=> (cfg_rdata == saved);
endproperty
a_roundtrip : assert property (p_probe_restore_roundtrip);
 
// SAFETY — P9: a byte with its enable low is preserved, above the forced
// attribute nibble. The executable form of "byte enables are applied".
property p_byte_enables_respected;
  @(posedge clk) disable iff (!rst_n)
  (cfg_wr && !cfg_be[3]) |=> (addr_q[31:24] == $past(addr_q[31:24]));
endproperty
a_be_respected : assert property (p_byte_enables_respected);
 
// SAFETY — P10: the exposed base is always naturally aligned to the resource
// size. Guaranteed rather than checked, because a misaligned value cannot be
// stored — the stronger form of Chapter 9.1's alignment precondition.
property p_base_always_aligned;
  @(posedge clk) disable iff (!rst_n)
  (resource_base & 32'(RESOURCE_SIZE - 64'd1)) == 32'h0000_0000;
endproperty
a_base_aligned : assert property (p_base_always_aligned);
 
// SAFETY — P11: nothing changes without a configuration write. Runtime
// traffic cannot alter the mapping (Chapter 9.1's thesis).
property p_stable_without_write;
  @(posedge clk) disable iff (!rst_n)
  !cfg_wr |=> $stable(addr_q);
endproperty
a_no_spontaneous_change : assert property (p_stable_without_write);
 
// SAFETY — P12: no output is ever unknown.
property p_outputs_never_unknown;
  @(posedge clk) disable iff (!rst_n)
  !$isunknown({cfg_rdata, resource_base});
endproperty
a_no_x : assert property (p_outputs_never_unknown);

P5 is the property worth studying. P4 says the readback equals IMPL_MASK | ATTR_RO — which is true by construction and would still be true if IMPL_MASK itself were derived with the wrong inversion. P5 closes that gap by running the specification's own arithmetic on the readback and requiring the answer to be the parameterised size. It is the design and the size-derivation formula checking each other, and it fails for any mask that is off by one bit position in either direction.

P8 is how "there is no sizing mode" becomes checkable. The claim is about absence, and absence is asserted by showing that a sequence which would need a mode to get wrong instead comes out right. Probe, restore, and the register is byte-for-byte where it started — no mode to leave, nothing to leave it in.

P3 exists because P2 alone can be satisfied on the read path. A design that stored everything and masked on read would satisfy P2 and still be wrong: its stored base would carry bits the decoder must not see, and resource_base would move the window. P3 requires the masking to have happened before the flop.

P10 states something the earlier chapters could only check. Chapter 9.1 §8 and Chapter 9.2 §11 verify alignment because they take a base from outside. Here alignment is structural — the storage cannot hold a misaligned value — and P10 records that upgrade. It is also the reason those earlier checks are conservative rather than redundant: they defend against a base arriving from a model that did not have this property.

14. Verification

Monitors observe: every configuration write with its data and byte enables; cfg_rdata on every read; addr_q; and resource_base.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY. An independent implementation of the normative size
// derivation (section 3, step 5, and the 64-bit rule). Deliberately shares
// NOTHING with bar_size_pkg or with the design's localparams.
function automatic longint unsigned size_from_readback
  (input logic [63:0] readback,     // combined; upper half zero for 32-bit
   input bit          is_io,
   input bit          is_64);
 
  logic [63:0] masked;
  logic [63:0] inverted;
 
  // An unimplemented Base Address register is hardwired to zero. Test this
  // BEFORE any arithmetic — the formula would wrap and report a wrong,
  // plausible answer (section 6, Example 5).
  if (readback == 64'd0) return 64'd0;
 
  // Step 5: clear EVERY NON-ADDRESS BIT — bits 1:0 for I/O, bits 3:0 for
  // memory. The kinds genuinely differ: bits 3:2 are ADDRESS in an I/O BAR
  // and ATTRIBUTES in a memory BAR, so the mask cannot be shared. Bit 1 of an
  // I/O BAR is reserved and must read 0, so masking it changes nothing on
  // conforming hardware — which is exactly why it costs nothing to mask, and
  // why relying on it reading 0 is a dependency worth removing.
  masked = is_io ? (readback & ~64'h3) : (readback & ~64'hF);
 
  // Nothing left after clearing the non-address bits means no implemented
  // address bits, which is again "not implemented".
  if (masked == 64'd0) return 64'd0;
 
  // Steps 5 continued: invert, then increment. At 64 bits for a 64-bit BAR;
  // confined to 32 bits otherwise, so a 32-bit BAR cannot borrow width it
  // does not have.
  if (is_64) begin
    inverted = ~masked;
    return inverted + 64'd1;
  end else begin
    logic [31:0] inv32;
    inv32 = ~masked[31:0];
    return 64'(inv32) + 64'd1;
  end
endfunction
 
// The checker runs this on every observed probe readback and requires:
//   size_from_readback(...) == RESOURCE_SIZE
// and, independently, that the result is a nonzero power of two.

Classification: verification-only.

The power-of-two check is not redundant. It catches the class of failure where the derived size happens to equal RESOURCE_SIZE by coincidence in one configuration but is structurally wrong — and, more usefully, it is the check that fires when non-address bits leak into the arithmetic (§6, Example 2), because a leak essentially always produces a non-power-of-two.

Resource sizes

Every scenario below runs across a size sweep, and the sweep is where most real bugs surface.

  • The smallest resource a PCI Express Endpoint may request — 128 bytes. IMPL_MASK is FFFF FF80h; clearing bits 3:0 leaves it unchanged, inverting gives 0000 007Fh, and adding one gives 0000 0080h. The lower boundary of the design's legal range, and the case where the implemented-bit mask sits closest to the attribute nibble.
  • The 16-byte encoding floor, as a negative test. Elaborate with RESOURCE_SIZE = 16 and verify elaboration fails — the bit layout could express it, but a conventional PCI Express Endpoint may not request it (§3). This is the check that keeps the two floors apart in the design as well as in the prose.
  • A middling size — 4 KB. The specification's suggested decode floor.
  • A large size — 1 GB. Few implemented bits, most of the register reading zero.
  • The largest a 32-bit register supports — 2 GB. IMPL_MASK is 8000 0000h: exactly one implemented address bit. The boundary a mask off-by-one destroys, because ~S instead of ~(S−1) produces a mask of zero and the BAR reports as unimplemented.
  • An illegal size. A non-power-of-two, a zero, and a size below the 128-byte Endpoint minimum, each in a separate elaboration. Verify elaboration fails — these are compile-time checks and cannot be exercised at run time.

The probe

  • Full-width all-ones write, then read back. Verify cfg_rdata equals the independently computed mask plus the attributes (P4), and that the independently derived size equals RESOURCE_SIZE (P5).
  • Probe with prefetchable set and clear. Verify the derived size is identical. This is Example 2's bug as a test — an implementation that failed to clear the attribute nibble would report different sizes for the two cases, and the derived size would not be a power of two in one of them.
  • Probe an unimplemented BAR. With no implemented address bits, verify the readback is zero and the checker's zero branch is taken, not the arithmetic.
  • Partial-byte probes. Write FFh to byte 0 only, then to byte 3 only. Verify each affects only its own byte, above the attribute nibble (P9). A probe is an ordinary write, so it is subject to byte enables like any other.
  • Probe with zero byte enables. Verify nothing changes.

Programming

  • Aligned legal base. Verify it is stored exactly, that resource_base equals it, and that cfg_rdata is that value with the attribute nibble.
  • Misaligned base. Write a value with nonzero bits below the resource size. Verify the unimplemented bits are discarded, the stored base is the aligned value below it, and resource_base is still aligned (P10).
  • A write attempting to change the attribute nibble. All byte enables, low nibble different from the implemented one. Verify the nibble is unchanged (P1) and the address bits took the write.
  • A base with every implemented bit set. The topmost legal placement. Verify it is stored and that the decoder's boundary still works.

Probe, read, restore — back to back

  • The full sequence with no idle cycles. Save, probe, read, restore, all in consecutive configuration accesses. Verify P8 — the register returns to exactly its previous readback.
  • The sequence run twice. Verify the second probe returns the same mask as the first, which fails on any design that accumulated state.
  • A restore that writes the saved value with partial byte enables. Verify the restore still completes correctly across the enabled bytes and leaves the others alone.
  • An interleaved read between the probe and the restore. Verify the read is non-destructive and the restore still works.

Reset

  • Reset before sizing. Verify the readback is the attribute nibble alone — no address bits set.
  • Reset immediately after a probe, before the restore. Verify the register clears rather than retaining the probe pattern. This is where a design with a hidden sizing mode would be caught, because a mode bit surviving reset changes the next readback.
  • Reset after programming. Verify the assigned base is lost and the readback returns to the attribute nibble.

64-bit derivation

Run against the verification model of §12 and §14 rather than the 32-bit RTL.

  • A resource with no implemented bits in the lower dword — Example 4's 8 GB case. Verify the derivation requires both dwords and that the lower dword alone yields the wrong answer, so the test proves the combination is doing work.
  • A resource straddling the dword boundary — 4 GB, where the implemented bits begin exactly at address bit 32. Verify the split is correct at the seam.
  • A 64-bit BAR describing a small resource — 64 KB in the 64-bit form. Verify the type field says 64-bit while the size math is unaffected by it, which is the "type says width, not size" distinction.

Coverage should include: resource sizes at the 128-byte Endpoint minimum, at 2 GB, and a spread between them, plus at least one size in the 64-bit model above 4 GB; prefetchable set and clear; all sixteen byte-enable patterns on both probe and programming writes; aligned and misaligned programming values; the probe/restore sequence with and without intervening reads; and reset from the unprogrammed, probed, and programmed states.

15. Debugging

Symptom: software believes a 64 KB resource is 128 KB

Recognise the shape first. The error is a clean factor of two, and the derived size is still a power of two. That combination excludes attribute leakage — which produces non-power-of-two results — and points at the mask itself.

The candidates, ordered:

  1. The implemented-bit boundary is one position too high. A resource of 64 KB needs implemented address bits 31:16 and a readback of FFFF 0000h. Built with bits 31:17 instead, it reads back FFFE 0000h; invert to 0001 FFFFh, add one, and software gets 0002 0000h — 128 KB. One bit in the mask, one factor of two in the answer. Compare the actual readback against FFFF 0000h and this is settled in one access.
  2. The mask was derived as ~S rather than ~(S − 1). The same one-position error arriving from the arithmetic rather than the RTL. For 64 KB, ~S is FFFE FFFFh, which is not even a contiguous mask — and the derived size is then not a power of two, so this variant usually presents differently.
  3. Software's inversion or increment is off. Check the derivation against Example 1's numbers, which are the specification's own.
  4. The Function genuinely consumes more than it uses. Permitted, and even suggested below 4 KB. If the Function's designer intended a 128 KB claim around a 64 KB register block, software is right and the expectation is wrong — and the reported size is the one that matters to the allocator.
  5. The wrong BAR kind's encoding mask was applied. Using the memory mask on an I/O BAR discards address bits 3:2 and reports four times the size. A factor of two does not fit this, but check bit 0 anyway, because it costs nothing.

The one observation that separates all of these: read the raw probe readback and compare it, digit by digit, against ~(S − 1) | attributes for the size you expected. If the readback is wrong, the fault is in the Function. If the readback is right and the derived size is wrong, the fault is in software's arithmetic. Those are the only two possibilities, and one read settles which.

Symptom: a sizing probe permanently destroys the programmed BAR value

Establish ownership before anything else, because it decides where to look.

Save and restore belong to software. §3's procedure is explicit: software saves the original value, writes the probe, reads back, and restores. The Function has no save buffer, no restore path, and no way to know a probe occurred — and §11's model asserts that absence (P8).

So the first question is not "why did the device lose the value" but "who was supposed to put it back".

The candidates:

  1. Software never saved it. A sizing pass that probes without reading first has nothing to restore. The BAR then holds the probe mask, and if decode is subsequently enabled the Function claims a window derived from FFFF FFFFh masked to its implemented bits — Chapter 9.1 §6's over-claiming failure, with a symptom that appears at another device.
  2. Software saved and restored, but decode was never disabled. The value is fine afterwards; the damage happened during. This looks like a different bug entirely — intermittent corruption near enumeration — and the cause is the missing first step of §3's procedure.
  3. Sizing ran after programming rather than before. A second sizing pass over an already-configured Function is legal and safe if it saves and restores. One that assumes the BAR is unprogrammed and restores zero has silently unassigned the resource.
  4. The design invented a sizing mode and did not leave it. Now it is a hardware fault. A register that recognises the probe value and enters a state it exits on some other condition can be left in that state indefinitely. §9 argued against ever building this; P8 is what catches it.

The distinguishing observation. Read the BAR after the sequence and compare against the probe mask. Equal to the mask means nothing restored it — a software fault, candidates 1 or 3. Neither the mask nor the original means the register is holding a state that no write in the sequence should have produced — a hardware fault, candidate 4.

Symptom: a BAR reports a size of zero

Zero means "no implemented address bits", which per §3 is how an unimplemented Base Address Register presents. Before concluding the Function is missing a resource, three things produce a false zero:

The 64-bit lower dword was sized alone. Example 4 exactly: a large 64-bit resource has no implemented bits in its lower dword, and 32-bit arithmetic on it wraps to zero. Check the type field first — bits 2:1 reading 10 means the upper dword is part of this BAR and must be included.

The mask was derived as ~S. For a 2 GB resource, ~S is zero, and the BAR reports as unimplemented. This is the same off-by-one as the first scenario, at the size where it produces total rather than partial nonsense.

The formula ran on a genuine zero without the zero branch. On 32-bit arithmetic this wraps back to zero, so the answer is accidentally right; on wider intermediate arithmetic it produces 1 0000 0000h, and software allocates 4 GB for a resource that does not exist. The zero test must be a branch, not an outcome.

16. Common Misconceptions

  • "The BAR size is stored as a number in the BAR." There is no size field in any BAR encoding. The size is inferred from which address bits are implemented, and those bits exist because the decoder needs them.
  • "Writing all ones means 'maximum address'." It is a probe. Its purpose is to set every bit that can be set, so that the bits that remain zero identify themselves as unimplemented. The value is meaningful as a test pattern, not as an address.
  • "The sizing probe allocates the BAR." It measures. Nothing is reserved, nothing is decided, and the register is restored to exactly what it held before. Allocation is Chapter 7.8; programming is Chapter 9.5.
  • "Sizing and programming are the same step." Sizing discovers a requirement; programming communicates a decision. They happen at different times, use the register differently, and the first must complete for every Function before the second can begin for any of them.
  • "Software may leave all ones in the BAR after sizing." The procedure restores the original value before re-enabling decode. A BAR left holding the probe mask, once enabled, claims a window nobody assigned.
  • "BAR size depends on where the host places it." Size is fixed by the implementation and is the same before and after placement. The host chooses only the base, and a base is constrained by the size rather than the other way round.
  • "Writing the low alignment bits should shift the resource." Those bits are not implemented. The write is discarded and the window does not move — structurally, as §11's trace shows.
  • "A 64-bit BAR can be sized as two unrelated 32-bit BARs." The two dwords are one number. The specification says to write all ones to both, read both, combine, and calculate on the 64-bit value — and Example 4 shows the alternative reporting a large resource as absent.
  • "BAR sizing writes device payload data." A configuration write to a BAR changes a mapping. There is no path from it to the resource behind the window — Chapter 9.1's thesis, and P7 asserts it here.
  • "The size can be discovered from the Device ID." Two units with the same Vendor and Device ID can be revisions with different footprints, and a generic allocator has no device table. Sizing is the only mechanism that works for a device the host has never seen.
  • "A Function must implement exactly as much address space as its registers occupy." It may consume more — the specification permits it and suggests decoding down to 4 KB for smaller resources — and is not required to respond to the unused portion.

17. Understanding Check

18. What's Next

Three chapters have taken a Base Address Register apart completely.

9.2 decoded the memory form — attribute bits below, address bits above, and a 64-bit form spanning two consecutive dwords that describe one resource. 9.3 decoded the I/O form and established that a resource is named by an address and a space. This chapter answered the question both of them deferred: where the size comes from, given that no BAR contains one.

The host now has everything it needs. It knows each Function exists, what address space each resource lives in, how wide the address is, and how much space each one requires. What it has not done is decide anything.

Chapter 9.5 — Address Assignment is that decision and the write that commits it: how the host turns a set of sizes and constraints into a placement, and how that placement is programmed into each BAR. Chapter 9.6 — Host Access then follows a CPU access from the processor through the fabric, into the decode built across these three chapters, and out to the resource.

The idea to carry forward: a BAR's shape is its size. The bits that exist are the bits the decoder needs, which is why measuring them cannot lie — and why the whole mechanism costs one & in hardware and three arithmetic steps in software.