PCIe · Module 23
BAR Logic — Which Aperture Owns This Address?
An address arrives with no label. The decoder must answer with exactly one aperture, no aperture, or an error — and the three costly mistakes are comparing ranges instead of masks, splitting a 64-bit BAR in two, and deciding before checking enable.
Chapter 23.1 placed a block in Figure 1 called inbound decode and did not build it. This chapter builds it.
The problem is deceptively small. An address arrives on the inbound path. It carries no indication of which of this device's resources it refers to — the requester knows nothing about your internal layout; it only knows a number the enumeration software gave it.
So one block must turn that number into an answer: which aperture, and what local address.
1. Sources, Scope, and What Module 9 Already Owns
2. An Address Arrives With No Label
The inbound packet carries an address and nothing else that identifies your resources. The requester used a number that enumeration software wrote into your BAR — it has no idea that number corresponds to your control registers rather than your frame buffer.
So the decoder's job has exactly three parts:
1. does any aperture of mine contain this address? (§3, §5)
2. if so, which one -- and only one (§5, §6)
3. what local address does it become (§8)And a fourth that people forget: am I even allowed to respond right now? (§7)
This is structurally Chapter 21.1's problem. A switch asks which port owns an address; an endpoint asks which aperture does. Both must produce one-hot, none, or an error — and Chapter 21.1 §8 measured what happens when the third case is resolved by priority instead of reported: the traffic goes silently and permanently the wrong way.
3. Mask Compare, Not Range Compare
4. Alignment Is a Precondition, and It Should Be Checked
Natural alignment is guaranteed by the assignment rules (9.5 §2, §3) — but the value in your BAR arrived from software, and software can write anything.
So the decoder has two defensible postures, and this chapter picks the second:
| Posture | Consequence |
|---|---|
| assume alignment | a misprogrammed base makes the decoder wrong in 33.3% of probes, silently |
| check alignment and report | a misprogrammed base produces a named configuration error |
The check is nearly free — (base & (size-1)) == 0, one AND and a zero-compare — and it converts the worst class of bug in this chapter into a register bit someone can read.
Note what the check is not. It does not fix a misaligned base, and it must not silently mask one off: a decoder that quietly aligns what software wrote is now decoding a different aperture than software believes it programmed (9.6 §7's "local decode is not reachability", from the other direction). Report and refuse to claim, which is §12's err_misaligned and P6.
5. Several BARs, and the Third Outcome
6. A 64-Bit BAR Is One Aperture, Not Two
7. Enable Is Not Decode
8. From Address to Local Offset
Once one aperture is selected, the local address is the offset within it:
offset = addr & (size - 1) [bytes, within the aperture]§14 Model 10 confirms this is exact: over 400,000 probes the mask form and the subtraction form addr - base produced identical results in every case — again because the base is aligned (§4). And again the mask form needs no subtractor.
Two things the decoder must emit together, and one it must not.
Emit the target select and the offset as one atomic result. They are two halves of one decision; a design that pipelines them separately can pair one packet's target with another's offset — structurally Chapter 23.1 §3's corruption, arriving through a different door.
And do not emit the full address. The application resource should receive an offset into itself, not a system address it would have to re-decode. A resource that sees system addresses has had a boundary destroyed (23.1 §2) — and it will break the first time software relocates the BAR.
9. No Match Is an Answer
An address matching no aperture is not an error in your device. It is a request you must not claim.
What happens next depends on the transaction and is owned elsewhere: a Non-Posted request that no one claims is completed as an Unsupported Request (21.1 §6, 13.2), and Chapter 23.5 owns generating that Completion.
What belongs to the decoder is the signal: no_match, distinct from ambiguous, distinct from disabled. Three different non-claims with three different causes, and collapsing them into one "miss" output makes §16's debugging impossible.
One thing the decoder must never do: claim an address in order to be helpful. A device that answers requests outside its apertures corrupts another device's transaction — the same class of failure as §6's aliased claim, and worse than silence in exactly the same way.
10. The Decode Path
Four things to read out of the figure.
All apertures are compared at once, not searched in sequence. That is what makes §5's three outcomes natural — a sequential search returns the first hit and structurally cannot notice the second.
The enable gate is a separate input to the resolver, not a filter applied afterwards. match is a fact about numbers; claim is a decision (§7), and separating them is what lets §9 distinguish disabled from no_match.
The aperture table holds base and mask, and 64-bit pairs are already combined — the pairing happened at elaboration, not in the comparator (§6).
And not claimed is an output, not a dead end. Chapter 23.5 turns it into a Completion; the decoder's job is to say so clearly.
11. The Waveform
Four ways not to claim an address, and only one of them is a hit
10 cyclesFour things to read out of the figure.
Cycles 2, 3, 6 and 7 all end in not_claim, for four different reasons. A decoder with a single "miss" output makes those four indistinguishable — and §16 shows that each one sends the debug in a different direction.
Cycle 3 is in range and not claimed. The address matched; the space was disabled; match is not claim (§7).
Cycle 6 asserts ambiguous and no hit. A priority encoder would have asserted hit_bar0 here and nothing else would ever have been noticed (§5).
And cycle 7 is the 64-bit case working. The low 32 bits match an aperture and the upper bits do not, so the address is refused — the 50.1% of §14 Model 7, prevented (§6).
12. RTL — The Decoder
// COMPILE-TIME. Aperture descriptions and the static 64-bit pairing.
// Whether a slot is the low half of a 64-bit BAR, the high half, or an
// independent 32-bit BAR is a property of the DESIGN, not a runtime
// decision that can disagree with itself (§6).
package bar_pkg;
parameter int ADDR_W = 64;
parameter int N_SLOT = 6; // physical BAR slots
parameter int SEL_W = (N_SLOT <= 1) ? 1 : $clog2(N_SLOT);
typedef enum logic [1:0] {
SLOT_UNIMPL = 2'd0,
SLOT_MEM32 = 2'd1,
SLOT_MEM64_LO = 2'd2, // this slot plus the next form ONE aperture
SLOT_MEM64_HI = 2'd3 // never a decode candidate on its own
} slot_kind_e;
// Design-time slot map. Slot 1 is the high half of slot 0's aperture.
parameter slot_kind_e SLOT_KIND [N_SLOT] = '{
SLOT_MEM64_LO, SLOT_MEM64_HI, SLOT_MEM32, SLOT_MEM32,
SLOT_UNIMPL, SLOT_UNIMPL };
// Aperture size per slot, in bytes. Powers of two (Chapter 9.4 §5).
parameter longint unsigned SLOT_SIZE [N_SLOT] = '{
64'h0000_0100_0000, 64'd0, 64'h0000_0001_0000, 64'h0000_0010_0000,
64'd0, 64'd0 };
function automatic logic [ADDR_W-1:0] size_mask(input longint unsigned sz);
// ~(size-1). Guarded so a zero size yields an ALL-ONES mask, which
// matches nothing useful, rather than an all-zero mask that matches
// EVERYTHING -- the difference between an unimplemented BAR that is
// inert and one that swallows the address space.
if (sz == 0) return {ADDR_W{1'b1}};
return ~(ADDR_W'(sz) - ADDR_W'(1));
endfunction
function automatic bit is_decode_candidate(input slot_kind_e k);
return (k == SLOT_MEM32) || (k == SLOT_MEM64_LO);
endfunction
// The aperture a candidate slot represents, after pairing.
function automatic bit is_64bit(input slot_kind_e k);
return (k == SLOT_MEM64_LO);
endfunction
endpackageimport bar_pkg::*;
// SYNTHESIZABLE. One aperture's match. MASK COMPARE, not range compare:
// exact for an aligned power-of-two aperture (§14 Model 8: 0 disagreements
// over 300,000 probes) and free of the adder and carry chain a range
// compare needs on an inbound-critical path (§3).
module aperture_match (
input logic [ADDR_W-1:0] addr,
input logic [ADDR_W-1:0] base, // already the FULL 64-bit base (§6)
input logic [ADDR_W-1:0] mask, // ~(size-1)
input logic implemented,
input logic space_enabled,
output logic match, // a fact about numbers
output logic claim, // a decision about behaviour (§7)
output logic err_misaligned // base not naturally aligned (§4)
);
// Alignment is a PRECONDITION of the mask form. With a misaligned base
// the mask and range forms disagree in 33.3% of probes (§14 Model 8),
// so a misprogrammed base is reported rather than silently obeyed.
assign err_misaligned = implemented && ((base & ~mask) != '0);
assign match = implemented
&& !err_misaligned
&& ((addr & mask) == (base & mask));
// MATCH IS NOT CLAIM. §14 Model 9: 49.9% of in-aperture probes arrived
// while the space was disabled; an ungated decoder claims all of them.
assign claim = match && space_enabled;
endmoduleimport bar_pkg::*;
// SYNTHESIZABLE. Assembles the FULL 64-bit base from a slot pair (§6).
// A decoder that compares only the lower 32 bits claimed an address 4 GiB
// away in 50.1% of aliased probes -- and an aliased claim is worse than
// silence, because the requester receives wrong data instead of a UR.
module bar_base_assemble #(parameter int SLOT = 0) (
input logic [31:0] bar_reg [N_SLOT],
output logic [ADDR_W-1:0] base,
output logic valid_candidate
);
localparam slot_kind_e KIND = SLOT_KIND[SLOT];
always_comb begin
valid_candidate = is_decode_candidate(KIND);
if (!valid_candidate) base = '0;
else if (is_64bit(KIND)) base = {bar_reg[SLOT+1], bar_reg[SLOT]};
else base = {32'd0, bar_reg[SLOT]};
end
endmoduleimport bar_pkg::*;
// SYNTHESIZABLE. THE FLAGSHIP BLOCK. All apertures, one address, three
// outcomes (§5). §14 Model 5: 0 disagreements against an independent
// range-scan oracle over 400,000 probes, with every overlap flagged.
module bar_decoder (
input logic [ADDR_W-1:0] addr,
input logic req_valid,
input logic [ADDR_W-1:0] base [N_SLOT],
input logic space_enabled,
output logic [N_SLOT-1:0] match_vec,
output logic hit, // exactly one
output logic no_match, // none of mine (§9)
output logic ambiguous, // two or more -- a CONFIG FAULT
output logic disabled_hit, // matched, but not permitted (§7)
output logic [SEL_W-1:0] sel,
output logic [N_SLOT-1:0] err_misaligned_vec
);
logic [N_SLOT-1:0] claim_vec;
genvar g;
generate
for (g = 0; g < N_SLOT; g++) begin : g_ap
aperture_match u_m (
.addr(addr),
.base(base[g]),
.mask(size_mask(SLOT_SIZE[g])),
.implemented(is_decode_candidate(SLOT_KIND[g])),
.space_enabled(space_enabled),
.match(match_vec[g]),
.claim(claim_vec[g]),
.err_misaligned(err_misaligned_vec[g])
);
end
endgenerate
// ==================================================================
// THREE OUTCOMES, and the third is REPORTED. A priority encoder here
// resolves every overlap to the same slot and makes the other aperture
// permanently unreachable with no error (§5, §14 Model 6: 24.9%).
// ==================================================================
assign hit = req_valid && $onehot(claim_vec);
assign ambiguous = req_valid && !$onehot0(match_vec);
assign disabled_hit = req_valid && !space_enabled && (match_vec != '0);
assign no_match = req_valid && (match_vec == '0);
always_comb begin
sel = '0;
if (hit)
for (int i = 0; i < N_SLOT; i++)
if (claim_vec[i]) sel = SEL_W'(i);
end
endmoduleimport bar_pkg::*;
// SYNTHESIZABLE. Target and offset, emitted ATOMICALLY (§8).
// Two halves of one decision. Pipelining them separately can pair one
// packet's target with another's offset -- Chapter 23.1 §3's corruption,
// through a different door.
module bar_translate (
input logic clk,
input logic rst_n,
input logic hit,
input logic [SEL_W-1:0] sel,
input logic [ADDR_W-1:0] addr,
input logic out_ready,
output logic out_valid,
output logic [SEL_W-1:0] out_target,
output logic [ADDR_W-1:0] out_offset
);
logic [ADDR_W-1:0] off_c;
// offset = addr & (size-1). Exact, and needs no subtractor: §14 Model 10
// found 0 disagreements with (addr - base) over 400,000 probes, because
// the base is aligned (§4).
always_comb begin
off_c = '0;
for (int i = 0; i < N_SLOT; i++)
if (SEL_W'(i) == sel) off_c = addr & ~size_mask(SLOT_SIZE[i]);
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin out_valid <= 1'b0; out_target <= '0; out_offset <= '0; end
else if (!out_valid || out_ready) begin
out_valid <= hit;
// ONE cycle, BOTH fields -- never a target from one packet and an
// offset from another.
out_target <= sel;
out_offset <= off_c;
end
// Under stall (out_valid && !out_ready) nothing changes: the payload
// is stable, per Chapter 23.1 §3's contract and P14 below.
end
endmoduleimport bar_pkg::*;
// SYNTHESIZABLE. Aperture snapshot (§13's audit).
// Software may write a BAR at any time, including while a request is in
// the decode pipeline. The decoder samples the whole table ONCE, at the
// request-accept boundary -- the rule Chapter 19.4 §12 established and
// Chapter 21.1 §13 reused for routing decisions.
module bar_table_snapshot (
input logic clk,
input logic rst_n,
input logic take, // request accepted
input logic [ADDR_W-1:0] base_live [N_SLOT],
input logic enable_live,
output logic [ADDR_W-1:0] base_eff [N_SLOT],
output logic enable_eff,
output logic cfg_changed_in_flight // sticky, diagnostic
);
logic [ADDR_W-1:0] b_q [N_SLOT];
logic e_q, chg_q;
always_comb begin
for (int i = 0; i < N_SLOT; i++) base_eff[i] = b_q[i];
enable_eff = e_q;
end
assign cfg_changed_in_flight = chg_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int i = 0; i < N_SLOT; i++) b_q[i] <= '0;
e_q <= 1'b0; chg_q <= 1'b0;
end else begin
if (take) begin
for (int i = 0; i < N_SLOT; i++) b_q[i] <= base_live[i];
e_q <= enable_live;
end else begin
// A change while a decode is in flight is REPORTED, not applied.
for (int i = 0; i < N_SLOT; i++)
if (base_live[i] != b_q[i]) chg_q <= 1'b1;
if (enable_live != e_q) chg_q <= 1'b1;
end
end
end
endmoduleimport bar_pkg::*;
// VERIFICATION-ONLY. Independent oracle: an explicit RANGE scan, written
// deliberately in the form the RTL does NOT use, so a shared misconception
// cannot make both agree (§15's DV rule).
module bar_decoder_oracle (
input logic [ADDR_W-1:0] addr,
input logic [ADDR_W-1:0] base [N_SLOT],
input logic space_enabled,
input logic [N_SLOT-1:0] rtl_match_vec,
output logic err_mismatch,
output logic [N_SLOT-1:0] ref_match_vec
);
always_comb begin
for (int i = 0; i < N_SLOT; i++) begin
automatic logic [ADDR_W-1:0] lo = base[i];
automatic logic [ADDR_W-1:0] hi = base[i] + ADDR_W'(SLOT_SIZE[i]);
ref_match_vec[i] = is_decode_candidate(SLOT_KIND[i])
&& (SLOT_SIZE[i] != 0)
&& (addr >= lo) && (addr < hi);
end
err_mismatch = (ref_match_vec !== rtl_match_vec);
end
endmoduleClassification: five synthesizable, one compile-time, one verification-only.
Failure — seven. Range compare with a misaligned base (33.3%, §14). Lower-32-only compare on a 64-bit BAR (50.1% aliased claims). A priority encoder over the match vector (24.9% silently resolved). Decoding before checking enable (49.9% of in-aperture probes). A zero size producing an all-zero mask that matches everything. Including a SLOT_MEM64_HI slot as a decode candidate (phantom matches). And emitting target and offset non-atomically.
13. Same-Cycle Audit and Assertions
// ==================================================================
// MATCHING (§3, §4) -- the mask form and its precondition.
// ==================================================================
// P1: a match means the masked address equals the masked base. This is
// the definition, asserted so a "clever" rewrite cannot drift from it.
property p_match_is_mask_compare;
@(posedge clk) disable iff (!rst_n)
match_vec[0] |-> ((addr & size_mask(SLOT_SIZE[0]))
== (base[0] & size_mask(SLOT_SIZE[0])));
endproperty
// P2: the mask form agrees with an explicit range scan. §14 Model 5:
// 0 disagreements over 400,000 probes.
property p_agrees_with_range_oracle;
@(posedge clk) disable iff (!rst_n)
!err_mismatch;
endproperty
// P3: an unimplemented slot never matches.
property p_unimplemented_never_matches;
@(posedge clk) disable iff (!rst_n)
(SLOT_KIND[4] == SLOT_UNIMPL) |-> !match_vec[4];
endproperty
// P4: the HIGH half of a 64-bit pair is never a decode candidate on its
// own -- including it produces phantom matches (§6).
property p_hi_half_never_candidate;
@(posedge clk) disable iff (!rst_n)
(SLOT_KIND[1] == SLOT_MEM64_HI) |-> !match_vec[1];
endproperty
// P5: a zero-size slot matches nothing. The guarded mask returns all-ones
// so an unimplemented BAR is inert rather than swallowing everything.
property p_zero_size_inert;
@(posedge clk) disable iff (!rst_n)
(SLOT_SIZE[5] == 0) |-> !match_vec[5];
endproperty
// P6: a misaligned base is REPORTED and does not match. §14 Model 8: the
// mask and range forms disagree in 33.3% of probes when alignment fails.
property p_misaligned_reported_not_obeyed;
@(posedge clk) disable iff (!rst_n)
err_misaligned_vec[0] |-> !match_vec[0];
endproperty
// ==================================================================
// 64-BIT APERTURES (§6).
// ==================================================================
// P7: a 64-bit aperture compares ALL address bits. §14 Model 7: a
// lower-32-only decoder claimed an address 4 GiB away in 50.1% of probes.
property p_64bit_compares_upper_bits;
@(posedge clk) disable iff (!rst_n)
(is_64bit(SLOT_KIND[0]) && match_vec[0]) |->
((addr[63:32] & size_mask(SLOT_SIZE[0])[63:32])
== (base[0][63:32] & size_mask(SLOT_SIZE[0])[63:32]));
endproperty
// P8: an address differing only in the upper 32 bits does NOT match.
property p_no_4gib_alias;
@(posedge clk) disable iff (!rst_n)
(is_64bit(SLOT_KIND[0]) && (addr[31:0] == base[0][31:0])
&& (addr[63:32] != base[0][63:32])) |-> !match_vec[0];
endproperty
// ==================================================================
// THREE OUTCOMES (§5, §9).
// ==================================================================
// P9: exactly one outcome per accepted request. No silent fourth case.
property p_outcome_total;
@(posedge clk) disable iff (!rst_n)
req_valid |-> $onehot({hit, no_match, ambiguous, disabled_hit})
|| (disabled_hit && no_match) === 1'b0;
endproperty
// P10: two or more matches are REPORTED, never resolved by priority.
// §14 Model 6: a priority encoder resolves 24.9% of probes silently.
property p_ambiguity_reported;
@(posedge clk) disable iff (!rst_n)
(req_valid && !$onehot0(match_vec)) |-> (ambiguous && !hit);
endproperty
// P11: ambiguity is computed on MATCH, so a misprogrammed overlap is
// visible even while the space is disabled (§13's audit).
property p_ambiguity_independent_of_enable;
@(posedge clk) disable iff (!rst_n)
(req_valid && !$onehot0(match_vec) && !space_enabled) |-> ambiguous;
endproperty
// P12: a hit names a slot that actually claimed the address.
property p_hit_names_claiming_slot;
@(posedge clk) disable iff (!rst_n)
hit |-> claim_vec[sel];
endproperty
// P13: no match means no claim -- the device never answers helpfully (§9).
property p_no_match_no_claim;
@(posedge clk) disable iff (!rst_n)
(req_valid && (match_vec == '0)) |-> (no_match && !hit);
endproperty
// ==================================================================
// ENABLE (§7) -- availability is not permission.
// ==================================================================
// P14: a disabled space never claims. §14 Model 9: 49.9% of in-aperture
// probes arrived disabled; an ungated decoder claims all of them.
property p_disabled_never_claims;
@(posedge clk) disable iff (!rst_n)
(!space_enabled) |-> !hit;
endproperty
// P15: a match while disabled is reported DISTINCTLY from no match --
// three different non-claims, three different debugging directions (§9).
property p_disabled_hit_distinct;
@(posedge clk) disable iff (!rst_n)
(req_valid && !space_enabled && (match_vec != '0)) |-> disabled_hit;
endproperty
// ==================================================================
// TRANSLATION (§8) and the STALL CONTRACT (Chapter 23.1 §3).
// ==================================================================
// P16: the offset is within the aperture -- it can never address outside
// the resource it was translated into.
property p_offset_within_aperture;
@(posedge clk) disable iff (!rst_n)
out_valid |-> (out_offset < ADDR_W'(SLOT_SIZE[out_target]));
endproperty
// P17: target and offset are emitted ATOMICALLY, from one decode.
property p_target_offset_atomic;
@(posedge clk) disable iff (!rst_n)
($rose(out_valid)) |-> ((out_target == $past(sel))
&& (out_offset == $past(off_c)));
endproperty
// P18: the result is STABLE under downstream stall.
property p_result_stable_under_stall;
@(posedge clk) disable iff (!rst_n)
(out_valid && !out_ready) |=>
(out_valid && $stable(out_target) && $stable(out_offset));
endproperty
// ==================================================================
// SNAPSHOT (§13's audit) and RESET.
// ==================================================================
// P19: the effective table changes only at a request-accept boundary --
// a BAR written mid-decode does not change the decision in flight.
property p_table_snapshot_at_boundary;
@(posedge clk) disable iff (!rst_n)
(base_eff[0] != $past(base_eff[0])) |-> $past(take);
endproperty
// P20: a configuration change while a decode is in flight is REPORTED.
property p_inflight_change_reported;
@(posedge clk) disable iff (!rst_n)
(!take && (base_live[0] != base_eff[0])) |=> cfg_changed_in_flight;
endproperty
// P21: reset claims nothing and emits nothing.
property p_reset_claims_nothing;
@(posedge clk)
(!rst_n) |=> (!hit && !out_valid && (base_eff[0] == '0));
endproperty
// P22: the decoder never drives the inbound interface -- it decides, it
// does not backpressure (Chapter 23.1 §4).
property p_decoder_does_not_backpressure;
@(posedge clk) disable iff (!rst_n)
$stable(req_valid) or !$stable({hit, no_match, ambiguous});
endpropertyTwenty-two properties. P1–P6 are the comparison and its precondition; P7 and P8 are the 64-bit case, which is the only place in this chapter where a bug is invisible below 4 GiB; P9–P13 are the three outcomes, the same contract as Chapter 21.1 §12; and P14, P15 are the enable gate that separates a fact about numbers from a decision about behaviour.
14. Measured Behaviour
15. Verification — DV and Mutations
DV, against the independent range-scan oracle of §12 — deliberately written in a different form so a shared misconception cannot make both agree: an address at the exact base · at base + size − 1 · at base + size (must miss) · at base − 1 (must miss) · a 4 GiB alias of a 64-bit aperture · a probe with the space disabled · overlapping apertures · an unimplemented slot · a zero-size slot · a misaligned base · a BAR written while a request is in the decode pipeline · a single-slot configuration (N_SLOT = 1) · a downstream stall on a hit · reset mid-decode.
| # | Mutation | Symptom | Caught by |
|---|---|---|---|
| 1 | Range compare with an unchecked base | 33.3% wrong when the base is misaligned (§14) | P2, P6 |
| 2 | Silently align a misprogrammed base | decodes an aperture software did not program (§4) | P6 |
| 3 | Compare only the lower 32 bits of a 64-bit BAR | claims an address 4 GiB away in 50.1% of probes | P7, P8 |
| 4 | Treat a 64-bit pair as two independent BARs | phantom aperture at the high half's value | P4 |
| 5 | Include a SLOT_MEM64_HI slot as a decode candidate | address bits interpreted as a base | P4 |
| 6 | Priority-encode the match vector | 24.9% resolved silently; one aperture unreachable | P10 |
| 7 | Drop the ambiguous output entirely | an overlap looks like a clean hit | P9, P10 |
| 8 | Compute ambiguity from claim_vec instead of match_vec | overlaps invisible while disabled — i.e. during enumeration | P11 |
| 9 | Report a hit for a slot that did not claim | the wrong resource is selected | P12 |
| 10 | Decode before checking space_enabled | claims 49.9% of in-aperture probes it must not (§14) | P14 |
| 11 | Collapse disabled_hit and no_match into one output | four causes, one symptom; §16 becomes impossible | P15 |
| 12 | Let an unimplemented slot match | a BAR that was never programmed claims addresses | P3 |
| 13 | Use ~(size-1) with size = 0 | an all-zero mask matches every address | P5 |
| 14 | Search apertures sequentially and stop at the first hit | structurally cannot detect the second match | P10 |
| 15 | Emit addr instead of the offset | the resource re-decodes; breaks when the BAR moves (§8) | P16 |
| 16 | Compute the offset with a stale sel | one packet's target with another's offset | P17 |
| 17 | Let target and offset change under stall | the resource sees a different access than was decoded | P18 |
| 18 | Read the BAR registers combinationally during decode | a mid-flight write changes a decision already made | P19, P20 |
| 19 | Apply a mid-flight configuration change silently | no evidence of a race that produced a wrong claim | P20 |
| 20 | Claim an address outside every aperture "to be safe" | corrupts another device's transaction (§9) | P13 |
| 21 | Let the decoder backpressure the inbound path | decode latency becomes link backpressure (23.1 §4) | P22 |
| 22 | Assume N_SLOT is at least 2 | $clog2(1) = 0; a zero-width select | design review |
| 23 | Use the DUT's own mask function in the DV oracle | a shared misconception makes both agree | design review |
| 24 | Publish the decode tables by hand | the 4 GiB alias and the misalignment cases are invisible | design review |
Two counterexamples worth stating explicitly.
Mutation 3 is the one that ships in a working product. A decoder written when the device only had 32-bit BARs is extended to support a 64-bit BAR, and the extension adds the upper register to the configuration path but not to the comparison. Every test passes, because test systems place apertures below 4 GiB where the upper half is zero and the two models are indistinguishable. The failure requires a system that maps the device above 4 GiB — which is increasingly the normal case — and it manifests as the device answering requests for a completely different device. §14 Model 7 measured 50.1% of aliased probes claimed. P8 is the property, and it is written specifically to be reachable only by a probe that differs in the upper bits.
Mutation 8 is subtler than mutation 6 and worse. A designer who has correctly implemented the ambiguity output may still compute it from claim_vec — the vector that already has enable folded in — reasoning that an overlap does not matter while the device cannot respond. But enumeration programs the BARs with the space disabled (9.4 §3), so the disabled window is exactly when a transient overlap exists. Computing ambiguity from match_vec makes the fault visible at the moment it is created, rather than after the space is enabled and the wrong aperture has started answering. P11 exists for this one case.
16. Debugging
Symptom — the device responds to addresses that belong to another device.
Two candidates, and they are distinguishable in one read. If the offending addresses are exactly 4 GiB from one of your apertures, this is mutation 3 — a lower-32-only compare (§6). If they are unrelated, check err_misaligned_vec (§4) and whether a zero-size slot produced an all-ones match (mutation 13). Both produce over-claiming; only the first has that distinctive 4 GiB signature.
Symptom — one BAR's resource is completely unreachable while the others work.
Suspect an overlap (§5). §14 Model 6 measured a priority encoder resolving 24.9% of probes to the same BAR — and the losing aperture is unreachable at every address, not intermittently. Read ambiguous; if the design has no such output, compare the programmed bases and masks numerically rather than checking each for plausibility.
Symptom — the device claims addresses during enumeration and everything is fine afterwards. Decode-before-enable (§7, mutation 10). During sizing the BAR transiently holds values that are not the final assignment, and the space is disabled precisely so those values are inert. A decoder that ignores enable claims whatever those transient values named. The signature is bring-up-only and timing-dependent.
Symptom — reads return data from the wrong offset within the right resource.
Translation, not decode (§8). Check whether the offset is masked (addr & (size-1)) or subtracted, and whether the target and offset are emitted atomically (mutation 16). A stale sel paired with a fresh offset produces exactly this: right resource, wrong place — or, worse, the reverse.
Symptom — accesses fail only after software relocates a BAR.
Two possibilities. The resource is receiving the system address rather than an offset (mutation 15), so it breaks whenever the base changes. Or the table is read combinationally and a request in flight during the reprogramming used a half-updated table (mutation 18). cfg_changed_in_flight distinguishes them immediately — it is set only for the second.
Symptom — the device is enumerated, the BARs look correct, and it never responds.
Work through the non-claims in order (§9): is no_match asserting (the address is not what you think), disabled_hit (the Command register), or ambiguous (a misprogrammed overlap)? Three outputs, three completely different fixes — and a design that reports one generic "miss" cannot tell you which. Chapter 9.6 §7's reminder also applies: local decode is not reachability, so a correct decoder can still be unreachable because something upstream does not route to it (21.3 §5).
17. Misconceptions
"A BAR comparison is a range check." It is a mask compare, exact because the base is aligned, and free of an adder (§3).
"Alignment is a nice-to-have." It is the precondition the mask form rests on — 33.3% wrong without it (§4).
"A 64-bit BAR is two BARs." It is one aperture in two slots (9.4 §12), and treating it as two claims addresses 4 GiB away in 50.1% of probes (§6).
"Only the low 32 bits matter; nothing is mapped above 4 GiB." Increasingly untrue, and the bug is invisible until it is not (§6).
"Two BARs can't overlap — enumeration prevents it." Enumeration establishes it; partial configuration, hot-plug and dual assignment break it (§5).
"If two match, take the first." That resolves 24.9% of probes silently and makes the other aperture permanently unreachable (§5).
"A match means I should respond." Match is a fact about numbers; claim requires the space to be enabled (§7).
"The space is disabled, so overlaps don't matter." Disabled is exactly when enumeration creates them (mutation 8).
"An unimplemented BAR is harmless." With an unguarded ~(size-1) at size 0 the mask is all-zero and it matches everything (mutation 13).
"Send the resource the full address; it can work it out." Then the resource re-implements the decode and breaks when the BAR moves (§8).
"Claim it anyway — better than a UR." A wrong Completion is worse than a UR, because the requester believes it (§9).
"BAR registers can be read combinationally during decode." Software can write them mid-decode; snapshot at the accept boundary (§13).
"If my decode is right, the device is reachable." Local decode is not reachability (9.6 §7).
18. Understanding Check
Q1. Why does the decoder use (addr & mask) == base rather than base <= addr < base + size?
They are equivalent for an aligned power-of-two aperture — §14 Model 8 found 0 disagreements over 300,000 probes — and the mask form needs no adder and no carry chain, which matters on an inbound-critical path that must compare every aperture in parallel (§3). The equivalence has a precondition: with a misaligned base the forms disagree in 33.3% of probes, which is why §12 reports misalignment rather than assuming it away.
Q2. Your device works on one server and answers other devices' requests on another. What is the first thing you check? Whether the second system maps your BAR above 4 GiB (§6). A decoder comparing only the low 32 bits is indistinguishable from a correct one below 4 GiB and claims aliased addresses above it — §14 Model 7 measured 50.1% of aliased probes claimed. The distinctive signature is that the offending addresses are exactly 4 GiB from one of your apertures.
Q3. Two BARs both match an inbound address. What should the hardware do, and what is the cost of doing the obvious thing? Report it as a configuration fault and claim nothing (§5). The obvious thing — a priority encoder — resolves 24.9% of probes to the same BAR, silently and consistently, so the other aperture's resource is unreachable at every address with no error anywhere (§14 Model 6). Reporting turns a permanent silent failure into a readable register bit.
Q4. Should ambiguous be computed from the match vector or from the claim vector, and why does it matter?
From the match vector (P11). Enumeration programs BARs with the memory space disabled (9.4 §3), so a transient overlap exists precisely during the window when claim_vec is all zeros. Computing ambiguity from claims makes the fault invisible at the moment it is created and visible only later, after the wrong aperture has begun answering (mutation 8).
Q5. An address falls inside a BAR while the Memory Space Enable bit is clear. Three possible behaviours — which is correct and why? Do not claim, and report it distinctly from a no-match (§7, §9). Claiming is wrong: §14 Model 9 shows 49.9% of in-aperture probes arriving disabled, and claiming them means answering with transient enumeration values. Reporting it as a plain no-match is also wrong — it loses the information that the address did match, which is what tells the debugger to look at the Command register rather than at the base values (§16).
Q6. Why does §12 snapshot the aperture table at the request-accept boundary instead of reading the registers directly?
Because software may write a BAR while a request is already in the decode pipeline. A combinational read lets a decision be made partly against the old table and partly against the new one — the same failure Chapter 19.4 §12 established and 21.1 §13 reused for routing. Sampling once, whole, at a defined boundary makes the decision self-consistent, and cfg_changed_in_flight records that the race occurred so it is diagnosable afterwards.
19. What's Next
The inbound path now has an answer. An address arrives, exactly one aperture claims it or none does, and the resource behind it receives a target and an offset rather than a system address.
Three blocks in Chapter 23.1's Figure 1 are still empty. 23.3 builds the descriptor-driven DMA engine that will originate requests on the requester path — and it consumes the packet descriptors Chapter 22.4 §10's chunker produces. 23.4 builds TLP assembly, the block that turns those descriptors into packets and drives them onto the interface under 23.1 §3's stability contract. 23.5 builds completion logic — including generating the Unsupported Request this chapter's no_match output asks for (§9), and the (Requester ID, Tag) matching Chapter 21.4 §3 established.
And 23.6 collects the patterns. Several have now appeared three times each — the three-outcome decoder (21.1, 21.3, here), the snapshot at an ownership boundary (19.4, 21.1, 22.4, here), and availability-is-not-permission (16.5, 22.3, here). That repetition is the module's real subject, and 23.6 is where it gets named.