Skip to content

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

PostureConsequence
assume alignmenta misprogrammed base makes the decoder wrong in 33.3% of probes, silently
check alignment and reporta 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

An endpoint inbound address decoder. An inbound address enters an aperture table holding the base and mask for each implemented BAR, with 64-bit pairs combined into single entries. Parallel mask comparators produce a match vector. An enable gate applies the memory space enable and implemented conditions. A resolver produces one of three outcomes: a one-hot target select, no match, or an ambiguity error. The selected target drives offset extraction, which delivers a target identifier and a local offset to the application resource.inbound addressaperture tablemask comparatorsenable gateresolveroffset extracttarget + offsetnot claimed12
Figure 1 — the inbound decode block. One address is compared against every implemented aperture at once using a mask compare, with 64-bit pairs treated as single apertures. The match vector feeds a resolver that produces exactly one target, no match, or an ambiguity error, and a separate gate applies the enable and implemented conditions before anything is claimed. The selected aperture also drives the offset extraction that the application resource receives.

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 cycles
Ten cycles of an endpoint address decoder. A request at cycle 0 hits BAR zero. A request at cycle 1 hits BAR one. A request at cycle 2 matches no aperture and is not claimed. At cycle 3 the memory space enable is low, so an in-aperture request is not claimed. At cycle 5 a request hits BAR zero again once the space is enabled. At cycle 6 two apertures match and ambiguity is reported. At cycle 7 a 4 GiB aliased address is correctly not claimed. At cycle 8 a request hits BAR zero.no aperture matches — not claimedno aperture matches — notclaimedin range but space disabledin range but space disabledtwo match — reported, not resolvedtwo match — reported, notresolved4 GiB alias — correctly refused4 GiB alias — correctlyrefusedclkreq_validmem_enhit_bar0hit_bar1ambiguousnot_claimt0t1t2t3t4t5t6t7t8t9
Figure 2 — ten cycles of inbound decode. Two requests hit different apertures, one falls outside every aperture and is not claimed, one falls inside an aperture while the memory space is disabled and is also not claimed, one matches two apertures and is reported as ambiguous, and one is a 4 GiB alias of a 64-bit aperture that a full 64-bit compare correctly refuses.

Four 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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;
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import 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
endmodule

Classification: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ==================================================================
// 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});
endproperty

Twenty-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.

#MutationSymptomCaught by
1Range compare with an unchecked base33.3% wrong when the base is misaligned (§14)P2, P6
2Silently align a misprogrammed basedecodes an aperture software did not program (§4)P6
3Compare only the lower 32 bits of a 64-bit BARclaims an address 4 GiB away in 50.1% of probesP7, P8
4Treat a 64-bit pair as two independent BARsphantom aperture at the high half's valueP4
5Include a SLOT_MEM64_HI slot as a decode candidateaddress bits interpreted as a baseP4
6Priority-encode the match vector24.9% resolved silently; one aperture unreachableP10
7Drop the ambiguous output entirelyan overlap looks like a clean hitP9, P10
8Compute ambiguity from claim_vec instead of match_vecoverlaps invisible while disabled — i.e. during enumerationP11
9Report a hit for a slot that did not claimthe wrong resource is selectedP12
10Decode before checking space_enabledclaims 49.9% of in-aperture probes it must not (§14)P14
11Collapse disabled_hit and no_match into one outputfour causes, one symptom; §16 becomes impossibleP15
12Let an unimplemented slot matcha BAR that was never programmed claims addressesP3
13Use ~(size-1) with size = 0an all-zero mask matches every addressP5
14Search apertures sequentially and stop at the first hitstructurally cannot detect the second matchP10
15Emit addr instead of the offsetthe resource re-decodes; breaks when the BAR moves (§8)P16
16Compute the offset with a stale selone packet's target with another's offsetP17
17Let target and offset change under stallthe resource sees a different access than was decodedP18
18Read the BAR registers combinationally during decodea mid-flight write changes a decision already madeP19, P20
19Apply a mid-flight configuration change silentlyno evidence of a race that produced a wrong claimP20
20Claim an address outside every aperture "to be safe"corrupts another device's transaction (§9)P13
21Let the decoder backpressure the inbound pathdecode latency becomes link backpressure (23.1 §4)P22
22Assume N_SLOT is at least 2$clog2(1) = 0; a zero-width selectdesign review
23Use the DUT's own mask function in the DV oraclea shared misconception makes both agreedesign review
24Publish the decode tables by handthe 4 GiB alias and the misalignment cases are invisibledesign 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.