PCIe · Module 9
Memory BARs — Attribute Bits and Address Bits in One Register
A Memory BAR packs read-only attribute bits into the low nibble and address bits above them. How the 32-bit and 64-bit forms differ, why a 64-bit BAR consumes two configuration dwords but represents one resource, and how to compose and verify that pair in RTL.
Chapter 9.1 established what a Base Address Register is — a configuration-space control point that participates in placing a resource window — and deliberately showed none of its bits.
This chapter shows them.
How does a Memory BAR encode the properties of a memory-space resource and the address assigned to that resource?
1. Two Kinds of Information, One Register
Start with the problem the encoding solves, because the bit positions follow from it and are impossible to remember otherwise.
Software walking a Type 0 header finds six dwords reserved for Base Address Registers. It does not yet know, for any of them, whether it describes a memory resource or an I/O resource, whether the address is 32 bits or 64 bits wide, or whether the region behaves like memory. It needs all of that before it can allocate anything, and it needs it from the same register it will later write the address into.
There is nowhere else to put that information. A separate "BAR properties" register per BAR would double the header's cost for six descriptors. So the properties live in the BAR itself — in the bits that an aligned address does not need.
That is the whole design: a naturally aligned window of at least 16 bytes has low address bits that are always zero, so those bit positions are free to carry attributes instead.
The consequence a designer must internalise: the low bits of a BAR are not small address bits. They are not address bits at all. Including them in an address computation shifts the window by up to 15 bytes, and §12 is what that looks like from the outside.
2. The Verified Field Map
Read the field map as three independent facts, not one number. Which address space (bit 0). How wide the address is (bits 2:1). Whether the region behaves like memory (bit 3). Nothing about any of them is derivable from the others, which is why they need three separate fields.
3. The 32-Bit Form
With bits 2:1 = 00, the BAR is one dword and describes a resource that can be mapped anywhere in the 32-bit memory space.
The split is at bit 4. Bits 31:4 carry address. Bits 3:0 carry attributes and are read-only. That is the entire register.
Why bit 4 and not somewhere else. Four attribute bits require the window to be at least 16 bytes and naturally aligned, so that bits 3:0 of any legal base are zero anyway — and 16 bytes is exactly the smallest window the encoding is defined to describe. The encoding is not arbitrary; it is the largest attribute field that costs nothing.
And a PCI Express Endpoint never gets that close to the edge. Its minimum resource is 128 bytes (§2), so bits 6:0 of a legal base are zero and the four attribute bits sit in a region with three bits to spare. The encoding floor and the Endpoint floor are different rules answering different questions: what can the bit layout express versus what may a PCIe Endpoint request.
What the 32-bit form does not do. It says nothing about how large the resource is. Bits 31:4 are all present in the register; how many of them the Function actually implements is a separate property, and discovering it is Chapter 9.4. This chapter's RTL takes the size as implementation metadata rather than pretending to derive it.
4. The 64-Bit Form — Two Dwords, One Resource
With bits 2:1 = 10, the BAR describes a resource that can be mapped anywhere in the 64-bit address space. One dword is not enough to hold a 64-bit address, so the encoding uses two.
The rule, stated exactly: the register at the next consecutive dword location is an extension of the first — bits 32–63 of the same address. The implemented 64-bit Base Address register consumes two consecutive dword locations.
Three consequences, all of which get misunderstood:
1. The upper dword is not a BAR. It has no bit 0, no type field, no prefetchable bit. Every one of its 32 bits is address. Reading it as an independent Base Address Register — noticing that its bit 0 happens to be 0 and concluding "another memory BAR" — is the single most common misreading of a Type 0 header, and it produces a phantom resource that software will then try to allocate.
2. The pair costs two slots. A Type 0 header has six dword locations. A Function implementing three 64-bit resources has used all six and can implement nothing else. A Function implementing one 64-bit and two 32-bit resources has used four and has two left. The budget is in dwords, not in resources.
3. The last slot cannot start a 64-bit BAR. This follows directly from the two normative statements: the pair occupies two consecutive locations, and software scans 10h through 24h. A 64-bit BAR beginning at 24h would need a seventh location that the header does not define. Official PCIe endpoint IP documentation states the same constraint from the other direction, requiring 64-bit addressing for prefetchable BARs on a non-legacy Endpoint except for the last BAR — which can only be true if the last slot cannot be 64-bit.
5. The Structure, Drawn
The figure's argument is the two paths out of the lower dword. One goes to attributes and stops there — it never reaches the base. The other goes to address. There is no arrow from the attribute nibble into the composed base, and that missing arrow is exactly what P1 asserts in §11.
The second thing to read is the conditional edge. The upper dword only participates because the type field says so. In the 32-bit form the entire lower row of the figure does not exist for this resource — the next dword belongs to some other BAR, or to nothing.
6. The Prefetchable Attribute
Bit 3 is the field most often explained wrongly, so it is worth stating what it actually asserts.
The normative definition. A device may set bit 3 to 1 if there are no side effects on reads, the device returns all bytes on reads regardless of the byte enables, and host bridges can merge processor writes into this range without causing errors. The bit must be 0 otherwise. The specification adds that any range behaving like normal memory should be marked prefetchable, and gives a graphics frame buffer as the example.
Read that as three promises the Function is making about its own behaviour:
| Promise | What it rules out |
|---|---|
| No side effects on reads | A register whose value changes because it was read — a read-to-clear status, a FIFO pop, a counter latch |
| All bytes returned regardless of byte enables | A resource that only produces the bytes it was asked for, because producing the others would itself have an effect |
| Writes may be merged | A resource where two separate narrow writes are not equivalent to one wider write covering both |
So prefetchable is a statement about access semantics, not about performance. It tells the rest of the system that reads from this range are safe to issue speculatively, in bursts, and possibly more than once — because doing so cannot change anything.
7. The Hardware Problem the Pair Creates
Everything so far has been encoding. This section is the design problem, and it is the part worth remembering.
A 64-bit BAR is one logical object stored in two independently writable registers. Configuration writes are ordinary register writes; nothing in the mechanism makes a write to 10h and a write to 14h happen together. So there is an interval — bounded by software's timing, not by anything hardware controls — in which the pair holds the new low half and the old high half, or the reverse.
During that interval the composed base is an address nobody chose. Not a rounded version of the old one or an approximation of the new one: a splice of two different 64-bit addresses, which can land anywhere.
And the decoder is running the whole time. If it decodes on the spliced value, the Function claims a window it was never assigned — Chapter 9.1 §6's over-claiming failure, where the symptom appears at whichever other device owned that range.
The lesson that generalises past PCIe: multi-register state written one register at a time passes through combinations nobody intended, and the exposure lasts as long as software takes. The fix is never "be quick"; it is to make the intermediate state undecodable.
What is not being claimed here. Nothing above says PCIe mandates an atomic commit across the two dwords, and this chapter does not verify any such requirement. What it does establish is that the two dwords are one address — and a design that treats them as one address has to decide what happens between the two writes. §10 picks a policy and states it as a local contract.
8. Microarchitecture — Encoding, Composition, Decode
Three responsibilities, and keeping them apart is what makes each one checkable.
Encoding is what the BAR registers hold: the read-only attribute nibble fixed by what the Function implements, and the writable address bits. This is the PCIe-visible surface.
Composition turns the register contents into one 64-bit resource base: extract the type field, strip the attribute bits, and place the upper dword's bits where they belong. Pure function of the register state.
Decode compares an inbound address against that base, using a mask derived from the resource's size — which is implementation metadata, not a BAR field.
9. RTL — Composing a Resource Base From the Encoding
// SYNTHESIZABLE. Decodes a Memory BAR's attribute bits and composes the
// resource base from one or two configuration dwords.
// Bit positions and the bits 2:1 type encoding: NORMATIVE (PCI Local Bus
// Specification Rev 3.0, section 6.2.5.1, Figure 6-5 and Table 6-4).
// Module boundary and port names: illustrative.
module mem_bar_compose (
// Register contents as configuration software left them.
input logic [31:0] bar_lo, // the BAR itself
input logic [31:0] bar_hi, // the NEXT consecutive dword — 64-bit form only
// Decoded attributes.
output logic is_io, // bit 0 == 1: this is not a memory BAR
output logic is_mem64, // bits 2:1 == 10
output logic prefetchable, // bit 3, meaningful only for memory
output logic type_reserved, // bits 2:1 == 01 or 11
output logic mem_valid, // a usable memory BAR encoding
// The composed resource base. Zero unless mem_valid.
output logic [63:0] base
);
// ---- NORMATIVE field positions and encodings -----------------------
localparam int SPACE_BIT = 0;
localparam int TYPE_LSB = 1;
localparam int TYPE_MSB = 2;
localparam int PREFETCH_BIT = 3;
// Bits 3:0 are read-only attribute bits and are NOT address bits.
localparam logic [31:0] MEM_ATTR_MASK = 32'h0000_000F;
localparam logic [1:0] TYPE_MEM32 = 2'b00;
localparam logic [1:0] TYPE_MEM64 = 2'b10;
// 2'b01 (below 1 MB, from earlier specification versions) and 2'b11 are
// reserved. This model refuses them rather than guessing a width.
wire [1:0] type_field = bar_lo[TYPE_MSB:TYPE_LSB];
assign is_io = bar_lo[SPACE_BIT];
assign is_mem64 = !is_io && (type_field == TYPE_MEM64);
assign prefetchable = !is_io && bar_lo[PREFETCH_BIT];
assign type_reserved = !is_io && (type_field != TYPE_MEM32)
&& (type_field != TYPE_MEM64);
assign mem_valid = !is_io && !type_reserved;
// ---- Composition ---------------------------------------------------
// Attribute bits are cleared, not shifted, not reused. This single line is
// the difference between a correct window and one displaced by up to 15
// bytes, and P1 asserts that it happened.
wire [31:0] lo_addr = bar_lo & ~MEM_ATTR_MASK;
// The upper dword contributes bits 63:32 ONLY when the type field says so.
// In the 32-bit form the next dword belongs to a different BAR entirely, so
// reading it here would import an unrelated resource's address.
wire [31:0] hi_addr = is_mem64 ? bar_hi : 32'h0000_0000;
// Explicit 64-bit concatenation. No sign extension (these are addresses,
// not signed values); no implicit width change; upper half on the left.
assign base = mem_valid ? {hi_addr, lo_addr} : 64'h0000_0000_0000_0000;
endmoduleClassification: synthesizable.
What it teaches — four things:
- Attribute bits are stripped, never reused.
bar_lo & ~MEM_ATTR_MASKis the whole mechanism, and it is one line because the encoding made it one line. A design that composes withbar_lodirectly produces a base up to 15 bytes high — small enough to look plausible in a trace, large enough to break every access. - The 32-bit form zero-extends explicitly.
hi_addris forced to zero rather than left to an implicit width conversion, and it is not taken frombar_hi, because in the 32-bit form that dword is a different BAR. Both errors produce a window in the wrong half of the address space. - Ordering is explicit in the concatenation. Upper dword on the left. Transposing it is a one-character edit that survives review, and P3 is written specifically to catch it.
- A reserved type produces nothing.
mem_validclears,baseis zero, and §10's decode cannot hit. The model refuses to guess an address width it was not given, which is the only safe response to an encoding it does not recognise.
Deliberately simplified: combinational, so it says nothing about coherence between the two dwords — that is §10. It also assumes both dwords are presented together; a real implementation reads them from its own register file.
Production implication: a real Function fixes the attribute nibble in hardware rather than decoding it from a writable register, instantiates this composition per BAR, and pairs it with the write-side semantics of §10.
10. RTL — Owning the Pair as One Object
// SYNTHESIZABLE. The write side of one Memory BAR, including the upper dword
// when the Function implements the 64-bit form.
// Read-only behaviour of bits 3:0: NORMATIVE.
// The pair-coherence policy (pair_ready): ILLUSTRATIVE local contract.
module mem_bar_pair_state #(
// What the FUNCTION implements. These are fixed at elaboration because the
// attribute bits are read-only — a Function does not change form at run
// time, and modelling them as writable would contradict the encoding.
parameter bit MEM64 = 1'b1,
parameter bit PREFETCHABLE = 1'b0
) (
input logic clk,
input logic rst_n,
// One configuration write, already decoded to this BAR by the header
// decode of Chapter 8.6. `cfg_wr_upper` selects which dword of the pair.
input logic cfg_wr,
input logic cfg_wr_upper,
input logic [31:0] cfg_wdata,
input logic [3:0] cfg_be, // byte enables — APPLIED, see below
output logic [31:0] bar_lo_q,
output logic [31:0] bar_hi_q,
// Local contract: the pair currently holds one coherent address.
output logic pair_ready
);
// NORMATIVE: bits 3:0 of a memory BAR are read-only. Their value is what
// the Function implements: bit 0 = 0 (memory), bits 2:1 = type, bit 3 =
// prefetchable.
localparam logic [3:0] ATTR_RO = {PREFETCHABLE,
(MEM64 ? 2'b10 : 2'b00),
1'b0};
logic [31:0] lo_q, hi_q;
logic ready_q;
logic pending_q; // one half written, the other not yet
logic pending_hi_q; // which half is already written
assign bar_lo_q = lo_q;
assign bar_hi_q = hi_q;
assign pair_ready = ready_q;
// ---- Byte-enable merge --------------------------------------------
// Byte enables are applied, not ignored: a configuration write may carry
// fewer than four bytes, and a model that overwrote the whole dword would
// silently destroy the bytes software did not address.
logic [31:0] lo_merged, hi_merged;
always_comb begin
lo_merged = lo_q;
hi_merged = hi_q;
for (int b = 0; b < 4; b++) begin
if (cfg_be[b]) begin
lo_merged[8*b +: 8] = cfg_wdata[8*b +: 8];
hi_merged[8*b +: 8] = cfg_wdata[8*b +: 8];
end
end
// The attribute nibble is restored AFTER the merge, so a write that
// enabled byte 0 cannot disturb it. This is the read-only rule expressed
// as logic rather than as a comment.
lo_merged[3:0] = ATTR_RO;
// The upper dword has no attribute bits — all 32 of its bits are address.
end
// Writes that this resource owns. In the 32-bit form the upper dword is a
// DIFFERENT BAR, so this module must not capture it.
wire wr_lo = cfg_wr && !cfg_wr_upper;
wire wr_hi = cfg_wr && cfg_wr_upper && MEM64;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// Unimplemented and unprogrammed BARs read as zero, except that the
// read-only attribute nibble is what the Function implements.
lo_q <= {28'h000_0000, ATTR_RO};
hi_q <= 32'h0000_0000;
ready_q <= 1'b0; // nothing decodes until a full address exists
pending_q <= 1'b0;
pending_hi_q <= 1'b0;
end else begin
if (wr_lo) lo_q <= lo_merged;
if (wr_hi) hi_q <= hi_merged;
// ---- Pair-coherence policy (ILLUSTRATIVE) ----------------------
// 32-bit form: one dword IS the whole address, so a write to it
// completes immediately.
if (!MEM64) begin
if (wr_lo) ready_q <= 1'b1;
end
// 64-bit form: the address is not coherent until BOTH dwords have been
// written in the current update. Decode is gated in between. The policy
// is order-agnostic: lower-then-upper and upper-then-lower both work.
else if (wr_lo || wr_hi) begin
if (!pending_q) begin
pending_q <= 1'b1;
pending_hi_q <= cfg_wr_upper;
ready_q <= 1'b0; // an update has started — stop decoding
end else if (pending_hi_q != cfg_wr_upper) begin
pending_q <= 1'b0;
ready_q <= 1'b1; // the other half arrived — commit
end
// Same half written twice: still pending, still not decoding.
end
end
end
endmoduleClassification: synthesizable.
Register semantics:
| Dimension | Behaviour |
|---|---|
| Reset | address bits cleared; attribute nibble takes its implemented value; pair_ready low |
| Read | the register contents; attribute bits always read their implemented value |
| Write | address bits only; a write to the attribute nibble is discarded (normative) |
| Byte enables | applied — an enabled byte is merged, a disabled byte is preserved |
| Fixed bits | lower dword bits 3:0; the upper dword has none |
| Writable bits | lower dword bits 31:4; upper dword bits 31:0 in the 64-bit form |
| Side effects | a write starts or completes a pair update, moving pair_ready |
| Hardware may update | no |
| Software may update | yes, through configuration |
| 32/64 relationship | with MEM64 low the upper dword is a different BAR and is never captured |
| Sizing behaviour | not modelled — Chapter 9.4 |
The coherence policy, stated plainly. In the 64-bit form, any write to either dword marks the pair as updating and stops decode. Decode resumes when the other dword is written. Writing the same dword twice in a row leaves the pair updating, because the other half still has not arrived.
Why order-agnostic. Software may reasonably write the upper dword first — some drivers do, to avoid ever presenting a valid low half against a stale high half. A policy that only recognised lower-then-upper would hang decode forever on such a driver, and the failure would look like a hardware fault rather than a policy mismatch.
Three policies were available and this one was chosen:
| Policy | Behaviour between writes | Why not chosen here |
|---|---|---|
| Update each dword immediately | Decodes on the spliced address | Over-claims a range nobody assigned — §7's failure |
| Stage both, commit on an explicit trigger | Safe, but needs a trigger the configuration model does not have | Would require inventing a control the encoding does not define |
| Gate decode until the pair completes | Decodes nothing | Chosen — needs no new control, and silence is debuggable |
Deliberately simplified: one BAR; no per-byte tracking of which bytes of a dword have been written; no interaction with an outstanding access already accepted; and no sizing behaviour at all.
Production implication: a real Function hardwires the attribute nibble rather than resetting a register to it, implements only the address bits it actually decodes (Chapter 9.4), instantiates the pair per 64-bit BAR, and must decide what happens to accesses already in flight when pair_ready drops.
11. RTL — Decode Against the Composed Base
// SYNTHESIZABLE. Post-programming decode for one memory resource window.
// The BAR-visible inputs come from mem_bar_compose; RESOURCE_SIZE is
// IMPLEMENTATION METADATA, not a BAR field (section 8).
module mem_bar_window #(
// The fixed size of the resource behind this BAR. How SOFTWARE discovers
// it is Chapter 9.4; the Function has always known it.
parameter longint unsigned RESOURCE_SIZE = 64'h0000_0000_0001_0000 // 64 KiB
) (
input logic mem_valid, // from mem_bar_compose
input logic [63:0] bar_base, // from mem_bar_compose
input logic pair_ready, // from mem_bar_pair_state
// Permission, from the Command Register (Chapter 8.4, bit 1).
input logic mem_space_enable,
input logic [63:0] req_addr,
output logic window_legal,
output logic hit
);
localparam logic [63:0] SIZE = 64'(RESOURCE_SIZE);
localparam logic [63:0] LOW = SIZE - 64'd1;
localparam logic [63:0] MASK = ~LOW;
// COMPILE-TIME checks. A bad parameter must fail elaboration, not produce a
// mask with holes at run time. All arithmetic is done at 64 bits, so
// SIZE - 1 cannot wrap through an unsized literal's width.
localparam bit SIZE_POW2 = (SIZE != 64'd0) && ((SIZE & LOW) == 64'd0);
// Section 2's PCI Express Endpoint minimum — 128 bytes — NOT the 16-byte
// encoding floor. This model is a conventional PCI Express Endpoint; a
// Legacy PCI Express Endpoint carries the older 16-byte minimum and is out
// of scope here rather than parameterised, because nothing else in this
// module depends on the Endpoint type.
localparam bit SIZE_MIN = (SIZE >= 64'd128);
generate
if (!SIZE_POW2) $error("RESOURCE_SIZE must be a nonzero power of two");
if (!SIZE_MIN) $error("A PCI Express Endpoint resource must be >= 128 bytes");
endgenerate
// A correctly implemented BAR cannot present a misaligned base, because the
// low address bits are not implemented (Chapter 9.4). This model accepts
// whatever address bits were written, so it CHECKS instead of assuming.
wire base_aligned = ((bar_base & LOW) == 64'd0);
assign window_legal = mem_valid && pair_ready && base_aligned;
assign hit = window_legal
&& mem_space_enable
&& ((req_addr & MASK) == (bar_base & MASK));
endmoduleClassification: synthesizable.
What it teaches:
- The two sources of truth stay separate.
bar_baseis PCIe-visible encoding;RESOURCE_SIZEis implementation metadata. The module cannot confuse them because they arrive through different mechanisms — one a port, one a parameter. - Bad parameters fail at elaboration.
$errorinsidegenerateis evaluated during elaboration, so a non-power-of-two or too-small size never reaches simulation. That is strictly better than a run-time check, because there is no stimulus that could miss it. - All arithmetic is 64-bit and explicit.
64'(RESOURCE_SIZE),64'd1,64'd0. Nothing depends on an unsized literal's width, andSIZE - 1cannot wrap in a narrower context than intended. - Three independent conditions gate a hit, exactly as Chapter 9.1 established: the encoding is usable, the pair is coherent, the geometry is legal — and separately, permission is granted.
Deliberately simplified: one window; a fixed size; no behaviour defined for an access that matches nothing; and no modelling of accesses in flight when pair_ready drops.
Production implication: a real Function decodes several windows concurrently, derives each mask from the address bits it actually implements, and defines a response for unmatched accesses.
12. Assertions
// SVA over mem_bar_compose, mem_bar_pair_state, and mem_bar_window composed
// into one Function-level instance. Implementation invariants for THESE
// designs plus the normative encoding facts they implement — not a claim
// about PCIe behaviour beyond section 2. Every property refers to explicit
// RTL state or inputs.
// ENCODING — P1: attribute bits never appear in the composed address. The
// chapter's central claim, made checkable. Catches a composition that used
// bar_lo directly and displaced the window by up to 15 bytes.
property p_attrs_not_in_base;
@(posedge clk) disable iff (!rst_n)
mem_valid |-> (base[3:0] == 4'h0);
endproperty
a_attrs_stripped : assert property (p_attrs_not_in_base);
// ENCODING — P2: the 32-bit form contributes no upper address. Catches a
// composition that imported the neighbouring BAR's contents.
property p_mem32_has_no_upper;
@(posedge clk) disable iff (!rst_n)
(mem_valid && !is_mem64) |-> (base[63:32] == 32'h0000_0000);
endproperty
a_mem32_upper_zero : assert property (p_mem32_has_no_upper);
// ENCODING — P3: the 64-bit form reconstructs both halves in the right
// places. Written as bit-slice equalities rather than by repeating the
// concatenation, so a transposed pair fails here.
property p_mem64_composition;
@(posedge clk) disable iff (!rst_n)
(mem_valid && is_mem64) |-> ((base[63:32] == bar_hi)
&& (base[31:4] == bar_lo[31:4]));
endproperty
a_mem64_composed : assert property (p_mem64_composition);
// ENCODING — P4: a reserved type encoding never produces a usable base and
// never decodes. The model refuses an encoding it cannot interpret.
property p_reserved_type_inert;
@(posedge clk) disable iff (!rst_n)
type_reserved |-> (!mem_valid && !hit && (base == 64'd0));
endproperty
a_reserved_inert : assert property (p_reserved_type_inert);
// ENCODING — P5: a BAR whose bit 0 says I/O is not treated as a memory BAR
// by this model. The boundary Chapter 9.3 picks up.
property p_io_not_memory;
@(posedge clk) disable iff (!rst_n)
is_io |-> (!mem_valid && !hit);
endproperty
a_io_excluded : assert property (p_io_not_memory);
// ENCODING — P6: the attribute nibble is read-only. NORMATIVE behaviour
// (bits 0-3 read-only) expressed as an invariant on the register.
property p_attr_nibble_readonly;
@(posedge clk) disable iff (!rst_n)
bar_lo_q[3:0] == ATTR_RO;
endproperty
a_attrs_fixed : assert property (p_attr_nibble_readonly);
// PAIR — P7: a 32-bit BAR never captures the upper dword. The upper slot
// belongs to a different BAR, and importing it would fabricate a resource.
property p_mem32_ignores_upper;
@(posedge clk) disable iff (!rst_n)
(!MEM64 && cfg_wr && cfg_wr_upper) |=> $stable(bar_hi_q);
endproperty
a_mem32_no_upper_capture : assert property (p_mem32_ignores_upper);
// PAIR — P8: a half-updated 64-bit pair never decodes. The local coherence
// contract of section 10, and the property that prevents the spliced-address
// over-claim of section 7.
property p_incomplete_pair_never_hits;
@(posedge clk) disable iff (!rst_n)
!pair_ready |-> !hit;
endproperty
a_pair_gated : assert property (p_incomplete_pair_never_hits);
// PAIR — P9: starting an update stops decode in the same step. Catches a
// policy that gates only after the second write, which leaves the spliced
// address exposed for exactly the interval the policy exists to close.
property p_update_gates_immediately;
@(posedge clk) disable iff (!rst_n)
(MEM64 && !pending_q && (wr_lo || wr_hi)) |=> !pair_ready;
endproperty
a_update_gates : assert property (p_update_gates_immediately);
// PERMISSION — P10: a hit requires Memory Space Enable. Composes this design
// with Chapter 8.4's Command Register bit 1.
property p_hit_requires_permission;
@(posedge clk) disable iff (!rst_n)
hit |-> mem_space_enable;
endproperty
a_hit_needs_enable : assert property (p_hit_requires_permission);
// CORRECTNESS — P11: hit agrees exactly with window membership. Catches an
// inverted or off-by-one mask, which produces a plausibly-behaving window of
// double or half the intended size.
property p_hit_matches_membership;
@(posedge clk) disable iff (!rst_n)
(window_legal && mem_space_enable)
|-> (hit == ((req_addr & MASK) == (bar_base & MASK)));
endproperty
a_hit_exact : assert property (p_hit_matches_membership);
// SAFETY — P12: a byte with its enable low is preserved. The executable form
// of "byte enables are applied", above the read-only attribute nibble.
property p_byte_enables_respected;
@(posedge clk) disable iff (!rst_n)
(wr_lo && !cfg_be[3]) |=> (bar_lo_q[31:24] == $past(bar_lo_q[31:24]));
endproperty
a_be_respected : assert property (p_byte_enables_respected);
// SAFETY — P13: runtime traffic never alters BAR state. Chapter 9.1's thesis,
// re-asserted now that the state has structure.
property p_access_does_not_reprogram;
@(posedge clk) disable iff (!rst_n)
!cfg_wr |=> ($stable(bar_lo_q) && $stable(bar_hi_q));
endproperty
a_no_reprogram_by_access : assert property (p_access_does_not_reprogram);
// SAFETY — P14: no decode output is ever unknown.
property p_outputs_never_unknown;
@(posedge clk) disable iff (!rst_n)
!$isunknown({hit, window_legal, mem_valid, is_mem64});
endproperty
a_no_x : assert property (p_outputs_never_unknown);P1 is the chapter's thesis as a property. "Attribute bits are not address bits" is a claim about a mask, and P1 is what makes it checkable. It fires on the first composed base after a design uses bar_lo where it meant bar_lo & ~MEM_ATTR_MASK — and it fires immediately, whereas the resulting 0-to-15-byte displacement can hide for a long time in traffic that never touches the bottom of the window.
P3 is written the way it is on purpose. The obvious formulation — asserting base equals the concatenation — restates the RTL and would pass on a design that transposed the halves and transposed them in the property. Comparing base[63:32] against bar_hi and base[31:4] against bar_lo[31:4] states the requirement in terms of where each register's bits must land, which a transposition cannot satisfy in both clauses at once.
P8 and P9 are a pair, and P9 is the one that gets forgotten. P8 says an incomplete pair does not decode; a design can satisfy it while still gating one cycle too late, because pair_ready is only sampled after both writes. P9 pins the edge: decode must stop when the update starts. The interval between those two readings is precisely the window in which the spliced address is live, which is the entire reason the policy exists.
P7 is a 32-bit-configuration property that a 64-bit-only testbench cannot fire. It is the same asymmetry Chapter 8.6 §8's P5 warns about: the bug is invisible in the configuration most likely to be tested, and §13 therefore requires both.
13. Verification
Monitors observe: every configuration write with its dword select, data, and byte enables; both BAR registers; the decoded attribute outputs; pair_ready; the composed base; the permission input; every runtime address; and the decode outputs.
The scoreboard independently composes the expected base by bit extraction from the two register images — take bits 31:4 of the lower dword, take all 32 bits of the upper dword only if bits 2:1 read 10, and place them — and independently computes membership as base <= addr < base + size in its own arithmetic. It must not reuse the design's mask or its concatenation, or it will agree with the design about exactly the two bugs P1 and P3 exist to catch.
The environment must instantiate both forms. Every scenario below runs with MEM64 = 1 and with MEM64 = 0, because P7's failure exists only in the 32-bit configuration and P8/P9's only in the 64-bit one.
Encoding
- 32-bit form, address bits varied. Verify the composed base equals the register's bits 31:4 with a zero low nibble and a zero upper half.
- 64-bit form, both halves nonzero. Verify both halves land in the right place. The single most valuable test in the chapter — a transposition passes every 32-bit test.
- 64-bit form, upper dword zero. A resource placed below 4 GB in a 64-bit BAR. Verify it behaves identically to the 32-bit case, because a transposition also passes this one.
- Attribute bits varied across all sixteen values of the low nibble. For each, verify the composed base is unchanged. This is P1 exhaustively.
- Prefetchable set and clear. Verify decode is identical. Prefetchable describes access semantics; it changes nothing about placement, and a design that let it perturb the base has confused an attribute for an address bit.
- Reserved type encodings
01and11. Verifymem_validclears, the base is zero, and no address hits (P4). - Bit 0 set. Verify the memory path declines it entirely (P5) — the boundary Chapter 9.3 picks up.
Register semantics
- Write to the attribute nibble. Write a full dword with all byte enables and a low nibble different from the implemented one. Verify the nibble is unchanged (P6) and the address bits took the write.
- Partial-byte writes. Each of the fifteen nonzero byte-enable patterns. Verify enabled bytes change and disabled bytes do not (P12), and that byte 0's enable still cannot move the attribute nibble.
- Zero byte enables with
cfg_wrasserted. Verify nothing changes. - Reset from a programmed state. Verify address bits clear, the attribute nibble returns to its implemented value, and
pair_readydrops.
The pair
- Lower then upper. Verify decode is gated after the first write and resumes after the second (P8, P9).
- Upper then lower. Verify the same. The order-agnosticism test — a policy that only handles one order fails here and nowhere else.
- Lower written twice, then upper. Verify the pair stays gated through the repeat and completes only on the upper write.
- Runtime traffic throughout an update. Present addresses continuously across both writes. Verify no hit occurs in the interval, including addresses that would have hit under the old base and under the new one.
- Reprogramming from one 64-bit address to another. Both halves changing. Verify the spliced address never decodes — construct the splice deliberately and target it.
- 32-bit BAR, write to the upper slot. Verify
bar_hi_qis untouched (P7) and decode is unaffected. - Sustained traffic, no configuration writes. Verify both registers are stable (P13).
Decode and boundaries
- First and last address in the window.
baseandbase + size - 1. Both must hit. The second is the boundary a mask error moves. - One below and one above the window. Neither may hit.
- Window at the top of the 64-bit space. Verify the boundary still decodes — the masked form has no end-address arithmetic to overflow, and this proves it rather than assuming it.
- Misaligned base. Verify
window_legalclears and nothing hits. - Memory Space Enable clear. Verify no hit at any address (P10), with the window otherwise perfect.
Coverage should include: both MEM64 settings; all sixteen attribute-nibble values; both reserved type encodings; prefetchable set and clear; all sixteen byte-enable patterns on each dword; both pair-write orders and the repeat case; addresses at both window boundaries and one beyond each; resource sizes across the legal range including the 128-byte PCI Express Endpoint minimum and a size near the address-space maximum; and the permission input in both states.
14. Debugging
Symptom: a 64-bit BAR reads back a plausible lower dword, but accesses land at the wrong high address
What "plausible lower dword" already establishes. Configuration access works, the header decode selected the right offset, and the lower dword holds what software wrote. The fault is above bit 31 — which is a short list, and the readback that looked reassuring is exactly what excluded everything below it.
The ladder:
- Is the type field actually
10? Read bits 2:1 of the lower dword. If they say00, the Function is a 32-bit BAR and there is no upper dword for this resource — software's high address was written to a different BAR, and that other resource is now also broken. This one observation explains both symptoms. - Did software write the correct consecutive slot? The upper dword is at the BAR's offset plus 4. A scanner that mis-strided (§4) writes it four bytes further along.
- Was the upper dword captured at all? Read it back. Zero where a nonzero high half was written means either the wrong slot was targeted or the Function does not implement the pair.
- Is the concatenation the right way round? Compare the composed base against the two register images by hand. A transposed pair puts the low half of the address at bits 63:32, which usually produces a wildly wrong address rather than a subtly wrong one — and is therefore easy to recognise once you look.
- Is the low nibble in the base? Compare
base[3:0]against zero. Nonzero means the attribute bits were not stripped, and the window is displaced by that amount. - Is the upper dword stale? A pair reprogrammed with only the lower half written holds the previous high address. Under the §10 policy that state cannot decode; under a design without the policy it decodes at the spliced address, and this is what that looks like.
The observation that resolves most of this in one step: read both dwords and compose the base by hand. Compare it against the address software is actually using. If they differ, the fault is in the composition or the pairing and the resource is not implicated; if they agree, the resource is next.
Symptom: the window is correctly aligned but everything lands a few bytes low
Recognise this one by its size. The displacement is small — under 16 bytes — and constant. That is the signature of the attribute nibble being carried into the address.
Which direction tells you which side. If the Function composes with the nibble included, its window sits above where the host placed it, and accesses at the intended base fall below the window. If the driver reads the BAR back and uses the whole 32-bit value as an address, its accesses sit above the window. Same magnitude, opposite sign, and a single access at the exact assigned base distinguishes them.
What it is not. It is not a mask error — those move a boundary by a power of two, not by a value under 16. It is not an alignment failure — the base is aligned, which is precisely why the low nibble was free to hold attributes in the first place. Sizing is Chapter 9.4's subject and is not implicated by a sub-16-byte offset.
Symptom: a Type 0 header appears to have one more resource than the Function implements
The phantom is an upper dword being read as a BAR (§4). The scan advanced 4 bytes past a 64-bit BAR instead of 8, landed on the extension dword, and interpreted its bit 0 as a space indicator.
How to confirm it in one read. Check the type field of the BAR before the suspicious one. If bits 2:1 read 10, the suspicious dword is not a BAR at all and the scan is off by one slot from that point onward.
Why the symptom is worth recognising immediately. The phantom resource's apparent size and type vary with the assigned address, so it looks like a flaky device rather than a scanning bug — and every BAR after it is misidentified too, which makes the header look comprehensively broken when only the stride is wrong.
15. Common Misconceptions
- "A 64-bit BAR is two independent BARs." It is one Base Address Register occupying two consecutive dword locations. The second dword is an extension — bits 32–63 of the same address — with no space indicator, no type field, and no prefetchable bit.
- "Prefetchable means faster." It asserts three things about the Function's behaviour: no side effects on reads, all bytes returned regardless of byte enables, and writes safely mergeable. Performance is a consequence some platforms may extract; it is not what the bit says.
- "All 32 bits of a Memory BAR are address bits." Bits 3:0 are read-only attribute bits. Bits 31:4 carry address. Including the low nibble in an address computation displaces the window by up to 15 bytes.
- "The BAR value is the resource contents." Chapter 9.1's correction, still true after the encoding is visible: the BAR describes where a window is reached, not what is behind it.
- "Writing only the lower dword is enough for every 64-bit BAR." It leaves the high half at its previous value. The composed address is then a splice of two placements, and §10's policy exists specifically so that splice cannot decode.
- "Memory Space Enable is encoded inside the BAR." It is a Command Register bit (Chapter 8.4). The BAR has no enable of its own, which is why P10 is a property joining two separate registers.
- "The upper dword of a 64-bit BAR describes a second resource." It describes the top half of the first one. Treating it as a resource fabricates a window the Function never claimed.
- "All BARs must be 64-bit." A Type 0 header supports up to six 32-bit BARs, or three 64-bit ones, or any mix that fits in six dwords. The form is a per-BAR property of what the Function implements.
- "Moving a resource's address changes its required size." Size is fixed by the implementation; the address is assigned by the host. Reprogramming a base moves a window of unchanged size, which is why Chapter 9.4's discovery happens once and placement can happen more than once.
- "The type field says how big the resource is." It says how wide the address is — 32-bit or 64-bit placement. A 64-bit BAR may describe a tiny resource, and a 32-bit BAR may describe a 2 GB one.
16. Understanding Check
17. What's Next
This chapter opened the Memory BAR encoding: attribute bits below, address bits above, one form that fits in a dword and one that spans two consecutive dwords to describe a single resource.
It said nothing about the other value bit 0 can take.
Chapter 9.3 — IO BARs covers the other address space: how an I/O BAR's encoding differs, why two address spaces exist at all, and why the same numeric address in the wrong space must not hit. Chapter 9.4 — BAR Sizing answers the question §8 deferred — how software discovers how large a resource must be, given that the encoding contains no size field anywhere. Chapter 9.5 covers the programming sequence that assigns a base, and Chapter 9.6 follows a host access through the decode to the resource.
The idea to carry forward: a BAR's low bits describe the resource; its high bits place it; and when there are two dwords, they are one address.