Skip to content

PCIe · Module 25

BAR Problems — Where Does Address Ownership Break?

A BAR problem is an address-ownership mismatch, and it has two outcomes that need opposite debugging. Five ownership boundaries, the alias containment result, and why a truncated comparator is only fatal in company.

Chapter 25.2 §9 stopped at the moment a device's BAR windows are programmed and handed the next stage away. This chapter takes it.

A BAR problem is not a "device not responding" problem. It is an address-ownership mismatch — a disagreement about which agent owns a given address — and the whole of this chapter follows from that one framing.

1. Sources, Scope, and the Boundary With Module 9 and 23.2

2. A BAR Problem Is an Address-Ownership Mismatch

Start from the symptom vocabulary and throw most of it away. "The device doesn't respond." "Reads return all-ones." "The driver reads garbage." "It works until we enable the second BAR." These describe four different faults and none of them names a mechanism.

Every one of them is the same sentence underneath: some agent believed it owned an address, and either it was wrong, or nobody believed it.

That reframing does real work, because ownership is transitive and checkable. An address travelling from the CPU to a register passes through a chain of claims:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
CPU physical address
  -> the root complex claims it for PCIe (or for DRAM, or for nothing)
    -> a bridge/switch window claims it for a downstream hierarchy
      -> a device claims it with one of its BARs
        -> that BAR's offset selects one local target
          -> the target decodes the offset to a register

Each arrow is an agreement between two parties, and each can be broken alone. The chain is exactly 25.1 §2's method applied to a single address: find the last link that was provably correct, and the first that was not.

Two consequences follow immediately, and they organise the rest of the chapter.

First, a BAR problem is rarely inside the BAR. Of the five boundaries in §3, only two live in the endpoint's decoder. The other three are the parent window, the uniqueness of the claim, and the local target — and §15's cases show the parent window is the most common real-world culprit and the one engineers check last.

Second, the fault has a direction. An ownership mismatch either denies an address that should have been claimed, or grants one that should not have been. Those two produce opposite evidence and demand opposite instruments, and conflating them is the single most expensive mistake in BAR debugging. §4 is entirely about that split.

3. The Five Ownership Boundaries

A left-to-right chain of five decision stages. Stage one is the parent window check, stage two the BAR comparator, stage three the uniqueness check, stage four the offset range check, and stage five the local target decode. Each stage has a rejection path leading to an Unsupported Request, except the fifth which leads to a register.Request address1 · Parent window2 · BAR match3 · Uniqueness4 · Offset range5 · Local targetRegisterUnsupported RequestWrong registernot claimedambiguouswrong offset12
Figure 1 — the five boundaries an address must cross to reach a register, and what each one rejects. The boundaries are ordered: a failure at an earlier boundary hides every later one, which is why a diagnosis must name the first disagreement rather than any disagreement.

The ordering is the point. These are not five independent tests you can run in any sequence — they are a pipeline, and an earlier failure masks every later one. A device whose parent window is wrong will look identical to a device whose BAR is wrong, because the request never reaches the BAR at all.

That is why §12's table reports the first disagreeing boundary, and why §15's cases proceed strictly outside-in. Checking the endpoint's BAR registers first is the natural instinct and it is the wrong order: you are inspecting boundary 2 while boundary 1 has already discarded the request.

Boundary 3 deserves a note, because it is the one that has no analogue in a single-BAR mental model. Uniqueness is not a property of any one BAR — it is a property of the set. Two BARs, each individually correct, each individually passing every self-check, can overlap. Neither one is broken. The configuration is. §10's decoder therefore reports three outcomes rather than two, and P7–P9 assert on the set rather than on any member.

Boundary 5 is where the chapter's most dangerous fault class lives, and it is the reason §4 exists.

4. Two Outcomes, and Why They Need Opposite Debugging

An ownership mismatch resolves in one of two directions, and everything about how you find it changes with the direction.

§12 measured all six fault classes against a golden decoder and sorted them by direction. The results are the reason this classification leads the chapter:

Injected faultRejected an address it ownsRejected at a different boundaryAccepted, wrong register
none (correct)000
high address bits dropped036,0980
limit compare inclusive020,0060
two BARs claim it64,34900
local offset truncated0087,414
parent window one page short41,33758,2800

Read the middle column carefully, because it is the good news. "Rejected at a different boundary" means the fault was contained: a downstream check caught what an upstream one got wrong. An inclusive limit comparison (addr <= base + size) lets one byte too many past the comparator — and then the offset range check rejects it anyway, because off == size fails off < size. The bug is real, the outcome is still safe, and this is the strongest practical argument for layering the checks rather than collapsing them into one expression.

Only one fault in the table reaches Direction B, and it is the one where the offset is wrong rather than the claim. That is not a coincidence, and §5 shows why.

5. Width — the Alias, and Why Truncation Is Only Fatal in Company

The classic 64-bit BAR bug is described as "the comparator ignores the upper address bits". A 64-bit BAR is programmed above 4 GiB; the decoder compares only addr[31:0]; therefore — the story goes — an address that matches in its low 32 bits but differs above is wrongly claimed, and the device answers for memory it does not own.

The story is half right, and the half that is wrong matters.

Variant B is the one everyone calls the bug, and it rejected every single alias. The comparator did match — that part of the story is true. But the offset was computed at full width, so addr - base came out enormous, the range check off < size failed, and the request was rejected at boundary 4. The truncation was contained by the boundary downstream of it.

Variant C accepted all 200,000 and handed the local target an offset that is perfectly valid for the wrong address. Successful Completion, wrong register, no error bit anywhere. That is Direction B, and it is the only configuration of the three that produces it.

The lesson is not "never truncate". It is sharper and more useful:

The comparator and the offset must be computed at the same width, and a range check on the offset must follow both.

A design that truncates consistently is broken. A design that truncates the comparator but not the offset is self-containing — ugly, worth fixing, and not a data-corruption risk. Knowing which one you have changes the severity of the bug report, and you cannot tell them apart by reading the comparator alone. §10's bar_decode_checked computes both at parameterised width for exactly this reason, and P10 and P11 assert the two halves separately.

This also explains a confusing field observation. Engineers report 64-bit BAR aliasing bugs that "cause URs instead of corruption" and assume they have misdiagnosed. They have not. That is variant B behaving exactly as measured.

6. Boundary Arithmetic

Three addresses matter, and one of them is the bug. For size = 64 KiB at base = 0x1_0000_0000:

addressmask comparison (addr & ~(size-1)) == basebase <= addr <= base + size
basetruetrue
base + size - 1 (last valid byte)truetrue
base + size (first invalid byte)falsetrue

The mask form is correct by construction and the inclusive form is off by exactly one byte. This is not a subtle arithmetic point — it is the direct consequence of a window of size bytes containing addresses base through base + size - 1.

Two things make it worth its own section.

The mask form only works because BARs are naturally aligned. (addr & ~(size-1)) == base is valid precisely because base has zeros in every bit below size, which 9.2 and 9.4 establish as a BAR property. A decoder that uses the mask form without asserting alignment has a hidden dependency, and P1 exists to make that dependency explicit rather than assumed. If a base is ever programmed misaligned — by a broken allocator, by a test, by a register write during a window where the BAR is live — the mask comparator does not degrade gracefully. It claims a differently-shaped region entirely.

And the off-by-one is contained, which is why it survives. §4 measured 20,006 affected probes and all of them were still rejected, one boundary later. An inclusive limit comparison can live in a design for years, passing every directed test, because the range check keeps cleaning up after it. It becomes visible only when someone removes the range check as redundant — and it is redundant, right up until it isn't.

7. What a Size Readback Proves

Chapter 9.4 owns the sizing handshake. This section asks a narrower, debugging-specific question: when you read a BAR back, what have you actually established?

The arithmetic, reproduced only for reference:

size32-bit readback(~readback) + 1
4 KiB0xFFFFF0004,096
1 MiB0xFFF000001,048,576
256 MiB0xF0000000268,435,456

A readback proves exactly one thing: which bits of the BAR are writable. That is the mechanism — low bits are hardwired to zero, so the first writable bit marks the size.

It does not prove any of the following, and every one of them is a real debugging error:

  • It does not prove the BAR is enabled. Memory Space Enable is a separate control. A perfectly-sized BAR with decoding disabled claims nothing.
  • It does not prove the base is programmed. Sizing and assignment are different operations (9.5); a device can report a correct size and hold a base of zero.
  • It does not prove the parent window contains the assigned base. Boundary 1 is invisible from the endpoint's own registers — this is §15 case 1 and the single most common real fault.
  • It does not prove the decoder implements the size it reports. The readback comes from the BAR register's writability. The comparator is separate logic. A device can advertise 1 MiB and decode 64 KiB, and nothing in the handshake catches it — which is why §10's bar_size_selfcheck exists and P4 asserts the two agree.

That last point is the one worth internalising. The sizing handshake is a conversation with a register, not with the decoder. They are supposed to be derived from the same parameter. When they are not, every host-side check passes and every access above the real aperture is rejected.

8. The Waveform

An in-range probe, then one byte past the aperture

10 cycles
Ten cycles of a BAR decoder. A request is valid at cycle 1 and cycle 6. In the first access the window check, BAR hit and offset range check all assert and a single local select asserts at cycle 3. In the second access at cycle 6 the BAR hit asserts because the limit comparison is inclusive, but the offset range check does not assert, so select valid stays low and an unsupported request is signalled at cycle 8.window and comparator agreewindow and comparator agreeoffset in range — one selectoffset in range — oneselectinclusive limit lets the hit assertinclusive limit lets thehit assertrange check rejects — containedrange check rejects —containedclkreq_validwin_okbar_hitoff_in_rngsel_validur_outt0t1t2t3t4t5t6t7t8t9
Figure 2 — two probes into the same BAR. The first is inside the aperture: the window check, the comparator and the range check all agree, and one local select asserts. The second is one byte past the aperture, where an inclusive limit comparison lets the match assert but the offset range check rejects it — the contained failure §4 and §6 measured, visible here as bar_hit high while sel_valid stays low.

Four readings, and the third is the one to carry away.

win_ok asserts before bar_hit in both accesses. The ordering in the trace is the ordering in §3 — the parent window is upstream of the comparator, and a trace where bar_hit asserts without win_ok is a decoder that has been wired to bypass boundary 1.

In the first access all three checks agree and exactly one sel_valid results. That is the healthy signature, and P7's one-hot assertion is written directly against it.

In the second access bar_hit asserts and off_in_rng does not. This is the contained off-by-one from §6 drawn out in time. bar_hit alone is not evidence that the address was owned — a fact that matters enormously when someone probes only bar_hit on a debug port and concludes the decoder claimed an address it rejected.

And ur_out asserts two cycles after the failing probe, not in the same cycle. The delay is ordinary pipelining, and it is the reason §15's cases insist on correlating by request rather than by cycle: the UR you see is not necessarily caused by the access adjacent to it in the trace.

9. The Instruments, Named

Before the RTL, the list of what is being built and what question each answers. Every one exists because some boundary in §3 is otherwise unobservable.

InstrumentAnswers
bar_window_checkdid the parent window claim this address? (boundary 1)
bar_decode_checkeddoes this BAR claim it, and at what width? (boundary 2)
bar_set_decodedoes exactly one BAR claim it? (boundary 3)
bar_offset_guardis the offset inside the aperture? (boundary 4)
bar_target_decodedoes that offset select a target that exists? (boundary 5)
bar_size_selfcheckdoes the advertised size match the decoded size? (§7)
bar_first_rejectwhich boundary rejected first, sticky, per request
bar_probe_enginea directed sweep of the aperture edges (§15 case 4)

The last two are the debugging instruments proper. The first six are a decoder written so that each boundary is separately observable; bar_first_reject is what turns that observability into a diagnosis, and it is the module §15 uses in seven of its nine cases.

10. RTL — The Decode Instruments

Block 1 — the parameter package and the boundary encoding. Every module below shares it, and the boundary enumeration is what makes bar_first_reject comparable across designs.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
package bar_dbg_pkg;
 
  // Boundary identifiers, ordered.  A smaller value is further upstream, so
  // "first rejection" is a minimum over the boundaries that rejected.
  typedef enum logic [2:0] {
    BND_NONE       = 3'd0,   // accepted by every boundary
    BND_WINDOW     = 3'd1,   // parent/bridge window did not claim the address
    BND_MATCH      = 3'd2,   // no BAR comparator claimed it
    BND_UNIQUE     = 3'd3,   // more than one BAR claimed it
    BND_OFFSET     = 3'd4,   // offset outside [0, size)
    BND_TARGET     = 3'd5    // offset selects a target that does not exist
  } bar_bnd_e;
 
  // Guarded width: $clog2(1) is 0 in some tools, which produces a zero-width
  // vector.  Every width in this chapter is computed through this function.
  function automatic int unsigned gw(input int unsigned n);
    return (n <= 1) ? 1 : $clog2(n);
  endfunction
 
  // The size encoding used throughout: a BAR of 2**LOG2_SIZE bytes.
  // Expressing size as its log2 makes the alignment property structural —
  // a base is aligned iff its low LOG2_SIZE bits are zero (see P1).
  function automatic logic [63:0] size_mask(input int unsigned log2_size);
    return ~((64'd1 << log2_size) - 64'd1);
  endfunction
 
endpackage

Block 2 — the parent window check, boundary 1. It is deliberately a separate module from the BAR comparator, because in a real system it is separate silicon, and the most common BAR fault (§15 case 1) lives here rather than in the endpoint.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module bar_window_check #(
  parameter int unsigned ADDR_W = 64
)(
  input  logic                     clk,
  input  logic                     rst_n,
  input  logic                     req_valid,
  input  logic [ADDR_W-1:0]        req_addr,
  // The window this hierarchy claims.  Programmed by the parent bridge;
  // NOT visible from the endpoint's own configuration space (§7).
  input  logic                     win_enabled,
  input  logic [ADDR_W-1:0]        win_base,
  input  logic [ADDR_W-1:0]        win_limit,   // inclusive: last owned byte
  output logic                     win_ok,
  output logic                     win_reject
);
 
  // Full-width comparison, both ends.  The limit is INCLUSIVE and named so,
  // because the whole of §6 is about which end is which.
  always_comb begin
    win_ok     = win_enabled && req_valid &&
                 (req_addr >= win_base) && (req_addr <= win_limit);
    win_reject = req_valid && !win_ok;
  end
 
endmodule

Block 3 — the BAR comparator, at explicitly parameterised width. §5's entire result is that the comparator width and the offset width must match, so this module takes one width and uses it for both, and exposes the offset it computed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module bar_decode_checked #(
  parameter int unsigned ADDR_W    = 64,
  parameter int unsigned CMP_W     = 64,   // width of the comparison
  parameter int unsigned LOG2_SIZE = 20    // aperture is 2**LOG2_SIZE bytes
)(
  input  logic                  clk,
  input  logic                  rst_n,
  input  logic                  bar_enabled,
  input  logic [ADDR_W-1:0]     bar_base,
  input  logic                  req_valid,
  input  logic [ADDR_W-1:0]     req_addr,
  output logic                  bar_hit,
  output logic [ADDR_W-1:0]     bar_offset,
  output logic                  offset_in_range
);
 
  import bar_dbg_pkg::*;
 
  localparam logic [63:0] SZ   = 64'd1 << LOG2_SIZE;
  localparam logic [63:0] MASK = size_mask(LOG2_SIZE);
 
  // The comparison, at CMP_W.  CMP_W < ADDR_W is the truncated variant of §5.
  logic [CMP_W-1:0] cmp_addr, cmp_base;
  assign cmp_addr = req_addr[CMP_W-1:0];
  assign cmp_base = bar_base[CMP_W-1:0];
 
  always_comb begin
    bar_hit = bar_enabled && req_valid &&
              ((cmp_addr & MASK[CMP_W-1:0]) == (cmp_base & MASK[CMP_W-1:0]));
 
    // The offset is computed at FULL width regardless of CMP_W.  This is
    // variant B in §5, and it is what contains a truncated comparator:
    // an aliased address produces an offset far outside the aperture.
    bar_offset      = req_addr - bar_base;
    offset_in_range = bar_hit && (bar_offset < SZ);
  end
 
endmodule

Block 4 — the set decoder, boundary 3. Uniqueness is a property of the set, so this module is the only place it can be checked. It reports three outcomes, never two — the pattern 23.6 §5 establishes for any decoder that can be ambiguous.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module bar_set_decode #(
  parameter int unsigned NBAR = 6
)(
  input  logic                     clk,
  input  logic                     rst_n,
  input  logic                     req_valid,
  input  logic [NBAR-1:0]          bar_hit,      // one bit per aperture
  output logic                     sel_valid,    // exactly one claimed it
  output logic [bar_dbg_pkg::gw(NBAR)-1:0] sel_idx,
  output logic                     sel_none,     // nobody claimed it
  output logic                     sel_ambig     // more than one claimed it
);
 
  import bar_dbg_pkg::*;
 
  logic [gw(NBAR+1)-1:0] hit_count;
 
  always_comb begin
    hit_count = '0;
    for (int i = 0; i < NBAR; i++) hit_count += bar_hit[i];
 
    sel_none  = req_valid && (hit_count == 0);
    sel_ambig = req_valid && (hit_count >  1);
    sel_valid = req_valid && (hit_count == 1);
 
    // Priority encode only in the unambiguous case.  Encoding an ambiguous
    // set would silently pick the lowest index — which is exactly the
    // failure mode mutation 14 injects, and it converts a detectable
    // configuration error into Direction B corruption.
    sel_idx = '0;
    if (sel_valid) begin
      for (int i = NBAR-1; i >= 0; i--) if (bar_hit[i]) sel_idx = gw(NBAR)'(i);
    end
  end
 
endmodule

Block 5 — the local target decode, boundary 5, plus the size self-check of §7. Two small modules, together because they are the pair that catches a decoder disagreeing with its own advertised size.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module bar_target_decode #(
  parameter int unsigned ADDR_W    = 64,
  parameter int unsigned LOG2_SIZE = 20,
  parameter int unsigned NTARGET   = 4
)(
  input  logic                  req_valid,
  input  logic [ADDR_W-1:0]     bar_offset,
  input  logic                  offset_in_range,
  output logic                  tgt_valid,
  output logic [bar_dbg_pkg::gw(NTARGET)-1:0] tgt_idx,
  output logic                  tgt_reject
);
 
  import bar_dbg_pkg::*;
  localparam int unsigned STRIDE_LOG2 = LOG2_SIZE - gw(NTARGET);
 
  logic [ADDR_W-1:0] idx_full;
 
  always_comb begin
    idx_full   = bar_offset >> STRIDE_LOG2;
    tgt_valid  = req_valid && offset_in_range && (idx_full < NTARGET);
    tgt_idx    = tgt_valid ? gw(NTARGET)'(idx_full) : '0;
    // A target index at or above NTARGET means the aperture is larger than
    // the targets behind it.  That is a design error, not a host error,
    // and it must be reported rather than wrapped (mutation 21).
    tgt_reject = req_valid && offset_in_range && (idx_full >= NTARGET);
  end
 
endmodule
 
module bar_size_selfcheck #(
  parameter int unsigned LOG2_SIZE = 20      // what the DECODER implements
)(
  input  logic [31:0] bar_readback,          // what the REGISTER advertises
  output logic        size_agrees,
  output logic [63:0] advertised_size,
  output logic [63:0] decoded_size
);
 
  // The advertised size is derived exactly as §7 describes: the readback's
  // writable field, inverted, plus one.  Bits [3:0] carry BAR type
  // information rather than address, and are excluded (see 9.2 and 9.4).
  always_comb begin
    advertised_size = {32'd0, (~(bar_readback & 32'hFFFF_FFF0)) + 32'd1};
    decoded_size    = 64'd1 << LOG2_SIZE;
    size_agrees     = (advertised_size == decoded_size);
  end
 
endmodule

Block 6 — the first-rejection recorder. This is the instrument the chapter exists to produce. It is sticky, per-request, and it records the first boundary to reject, which §3 argued is the only diagnosis that means anything.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module bar_first_reject #(
  parameter int unsigned ADDR_W = 64
)(
  input  logic              clk,
  input  logic              rst_n,
  input  logic              req_valid,
  input  logic [ADDR_W-1:0] req_addr,
  // one bit per boundary, all sampled in the same cycle as req_valid
  input  logic              rej_window,
  input  logic              rej_match,
  input  logic              rej_unique,
  input  logic              rej_offset,
  input  logic              rej_target,
  input  logic              clear,          // explicit, software-initiated
  output bar_dbg_pkg::bar_bnd_e first_bnd,
  output logic [ADDR_W-1:0] first_addr,
  output logic              captured,
  output logic [31:0]       reject_count    // saturating
);
 
  import bar_dbg_pkg::*;
 
  bar_bnd_e this_bnd;
 
  // The minimum boundary that rejected THIS request.  Because BND_* is
  // ordered upstream-to-downstream, "first" is a priority select, and the
  // priority is the pipeline order in §3 — not an arbitrary choice.
  always_comb begin
    if      (rej_window) this_bnd = BND_WINDOW;
    else if (rej_match ) this_bnd = BND_MATCH;
    else if (rej_unique) this_bnd = BND_UNIQUE;
    else if (rej_offset) this_bnd = BND_OFFSET;
    else if (rej_target) this_bnd = BND_TARGET;
    else                 this_bnd = BND_NONE;
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      first_bnd    <= BND_NONE;
      first_addr   <= '0;
      captured     <= 1'b0;
      reject_count <= '0;
    end else if (clear) begin
      first_bnd    <= BND_NONE;
      first_addr   <= '0;
      captured     <= 1'b0;
      reject_count <= '0;
    end else if (req_valid && (this_bnd != BND_NONE)) begin
      // Sticky: the FIRST failing request is kept, not the most recent.
      // 25.1 §6 established why — the most recent failure is usually a
      // consequence of the first, and overwriting destroys the cause.
      if (!captured) begin
        first_bnd  <= this_bnd;
        first_addr <= req_addr;
        captured   <= 1'b1;
      end
      // Saturating, so a wedged system does not wrap the evidence away.
      if (reject_count != 32'hFFFF_FFFF) reject_count <= reject_count + 32'd1;
    end
  end
 
endmodule

Block 7 — the aperture probe engine. §15's cases repeatedly need the same directed sweep: the edges of a window, and the aliases around it. Doing it by hand is where wrong conclusions come from, because the interesting addresses are exactly the ones a random sweep almost never generates.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module bar_probe_engine #(
  parameter int unsigned ADDR_W    = 64,
  parameter int unsigned LOG2_SIZE = 20
)(
  input  logic              clk,
  input  logic              rst_n,
  input  logic              start,
  input  logic [ADDR_W-1:0] bar_base,
  output logic              probe_valid,
  output logic [ADDR_W-1:0] probe_addr,
  output logic [3:0]        probe_kind,
  output logic              done
);
 
  localparam logic [63:0] SZ = 64'd1 << LOG2_SIZE;
 
  // The eight addresses that matter.  Every one of them is a boundary in
  // §6 or an alias in §5; none of them is likely to appear in a random
  // sweep, which is why 25.3's "prove the edges" rule applies here too.
  localparam int unsigned NPROBE = 8;
 
  logic [3:0] idx;
  logic       running;
 
  always_comb begin
    probe_addr = '0;
    unique case (idx)
      4'd0: probe_addr = bar_base;                        // first byte
      4'd1: probe_addr = bar_base + SZ - 64'd1;           // last valid byte
      4'd2: probe_addr = bar_base + SZ;                   // first byte past
      4'd3: probe_addr = bar_base - 64'd1;                // last byte before
      4'd4: probe_addr = bar_base ^ (64'd1 << 32);        // 4 GiB alias
      4'd5: probe_addr = bar_base + (SZ >> 1);            // aperture midpoint
      4'd6: probe_addr = bar_base + SZ + (SZ >> 1);       // clearly outside
      4'd7: probe_addr = bar_base | (SZ - 64'd1);         // all offset bits set
      default: probe_addr = bar_base;
    endcase
    probe_kind = idx;
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      idx <= '0; running <= 1'b0; probe_valid <= 1'b0; done <= 1'b0;
    end else begin
      probe_valid <= 1'b0;
      done        <= 1'b0;
      if (start && !running) begin
        running <= 1'b1; idx <= '0;
      end else if (running) begin
        probe_valid <= 1'b1;
        if (idx == 4'(NPROBE-1)) begin
          running <= 1'b0; done <= 1'b1;
        end else begin
          idx <= idx + 4'd1;
        end
      end
    end
  end
 
endmodule

Block 8 — the reference decoder, for the comparison §12 runs. A golden model is only useful if it is written independently of the design under test, so this one is expressed as a direct statement of §3's five boundaries with no shared code.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// verilog_lint: waive-start module-filename
module bar_golden_decode #(
  parameter int unsigned ADDR_W    = 64,
  parameter int unsigned LOG2_SIZE = 20,
  parameter int unsigned NTARGET   = 4
)(
  input  logic [ADDR_W-1:0] addr,
  input  logic [ADDR_W-1:0] base,
  input  logic [ADDR_W-1:0] win_lo,
  input  logic [ADDR_W-1:0] win_hi,
  input  logic              other_bar_hits,
  output bar_dbg_pkg::bar_bnd_e verdict,
  output logic [bar_dbg_pkg::gw(NTARGET)-1:0] target
);
 
  import bar_dbg_pkg::*;
  localparam logic [63:0] SZ = 64'd1 << LOG2_SIZE;
 
  logic [ADDR_W-1:0] off;
 
  // Written as five sequential questions, in §3's order, at full width and
  // with no sharing of expressions with the DUT.  This is deliberately the
  // slowest possible formulation.
  always_comb begin
    off     = addr - base;
    target  = '0;
    if      (!(addr >= win_lo && addr <= win_hi))       verdict = BND_WINDOW;
    else if ((addr & size_mask(LOG2_SIZE)) != base)     verdict = BND_MATCH;
    else if (other_bar_hits)                            verdict = BND_UNIQUE;
    else if (!(off < SZ))                               verdict = BND_OFFSET;
    else if ((off >> (LOG2_SIZE - gw(NTARGET))) >= NTARGET) verdict = BND_TARGET;
    else begin
      verdict = BND_NONE;
      target  = gw(NTARGET)'(off >> (LOG2_SIZE - gw(NTARGET)));
    end
  end
 
endmodule
// verilog_lint: waive-stop module-filename

11. Assertions

Structural properties — the assumptions the decoders rest on.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P1 — natural alignment.  Every mask comparator in §10 is valid only
// under this, and §6 explained that it degrades ungracefully, not
// gradually, if violated.
property p1_base_aligned;
  @(posedge clk) disable iff (!rst_n)
    bar_enabled |-> ((bar_base & ((64'd1 << LOG2_SIZE) - 64'd1)) == '0);
endproperty
a_p1: assert property (p1_base_aligned);
 
// P2 — the aperture is a power of two.  Stated over the decoded size so
// that a parameterisation error is caught at elaboration-adjacent time.
property p2_size_pow2;
  @(posedge clk) disable iff (!rst_n)
    (decoded_size != 0) |-> ((decoded_size & (decoded_size - 64'd1)) == '0);
endproperty
a_p2: assert property (p2_size_pow2);
 
// P3 — a disabled BAR claims nothing.  Trivial to state, routinely broken
// by a decoder that qualifies the select but not the hit (mutation 2).
property p3_disabled_claims_nothing;
  @(posedge clk) disable iff (!rst_n)
    !bar_enabled |-> !bar_hit;
endproperty
a_p3: assert property (p3_disabled_claims_nothing);
 
// P4 — the advertised size equals the decoded size.  §7's fourth
// non-proof: the sizing handshake talks to a register, not the decoder.
property p4_size_agrees;
  @(posedge clk) disable iff (!rst_n)
    bar_enabled |-> size_agrees;
endproperty
a_p4: assert property (p4_size_agrees);

Boundary-ordering properties — §3's pipeline expressed as implications.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P5 — the window is upstream of the comparator.  A hit without a window
// claim means boundary 1 has been bypassed in the wiring.
property p5_hit_implies_window;
  @(posedge clk) disable iff (!rst_n)
    bar_hit |-> win_ok;
endproperty
a_p5: assert property (p5_hit_implies_window);
 
// P6 — the range check is downstream of the comparator, never the reverse.
property p6_range_implies_hit;
  @(posedge clk) disable iff (!rst_n)
    offset_in_range |-> bar_hit;
endproperty
a_p6: assert property (p6_range_implies_hit);
 
// P7 — exactly one select, or none, never several.  This is boundary 3
// and it is a property of the SET.
property p7_select_onehot0;
  @(posedge clk) disable iff (!rst_n)
    req_valid |-> $onehot0(bar_hit);
endproperty
a_p7: assert property (p7_select_onehot0);
 
// P8 — the three set outcomes are mutually exclusive and total.  A decoder
// that can report none of them, or two, has an unreachable request class.
property p8_set_outcomes_exclusive;
  @(posedge clk) disable iff (!rst_n)
    req_valid |-> $onehot({sel_valid, sel_none, sel_ambig});
endproperty
a_p8: assert property (p8_set_outcomes_exclusive);
 
// P9 — ambiguity is never resolved by picking.  sel_idx must not be
// presented as meaningful when more than one aperture claimed the address.
property p9_no_pick_on_ambiguous;
  @(posedge clk) disable iff (!rst_n)
    sel_ambig |-> !sel_valid;
endproperty
a_p9: assert property (p9_no_pick_on_ambiguous);

Width properties — §5, stated as two separate obligations.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P10 — the offset is computed at full address width.  This is the half
// of §5 that CONTAINS a truncated comparator, and it must hold even when
// CMP_W < ADDR_W.
property p10_offset_full_width;
  @(posedge clk) disable iff (!rst_n)
    bar_hit |-> (bar_offset == (req_addr - bar_base));
endproperty
a_p10: assert property (p10_offset_full_width);
 
// P11 — an accepted address agrees with the base in EVERY bit above the
// aperture.  Stated at full width, this is the property a truncated
// comparator violates, independent of how the offset is computed.
property p11_upper_bits_agree;
  @(posedge clk) disable iff (!rst_n)
    offset_in_range |->
      ((req_addr & size_mask(LOG2_SIZE)) == (bar_base & size_mask(LOG2_SIZE)));
endproperty
a_p11: assert property (p11_upper_bits_agree);
 
// P12 — the comparator and the offset use the same width.  Written as a
// consistency check rather than a parameter equality so that it holds for
// a decoder whose widths are computed rather than declared.
property p12_width_consistency;
  @(posedge clk) disable iff (!rst_n)
    (bar_hit && !offset_in_range) |-> (bar_offset >= (64'd1 << LOG2_SIZE));
endproperty
a_p12: assert property (p12_width_consistency);

Boundary-arithmetic properties — §6.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P13 — the last valid byte is inside.
property p13_last_byte_inside;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && bar_enabled && (req_addr == bar_base + (64'd1 << LOG2_SIZE) - 64'd1))
      |-> offset_in_range;
endproperty
a_p13: assert property (p13_last_byte_inside);
 
// P14 — the first byte past the aperture is outside.  P13 and P14 are a
// pair; either one alone is satisfied by a decoder that is off by one in
// the direction the other would catch.
property p14_first_byte_past_outside;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && (req_addr == bar_base + (64'd1 << LOG2_SIZE)))
      |-> !offset_in_range;
endproperty
a_p14: assert property (p14_first_byte_past_outside);
 
// P15 — the byte immediately below the base is outside.
property p15_byte_below_outside;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && (req_addr == bar_base - 64'd1)) |-> !offset_in_range;
endproperty
a_p15: assert property (p15_byte_below_outside);
 
// P16 — the offset never equals or exceeds the size when accepted.  The
// direct statement of boundary 4, kept separate from P13/P14 because
// those are edge cases and this is the invariant.
property p16_offset_bounded;
  @(posedge clk) disable iff (!rst_n)
    offset_in_range |-> (bar_offset < (64'd1 << LOG2_SIZE));
endproperty
a_p16: assert property (p16_offset_bounded);

Target properties — boundary 5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P17 — a selected target index is within range.  The alternative is
// wrapping, which is Direction B (mutation 21).
property p17_target_in_range;
  @(posedge clk) disable iff (!rst_n)
    tgt_valid |-> (tgt_idx < NTARGET);
endproperty
a_p17: assert property (p17_target_in_range);
 
// P18 — a target is selected only for an in-range offset.
property p18_target_needs_range;
  @(posedge clk) disable iff (!rst_n)
    tgt_valid |-> offset_in_range;
endproperty
a_p18: assert property (p18_target_needs_range);
 
// P19 — an out-of-range target index is REPORTED, never silently dropped.
// 25.1 §6's rule: a condition that is detected and discarded is
// indistinguishable from one that was never detected.
property p19_target_reject_reported;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && offset_in_range && ((bar_offset >> (LOG2_SIZE - gw(NTARGET))) >= NTARGET))
      |-> tgt_reject;
endproperty
a_p19: assert property (p19_target_reject_reported);

Recorder properties — the instrument must itself be trustworthy.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P20 — the recorder captures the FIRST rejection, not the most recent.
// Once captured, the stored boundary and address are stable until clear.
property p20_first_is_sticky;
  @(posedge clk) disable iff (!rst_n)
    (captured && !clear) |=> (captured && $stable(first_bnd) && $stable(first_addr));
endproperty
a_p20: assert property (p20_first_is_sticky);
 
// P21 — the recorded boundary is the most upstream one that rejected.
// This is what makes the record a diagnosis rather than an observation.
property p21_records_most_upstream;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && !captured && rej_window) |=> (first_bnd == BND_WINDOW);
endproperty
a_p21: assert property (p21_records_most_upstream);
 
// P22 — the counter saturates rather than wrapping.  A wrapped counter
// reporting a small number is worse than no counter.
property p22_count_saturates;
  @(posedge clk) disable iff (!rst_n)
    (reject_count == 32'hFFFF_FFFF) |=> (reject_count == 32'hFFFF_FFFF);
endproperty
a_p22: assert property (p22_count_saturates);
 
// P23 — an accepted request never advances the reject count.
property p23_accept_no_count;
  @(posedge clk) disable iff (!rst_n)
    (req_valid && (this_bnd == BND_NONE)) |=> $stable(reject_count);
endproperty
a_p23: assert property (p23_accept_no_count);

Cover — the anti-vacuity set.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// P24 — every request produces exactly one outcome: an accepted access or
// a recorded rejection.  A request that produces neither has vanished
// inside the decoder, and no instrument downstream can tell that it did.
property p24_every_request_resolved;
  @(posedge clk) disable iff (!rst_n)
    req_valid |-> (sel_valid || win_reject || sel_none || sel_ambig || tgt_reject);
endproperty
a_p24: assert property (p24_every_request_resolved);
 
// P24's covers — the edge and alias cases must actually occur.  Without these
// covers, P11, P13, P14 and P15 are all vacuously true against a
// testbench that only ever probes the middle of the aperture, which is
// the default behaviour of every random address generator.
c1_last_byte:   cover property (@(posedge clk) disable iff (!rst_n)
                  req_valid && (req_addr == bar_base + (64'd1 << LOG2_SIZE) - 64'd1));
c2_first_past:  cover property (@(posedge clk) disable iff (!rst_n)
                  req_valid && (req_addr == bar_base + (64'd1 << LOG2_SIZE)));
c3_alias:       cover property (@(posedge clk) disable iff (!rst_n)
                  req_valid && (req_addr == (bar_base ^ (64'd1 << 32))));
c4_ambiguous:   cover property (@(posedge clk) disable iff (!rst_n) sel_ambig);
c5_win_reject:  cover property (@(posedge clk) disable iff (!rst_n) win_reject);
c6_tgt_reject:  cover property (@(posedge clk) disable iff (!rst_n) tgt_reject);

12. Measured Behaviour

Fault direction, 200,000 probes per row, against the golden decoder:

Injected faultRejected an address it ownsRejected at a different boundaryAccepted, wrong register
none (correct)000
high address bits dropped036,0980
limit compare inclusive020,0060
two BARs claim it64,34900
local offset truncated0087,414
parent window one page short41,33758,2800

Alias containment, 200,000 aliased probes, 1 MiB aperture at 0x0000_0004_8000_0000:

decoder variantacceptedrejectedaccepted with wrong data
64-bit compare, 64-bit offset0200,0000
low-32 compare, 64-bit offset0200,0000
low-32 compare, low-32 offset200,0000200,000

Three readings.

The healthy row is zero in every column. That is the baseline check every measurement in this chapter depends on, and the first model defect above was caught precisely because the healthy row was not distinguishable from the faulty ones.

Only one fault reaches Direction B on its own. Truncating the offset corrupts. Truncating the comparator does not. §5 stated it; this is the measurement.

And the parent window fault appears in two columns at once, because a window that is short by one page both rejects addresses the device owns and changes which boundary rejects others. It is the messiest signature in the table, and §15 case 1 is about recognising it.

13. Executable Counterexamples

Two minimal designs, each violating exactly one property, each with the failing address stated so the result can be reproduced by hand.

Counterexample A — the consistently truncated decoder (violates P11).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A 64-bit BAR whose comparator AND offset are both computed at 32 bits.
// This is variant C in §5.  It is the only single fault in §12 that
// produces a Successful Completion carrying the wrong register's data.
module ce_a_truncated_both #(parameter int unsigned LOG2_SIZE = 20)(
  input  logic [63:0] addr, input logic [63:0] base,
  output logic        hit,  output logic [31:0] offset, output logic in_range
);
  localparam logic [31:0] M = ~((32'd1 << LOG2_SIZE) - 32'd1);
  assign hit      = ((addr[31:0] & M) == (base[31:0] & M));
  assign offset   = addr[31:0] - base[31:0];          // <-- the fatal half
  assign in_range = hit && (offset < (32'd1 << LOG2_SIZE));
endmodule
 
// Failing stimulus:  base = 64'h0000_0004_8000_0000, LOG2_SIZE = 20
//                    addr = 64'h0000_0005_8000_0040   (base ^ (1<<32)) + 64
// Golden:  BND_MATCH  — the upper bits disagree, nothing claims it.
// This:    hit = 1, offset = 64, in_range = 1, target selected.
// P11 fails: (addr & mask) != (base & mask) yet offset_in_range is high.
// Observable consequence: Successful Completion, register at offset 64
// of an aperture the request never addressed.

Counterexample B — the ambiguity that picks (violates P9 and P7).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Two apertures overlap.  The decoder priority-encodes unconditionally,
// so it resolves the ambiguity by silently preferring the lower index.
module ce_b_picks_on_ambiguous #(parameter int unsigned NBAR = 4)(
  input  logic            req_valid,
  input  logic [NBAR-1:0] bar_hit,
  output logic            sel_valid,
  output logic [1:0]      sel_idx
);
  always_comb begin
    sel_valid = req_valid && (bar_hit != '0);          // <-- "any", not "one"
    sel_idx   = 2'd0;
    for (int i = NBAR-1; i >= 0; i--) if (bar_hit[i]) sel_idx = 2'(i);
  end
endmodule
 
// Failing stimulus:  bar_hit = 4'b0011  (apertures 0 and 1 both claim it)
// Golden:  BND_UNIQUE — the configuration is ambiguous and must be reported.
// This:    sel_valid = 1, sel_idx = 0.  Aperture 1's register is never
//          reached, and no error is raised at any layer.
// P7 fails ($onehot0 is false).  P9 fails (sel_valid high while ambiguous).
// This is the fault §12 measured as 64,349 wrongly-rejected accesses when
// reported correctly — and as SILENT misdirection when it is not.

Both counterexamples share a shape, and it is the one 23.6 §5 named: a decoder that cannot express "I don't know" will invent an answer. Counterexample A invents an owner for an address nobody owns; counterexample B invents a winner among two claimants. Neither reports anything.

14. Verification — Mutations

Thirty-two mutations. Every "Caught by" entry names properties defined in §11.

#MutationSymptomCaught by
1Base programmed misalignedcomparator claims a differently-shaped regionP1
2bar_hit not qualified by bar_enableda disabled BAR still claims addressesP3
3Size parameter not a power of twomask has holes; claim is non-contiguousP2
4Decoder size ≠ advertised sizehost maps more than the device decodes (§7)P4
5Advertised size derived from the wrong bit fieldsizing handshake reports a wrong apertureP4
6Comparator wired before the window checkboundary 1 bypassedP5
7Window limit treated as exclusivelast page of the window unreachableP5, P13
8Window limit one page short41,337 wrongly rejected (§12)P5
9Range check placed before the comparatoroffset computed from an unmatched baseP6
10offset_in_range asserted without bar_hitaccepts on range aloneP6
11Comparator truncated to 32 bits, offset full widthcontained; rejects aliases (§5 variant B)P11
12Comparator and offset truncated200,000 of 200,000 accepted wrong (§5 variant C)P10, P11
13Offset computed as base - addroffset wraps; range check passes near the baseP10, P16
14Ambiguous set priority-encodedsilently prefers the lowest apertureP7, P9
15sel_valid asserted on "any hit"ambiguity resolved by inventionP8, P9
16sel_none and sel_ambig both derivable as falsea request class with no outcomeP8
17Uniqueness checked per-BAR instead of per-seteach BAR passes; the set is brokenP7
18Limit comparison inclusive (<=)20,006 affected; all still rejected (§12)P14
19Last valid byte excludedfinal byte of every aperture unreachableP13
20Byte below base acceptedaperture effectively starts one byte earlyP15
21Target index wrapped instead of rejectedoffset selects an existing but wrong targetP17, P19
22Target selected without range checkout-of-aperture offset reaches a registerP18
23tgt_reject computed but not driven outdetected and discarded — invisibleP19
24Local offset truncated below aperture width87,414 accepted at the wrong register (§12)P16, P17
25Recorder overwrites on each new rejectionkeeps the last failure, loses the firstP20
26Recorder priority reversed (downstream first)reports BND_TARGET for a window failureP21
27Recorder captures address one cycle laterecords the following request's addressP20
28Reject counter wraps at 32'hFFFF_FFFFa wedged system reports a small countP22
29Counter advances on accepted requestsevery healthy device looks brokenP23
30clear implemented as a level, not a commandrecorder never latches while software pollsP20
31Probe engine omits the alias probeP11 and cover c3_alias never evaluateP24 (c3)
32Probe engine omits the first-byte-past probethe off-by-one of §6 is never exercisedP24 (c2)

Mutations 11 and 12 are the pair to study. They differ by one line — whether the offset is truncated — and they differ in outcome by everything. A mutation table that listed only "comparator truncated" would score both as the same defect, and §5's measurement is the argument that they are not.

Mutations 31 and 32 are different in kind from the rest. They do not break the design; they break the testbench, and their symptom is that assertions pass. They are in the table because 24.4 §5's rule applies here directly: a property that never evaluates is not a property that holds.

15. Debugging

Case 1 — every access to the device returns Unsupported Request; the BAR registers read back correctly.

The endpoint's registers are boundary 2 and they are fine. The request is not reaching them. Read the parent bridge's memory base and limit registers and confirm they enclose the assigned BAR range — 21.3 §5's containment property. §12 measured a window one page short producing 41,337 wrongly-rejected accesses out of 200,000, and the signature is exactly this one. This is the most common real fault in the chapter and the one checked last. Confidence: high when the BAR base is near a window edge.

Case 2 — accesses to the low part of the aperture work; the top page returns UR.

A window or aperture that is short by exactly one page. Probe base + size - 1 and base + size with bar_probe_engine (§10 Block 7, probes 1 and 2). If the last valid byte fails, the limit is being treated as exclusive somewhere (mutation 7 or 19). If the first byte past succeeds, the limit is inclusive by one (mutation 18) — and note from §12 that the latter is usually still contained downstream, so a failing last byte is the stronger signal.

Case 3 — reads return plausible but wrong values; no errors anywhere.

Direction B (§4). Stop looking at error logs; there will be none. Two candidates: the offset is truncated (mutation 24, measured at 87,414 wrong accesses) or the target index wraps (mutation 21). Discriminate by reading one register through two different offsets that alias under the suspected truncation — if both return the same value, the offset is losing bits. Confidence: high; this test has no false positives.

Case 4 — a 64-bit BAR above 4 GiB behaves oddly, but produces URs rather than corruption.

This is not a misdiagnosis. §5 measured it: a truncated comparator with a full-width offset rejects every alias, 200,000 of 200,000. You have variant B. It is a real defect worth fixing and it is not corrupting data. Confirm by probing base ^ (1 << 32) (probe 4) and checking that the rejection is recorded at BND_OFFSET rather than BND_MATCH — that boundary code is the fingerprint, and it is why bar_first_reject records which boundary rather than merely that one rejected.

Case 5 — the device works alone and fails when a second function or second BAR is enabled.

Boundary 3. Two apertures overlap. Neither BAR is individually wrong, which is why every per-BAR check passes. Read all apertures and compute the intersection by hand; then check whether the decoder reports ambiguity at all. If it priority-encodes (counterexample B), the lower-indexed aperture wins silently and the higher one appears dead. §12 measured 64,349 affected accesses.

Case 6 — the driver reports the right aperture size and accesses above some smaller boundary fail.

The sizing handshake and the decoder disagree — §7's fourth non-proof, mutation 4. The readback proved the register's writability, not the comparator's implementation. Sweep the aperture with bar_probe_engine and find the highest address that succeeds; the transition point is the decoder's real size. Confidence: high, and the fix is in the design's parameterisation rather than in configuration.

Case 7 — the first-rejection recorder reports BND_TARGET and you do not believe it.

Check the recorder before the design. Mutation 26 reverses the priority so that the most downstream boundary is reported, which turns a window failure into a target failure. The discriminator is a request that should fail at boundary 1 only: probe an address far outside the window (probe 6) and confirm the recorder reports BND_WINDOW. An instrument that has never been validated against a known fault is not evidence — this is 25.1 §5's rule applied to your own debug hardware.

Case 8 — accesses fail only after the device has been running for a while.

The BAR itself is almost certainly not involved. A decoder is combinational and does not degrade. Look for a base register being rewritten while the aperture is live (a reset sequence, a re-enumeration, a hot-reset path), or a window being reprogrammed by power management. Correlate the first rejection's timestamp with configuration writes rather than with traffic. Confidence: moderate; this case's value is in ruling the decoder out quickly.

Case 9 — the reject counter reads a small number on a system that has been failing for hours.

Suspect the counter before the conclusion. Mutation 28 wraps at full scale; a wrapped counter reporting 47 is worse than no counter, because it invites the inference that the fault is rare. Read the counter twice, separated by a known interval, and check that the delta is consistent with the observed failure rate. bar_first_reject saturates for this reason (P22), and the saturated value is itself the diagnosis: at least 4,294,967,295 rejections is a different statement from "47".

16. Misconceptions

"The BAR registers read back correctly, so the BAR is fine." They read back the register. The comparator is separate logic (§7, mutation 4), and the parent window is separate silicon (case 1). A correct readback rules out one of five boundaries.

"A 64-bit BAR bug corrupts memory." Only if the offset is truncated too (§5). Measured: the comparator-only variant rejected 200,000 of 200,000 aliases. Two different bugs, two different severities.

"If it were a BAR problem I'd see an error." Only in Direction A. §12 measured 87,414 accesses that returned Successful Completions carrying the wrong register (§4). The absence of errors is not evidence of correct decoding.

"Overlapping BARs would obviously fail." They fail silently if the decoder priority-encodes (counterexample B). Each BAR is individually correct; the set is not, and nothing checks the set unless you write P7.

"The off-by-one in the limit comparison is harmless — we've shipped it for years." It has been harmless because a downstream range check kept catching it (§12: 20,006 affected, all still rejected). It becomes a data-corruption bug the day someone removes the range check as redundant.

"Start by dumping the endpoint's configuration space." It is the easiest thing to read and boundary 2 of 5. §15 orders the cases outside-in for exactly this reason, and case 1 is the fault that hides from that dump entirely.

"Zero rejections means the decoder is correct." It means the decoder rejected nothing. Direction B produces zero rejections by construction, and mutations 31 and 32 produce zero because the interesting addresses were never probed (§14).

17. Understanding Check

Q1. A device returns Unsupported Request for every access, and its BAR registers read back exactly what the OS programmed. Where do you look, and why not at the BAR?

At the parent bridge's window (§15 case 1, §3 boundary 1). The BAR registers are boundary 2 and the readback confirms only that the register holds the right value — the request is being discarded upstream of it, so the endpoint never sees the address at all. §12 measured a window one page short producing 41,337 wrongly rejected accesses. The containment property in 21.3 §5 is the thing to verify: the parent window must enclose the assigned range, and that is invisible from the endpoint's own configuration space (§7).

Q2. Your 64-bit BAR sits above 4 GiB and the comparator ignores addr[63:32]. Is data being corrupted?

Not by that alone (§5). Measured over 200,000 aliased probes, the low-32 comparator with a full-width offset rejected every one: the comparator matched, addr - base landed far outside [0, size), and the range check caught it. Corruption requires the offset to be truncated to the same width — that variant accepted 200,000 of 200,000 and delivered an offset valid for the wrong address. The rule is that the comparator and the offset must be computed at the same width with a range check after both, and P10 and P11 assert the two halves separately.

Q3. Two BARs each pass every self-check and the device misbehaves only when both are enabled. What is the property nobody wrote?

Uniqueness — a property of the set, not of any member (§3 boundary 3, P7). Two individually-correct apertures can overlap, and a decoder that priority-encodes resolves the overlap by silently preferring one (counterexample B). No per-BAR assertion can catch it, because no per-BAR assertion can see the other BAR. $onehot0(bar_hit) is the whole fix, and §12 measured 64,349 affected accesses.

Q4. Reads return plausible values that are simply wrong, and no log anywhere shows an error. What class of fault is this and what does that rule out?

Direction B — an address granted to the wrong target (§4). It rules out every error-driven instrument: no UR, no status bit, no log entry, and 25.9 will show a byte-perfect trace. The only detection is comparing returned values against expected ones. §12's two Direction B faults were a truncated local offset (87,414 wrong accesses) and a wrapped target index — both of which return Successful Completion with correct length and Tag.

Q5. A design has shipped for years with addr <= base + size as its hit comparison. Why has nothing broken, and when will it?

Because a downstream range check has been containing it (§6, §12: 20,006 affected probes, all still rejected). The inclusive comparison lets exactly one byte past the comparator; off == size then fails off < size and the request is rejected one boundary later. It breaks the day someone removes the range check as redundant — and it is redundant, which is precisely why it gets removed. This is the strongest argument in the chapter for layering the checks rather than collapsing them.

Q6. The sizing handshake reports 1 MiB. What have you proved?

That the BAR register's low 20 bits are hardwired to zero (§7). Nothing more. It does not prove the BAR is enabled, that a base has been assigned, that the parent window contains the assigned base, or that the decoder implements 1 MiB — the handshake talks to a register and the comparator is separate logic (mutation 4, §15 case 6). A device can advertise 1 MiB and decode 64 KiB while every host-side check passes.

Q7. Your first-rejection recorder says BND_TARGET. What do you check before believing it?

The recorder (§15 case 7, mutation 26). Reversed priority reports the most downstream boundary instead of the most upstream, turning a window failure into a target failure. Validate it by probing an address far outside the window and confirming it reports BND_WINDOW. An instrument never validated against a known fault is not evidence — and P21 exists to state the priority obligation formally, because "which boundary rejected first" is the entire diagnostic value of the instrument.

18. Module 25 So Far

Five chapters, and this one changes the question.

ChapterAsks
25.1 Debugging Overviewwhich layer, and what is the last provable event?
25.2 Enumeration Failureswhere did the configuration conversation stop?
25.3 Link Training Failureswhich state is it in, and which exit is missing?
25.4 LTSSM Issueswhich contract inside the state is wrong?
25.5 (this)which agent disagrees about who owns this address?

The first four chapters debug a link that is not working. This one debugs a link that is working perfectly and delivering requests to the wrong place — which is why it is the first chapter in the module where the absence of errors is not evidence of health.

That shift brings a new obligation with it. 25.3 and 25.4 could rely on a fault announcing itself: a state that will not exit, a counter that will not advance. §4's Direction B announces nothing. The instrument has to be built before the bug appears, because after it appears there is no trace of it anywhere — and that is the argument for bar_first_reject existing in silicon rather than being added when someone files a bug.

And one result generalises past BARs. §5 measured a truncated comparator being contained by a full-width range check downstream. Layered checks at different widths do not merely duplicate each other; they convert silent corruption into visible rejection. The same shape appears in the next four chapters — a descriptor length checked against a buffer bound, a Completion checked against its request's identity, a credit checked against a reservation — and each time, the layer that looks redundant is the one that turns a Direction B fault into a Direction A one.

25.6 takes the next stage. A BAR gets a request to the right register; DMA is what the device does when it originates traffic of its own — and the ownership question moves from who owns this address to who owns this descriptor.