Skip to content

When an APB write lands in the wrong register or a read returns another peripheral's value, the bug is almost never in the slave — it is in the address decoder, and the mistakes cluster into a small, recognisable set. You have learned how the address decoder turns PADDR into a one-hot PSEL, how address allocation carves the map into per-slave windows, and how a register bank decodes the low offset bits inside a slave. This chapter inverts that knowledge into a debugging instrument: a catalog of the classic address-decode bugs — the ways a decoder selects the wrong slave, two slaves, or no slave at all — each as a named bug with a symptom, the wrong RTL that produces it, the map signature that fingerprints it, and the correct base/mask RTL that fixes it. The single idea to carry: when an access "writes the wrong register" or "reads someone else's data," do not start from the slave — pattern-match the decode, because the bug is almost certainly one of these.

1. Problem statement

The problem is that the address decoder is the single block that decides which register an APB access touches, and a handful of recurring RTL mistakes break that decision — so a perfectly correct slave faithfully services the wrong access.

A decoder makes exactly one promise per access: "for this PADDR, exactly one slave — and inside it, exactly one register — is selected." Every classic decode bug breaks that promise in one of three ways, and each has a distinct map signature:

  • Two matches (overlap). Two windows include the same address, so two PSELs assert at once. Two slaves drive the shared PRDATA (a bus fight) and a write commits to both. The map signature is windows that touch or cross.
  • Zero matches (hole / gap). A legal address falls between windows, so no PSEL asserts. The read-data mux selects nothing, PRDATA floats, and a write is silently dropped — or the access hangs waiting for a PREADY no slave will drive. The map signature is a gap in the address map.
  • Right count, wrong target (aliasing / off-by-one / stale / byte-vs-word). Exactly one slave selects, but the wrong one, or the wrong register inside it — because too few PADDR bits were compared (the window mirrors across the map), a boundary used <= where < belongs, a registered/stale PADDR was decoded, or the byte PADDR LSBs were mixed up with a word index. The map signature is a register that answers at two addresses, or a boundary that is one off.

The engineering problem of this chapter is therefore not "how does decode work" — you know that from chapter 5.1 — but "what are the specific ways real decoder RTL gets the select wrong, what does each look like on the map and the bus, and what is the correct base/mask fix for each." That is a pattern library, and the fastest debuggers carry it in their heads.

2. Why previous knowledge is insufficient

This module built the model of decoding from the map outward, and each prior chapter is now a reference this catalog points back to — but none of them is the catalog itself.

  • Address decoder taught how a clean decoder turns PADDR into one-hot PSEL. That chapter is the deep treatment of the correct structure; here it is the baseline the bugs deviate from, not re-taught. Knowing the right shape does not give you the taxonomy of every way real RTL drifts off it.
  • Address-allocation strategy taught how to carve the map into power-of-two-aligned windows so a base/mask compare is even possible. That is the prevention — a good map makes overlap and aliasing hard to write. This chapter is what happens when the map or the compare is wrong anyway, and how to recognise it after the fact.
  • PADDR address bus and PSEL select signal taught the signals the decoder consumes and produces — byte-addressed PADDR, one PSEL per subordinate. They explained the semantics of those signals; they did not enumerate the ways a decoder misuses them (comparing too few bits, ignoring the LSB convention, letting PSEL float outside its window).

The gap is this: prior chapters taught the correct decode and the map discipline that enables it. They did not assemble the failure taxonomy — the named set of decode bugs with wrong-versus-right RTL and a map signature each — that lets you debug a real misrouted access by recognition instead of derivation. Building that taxonomy is this chapter; it is the address-side sibling of the data-side incorrect-PRDATA bugs, and the two together cover "the access touched the wrong thing" end to end.

3. Mental model

The model: a decoder is a set of windows over the address line, and every bug is a window that is the wrong size, the wrong place, or read with the wrong bits — producing two selects, zero selects, or one wrong select. Sort the symptom into count first, then target.

The three bins are the three ways the one-hot promise fails:

  • Two selects (overlap). Windows touch or cross. On the bus you see a corrupted PRDATA (two slaves wired together) and a write that hits two registers. The cause is always windows that are not disjoint — an inclusive <= boundary, a base that is not size-aligned, or hand-coded ranges that drifted into each other.
  • Zero selects (hole). A legal address falls in a gap. PRDATA floats (X or a stale mux default) and a write vanishes, or the access hangs for a PREADY no slave drives. The cause is always a map with a gap, or a compare that excludes a legal address — a missing window, an off-by-one that opens a one-address hole, or a default branch that drives nothing.
  • One wrong select (mis-target). Exactly one slave or register selects, but the wrong one. The cause is always the decoder reading the wrong bits — too few high bits compared (the window aliases and mirrors), the wrong LSBs (byte PADDR treated as a word index, or vice-versa), or a registered, stale PADDR decoded a cycle late so the select belongs to the previous access.

The discipline this model buys you: count before you target. "PRDATA is corrupted / a write hit two registers" is overlap; "the read floats or the write disappeared" is a hole; "the right number of slaves answered but the wrong one" is mis-target. Three refinements sharpen it:

  • Overlap and hole are map bugs; mis-target is a compare bug. You debug the first two by drawing the map and looking for crossings or gaps; you debug the third by checking which bits the compare actually uses — ((paddr & ~MASK) == BASE) with the right MASK width is the whole game.
  • Aliasing is a hole's evil twin. A window that compares too few bits does not leave a gap — it fills the gap with a mirror of itself, so a legal-but-unallocated address silently hits a real register. It looks like "a register that answers at two addresses," and a memory-walk test is what exposes it.
  • The stale-PADDR bug is a timing mis-target. Decoding a registered PADDR (or one that changed the same cycle PSEL rose) selects for the address of the previous beat. It is intermittent and back-to-back-dependent — the select is right in isolation and wrong only when two accesses abut.
A structural diagram of APB address decoding. On the left, four address windows labelled UART, TIMER, GPIO, and CRYPTO each show a BASE and SIZE. In the centre a base/mask decoder computes one comparison per slave and produces a one-hot PSEL that fans out on the right to the four subordinates. Two callout boxes mark the overlap hazard (two windows cover one address, two selects assert, $onehot0 fails) and the hole hazard (a legal address falls in a gap, no select asserts, PRDATA floats).
Figure 1 — the APB address map feeding a base/mask decoder that produces a one-hot PSEL, with the two structural hazards annotated. On the left, four per-subordinate windows (UART at BASE 0x000, TIMER at 0x100, GPIO at 0x200, CRYPTO at 0x300, each SIZE 0x100) define the map. In the centre, the decoder computes one comparison per slave — ((paddr & ~MASK_N) == BASE_N), MASK_N = SIZE_N − 1 — gated by the PSEL window, and its output must satisfy $onehot0 every cycle. On the right, the resulting one-hot PSEL fans out to the four subordinates. The two red/amber callouts are the hazards every decoder must avoid: an OVERLAP (two windows cover the same address, so two selects assert, $onehot0 fails, and two slaves fight on PRDATA) and a HOLE (a legal address falls in a gap, so no select asserts and PRDATA floats or the access hangs). The figure is the chapter's spine: a clean decoder is disjoint, size-aligned BASE+SIZE windows, one base/mask compare per slave, a one-hot PSEL, and a default slave to catch holes.

4. Real SoC implementation

In real RTL the top decode bugs are a line or two of wrong code, and the fixes are equally short — which is exactly why they ship: each looks reasonable in isolation. Here are the wrong-versus-right pairs for the two highest-frequency decode bugs, overlapping ranges (from an off-by-one boundary) and aliasing from too-few bits compared, both fixed by the same clean base/mask, one-hot pattern.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ============================================================
// BUG 1 — OVERLAPPING RANGES: an off-by-one boundary (<= vs <)
// ============================================================
// WRONG: hand-coded inequality ranges with an inclusive upper bound. The
// TIMER window runs 0x100..0x200 INCLUSIVE, the GPIO window starts at 0x200,
// so paddr == 0x200 matches BOTH -> two selects -> bus fight on PRDATA.
assign psel_s1_bug = psel & (paddr >= 32'h100) & (paddr <= 32'h200); // TIMER (one past!)
assign psel_s2_bug = psel & (paddr >= 32'h200) & (paddr <= 32'h2FF); // GPIO
// at paddr=0x200: psel_s1_bug=1 AND psel_s2_bug=1 -> $onehot0 FAILS,
// both slaves drive prdata, and a write commits to TWO registers.
 
// ============================================================
// BUG 2 — ALIASING: too few PADDR bits compared (window mirrors)
// ============================================================
// WRONG: only the low 8 bits are decoded, so the slave answers at EVERY
// 0x100 stride across the map. GPIO at 0x200 ALSO matches 0x300, 0x1200, ...
// A legal CRYPTO address (0x300) silently hits the GPIO register bank.
assign psel_s2_alias = psel & (paddr[7:0] == 8'h00);  // ignores paddr[31:8]!
// reg index inside the slave, also under-masked -> mirrors within the slave too:
assign reg_idx_bug   = paddr[3:0];                     // word-vs-byte: drops paddr[1:0]?
 
// ============================================================
// CORRECT — clean base/mask decode + one-hot select for ALL slaves
// ============================================================
// Compare the FULL high address against a size-aligned BASE with MASK=SIZE-1.
// Disjoint, power-of-two windows -> exactly one psel_sN, no overlap, no gap.
localparam logic [31:0] BASE_S0 = 32'h0000_0000, MASK_S0 = 32'h0000_00FF; // UART
localparam logic [31:0] BASE_S1 = 32'h0000_0100, MASK_S1 = 32'h0000_00FF; // TIMER
localparam logic [31:0] BASE_S2 = 32'h0000_0200, MASK_S2 = 32'h0000_00FF; // GPIO
localparam logic [31:0] BASE_S3 = 32'h0000_0300, MASK_S3 = 32'h0000_00FF; // CRYPTO
 
assign psel_s0 = psel & ((paddr & ~MASK_S0) == BASE_S0); // full high bits compared
assign psel_s1 = psel & ((paddr & ~MASK_S1) == BASE_S1);
assign psel_s2 = psel & ((paddr & ~MASK_S2) == BASE_S2);
assign psel_s3 = psel & ((paddr & ~MASK_S3) == BASE_S3);
 
// register offset INSIDE the slave: APB is BYTE-addressed, registers are word
// (4-byte) aligned, so index with paddr[N:2] -- NEVER paddr[N:0].
assign reg_idx = paddr[OFFSET_MSB:2];   // word index, drops the 2 byte LSBs
 
// default slave: any address that matches NO window selects a benign sink,
// so a HOLE returns a defined error response instead of floating PRDATA.
assign psel_default = psel & ~(psel_s0 | psel_s1 | psel_s2 | psel_s3);

Two facts drive these fixes. First, an overlap or hole fix is structural: make the windows disjoint and gap-free with size-aligned BASE and MASK = SIZE − 1. The off-by-one bug is not a typo on PSEL — it is hand-coded >=/<= ranges that drifted; replacing them with ((paddr & ~MASK) == BASE) over power-of-two-aligned windows makes overlap and gaps structurally impossible, because each window owns exactly its SIZE block and nothing else. Second, a mis-target fix is about comparing the right bits. Aliasing is too few high bits compared — the fix is to compare the full high address (paddr & ~MASK), so the window can match only its own block and never mirrors. The byte-vs-word bug is the wrong LSBs — APB PADDR is byte-addressed (5.x), so a word-register index must be paddr[N:2], dropping the two byte LSBs; indexing with paddr[N:0] makes every register answer at four consecutive byte addresses and skips three out of four. The default-slave branch turns a hole from a silent float into a defined error — the illegal-address response path.

5. Engineering tradeoffs

The catalog itself is the deliverable: one row per bug, the map signature you will actually see, the root cause in the RTL, and the fix. Memorise the shape of each row — count-binned, with a structural or bit-selection fix — and you can debug most APB mis-routing failures by recognition.

Bug nameMap / bus signatureRoot causeFix
Overlapping rangesTwo windows touch/cross; PRDATA corrupted, write hits two registers ($onehot0 fails)Hand-coded >=/<= ranges with an inclusive boundary, or a BASE not size-alignedDisjoint, power-of-two windows; ((paddr & ~MASK) == BASE) per slave; assert one-hot
Gap / holeA legal address selects nothing; PRDATA floats (X) or write dropped; or access hangsA window missing, or an off-by-one that opens a one-address gap; no default slaveMake windows abut with no gap; add a default slave that returns a defined error for unmapped addresses
Aliasing (too few bits)A register answers at two addresses; a higher unmapped address hits a real slaveDecoder compares only the low bits (paddr[7:0]), ignoring the high bits, so the window mirrors across the mapCompare the full high address (paddr & ~MASK == BASE); never decode a partial slice
Off-by-one boundaryThe first or last address of a window is misrouted (<= vs <, SIZE vs SIZE−1)Inclusive/exclusive boundary error, or MASK computed as SIZE not SIZE−1Use MASK = SIZE − 1 on a size-aligned base; the mask is the boundary, no inequality needed
Byte-vs-word PADDR LSBsEvery register answers at 4 consecutive byte addresses, or 3 of 4 are skippedRegister index built from paddr[N:0] (byte) when registers are word-aligned, or vice-versaIndex word registers with paddr[N:2]; reserve paddr[1:0] for byte lanes (PADDR)
Stale / registered PADDRIntermittent wrong-register only on back-to-back accesses; correct in isolationDecoder uses a registered PADDR (or one that changed as PSEL rose), selecting for the previous beatDecode the combinational, current PADDR sampled in the SETUP phase, stable through ACCESS
Decode floats outside PSELA slave's internal select toggles during idle; spurious writes between real accessesPer-slave select derived from PADDR alone, not gated by PSEL/PENABLE, so it "decodes" garbageGate every select on PSEL (and writes on PSEL && PENABLE); decode only inside the access window

The throughline: every row sorts into two-selects, zero-selects, or one-wrong-select, and every fix is either "make the windows disjoint and gap-free" or "compare the right bits at the right time." Two of the bugs — aliasing and stale-PADDR — add the non-local dimension: aliasing makes a bug appear at an address far from the buggy window, and stale-PADDR makes it appear only when two accesses abut, which is why a single directed read clears neither. The byte-vs-word bug adds the convention dimension: it is invisible if every test happens to use word-aligned addresses, and only a byte-stride walk exposes it.

6. Common RTL mistakes

7. Debugging scenario

Pick the aliasing bug, because it is the most insidious of the catalog: it passes every directed test that stays inside a window, ships, and then surfaces when firmware (or a memory test) touches an address the decoder should have left unmapped but secretly mirrors onto a real register.

  • Observed symptom: a production memory-walk diagnostic — firmware writing a unique value to every word in the peripheral region and reading it back — reports that two different addresses hold the same value, and that writing one corrupts the other. One of the addresses is a real GPIO register at 0x200; the other is 0x300, which the map says belongs to CRYPTO. CRYPTO reads behave erratically because GPIO is answering for it. Directed register tests, which only ever touched 0x200, all passed.
  • Map / bus clue: capturing PSEL per slave while sweeping PADDR shows psel_s2 (GPIO) asserting at 0x200 and at 0x300, 0x1200, 0x2200 — every 0x100 stride. The GPIO window is mirrored across the entire map. At 0x300 both the GPIO mirror and the (correct) CRYPTO window assert: a $onehot0 check on the select vector fails at exactly those mirror addresses.
  • Root cause: the GPIO decode compared only the low byte — psel_s2 = psel & (paddr[7:0] == 8'h00) — instead of the full high address. Because paddr[31:8] was never compared, the window matched its base offset modulo 0x100, mirroring at every stride. The map looked gap-free in the spec, but the decoder filled the unallocated space with copies of GPIO instead of leaving it to a default slave.
  • Correct RTL: compare the full high address against a size-aligned base so the window can match only its own block:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    localparam logic [31:0] BASE_S2 = 32'h0000_0200, MASK_S2 = 32'h0000_00FF;
    assign psel_s2 = psel & ((paddr & ~MASK_S2) == BASE_S2);  // full high bits compared
    // every other slave likewise; an unmatched address goes to the default slave.
  • Verification assertion: prove the select vector is one-hot for every selected access, which fails the instant a mirror or an overlap asserts a second select:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    // exactly one slave selected whenever the bus is selecting a subordinate
    assert property (@(posedge pclk) disable iff (!presetn)
      psel |-> $onehot({psel_s3, psel_s2, psel_s1, psel_s0})
    );
  • Debug habit: when two addresses behave as the same register, do not chase the slave — sweep PADDR across the whole region and watch each PSEL. A memory-walk that writes a unique value per word and reads it back is the cheapest aliasing detector ever built: if any two distinct addresses read back each other's data, a window is mirroring because the decoder compares too few bits. Then check the decode for a partial paddr[k:0] slice instead of a full (paddr & ~MASK) == BASE compare.
A two-panel contrast on a shared address axis from 0x000 to 0x300. The top red bug panel shows a TIMER window 0x100 to 0x200 inclusive overlapping a GPIO window 0x200 to 0x2FF, with address 0x200 matching both so psel_s1 and psel_s2 assert together, $onehot0 fails, and two slaves drive PRDATA. The bottom green correct panel shows disjoint base/mask windows where TIMER owns 0x100 to 0x1FF and GPIO owns 0x200 to 0x2FF, so 0x200 matches GPIO only and exactly one select asserts.
Figure 2 — two overlapping windows producing two selects, contrasted with disjoint base/mask windows producing a clean one-hot select, on the same address axis. Top (bug, red): an off-by-one boundary makes the TIMER window run 0x100..0x200 inclusive while GPIO runs 0x200..0x2FF, so paddr = 0x200 falls inside both; the decoder asserts psel_s1 and psel_s2 together, $onehot0 fails, and both the TIMER and GPIO subordinates drive PRDATA — a wired bus fight that also writes the wrong register. The panel notes the same signature appears with aliasing, where too-few PADDR bits compared mirror a window across the map. Bottom (correct, green): each window is a power-of-two-aligned BASE plus SIZE compared with ((paddr & ~0xFF) == BASE), so TIMER owns 0x100..0x1FF and GPIO owns 0x200..0x2FF with no overlap and no gap; paddr = 0x200 matches GPIO only and exactly one select asserts. The figure teaches that overlapping or off-by-one ranges produce multiple selects and a bus fight, while disjoint base/mask windows guarantee a one-hot select.

8. Verification perspective

Because the catalog splits cleanly into count failures (overlap, hole) and target failures (aliasing, off-by-one, byte-vs-word, stale, float), the verification plan needs one structural property over the select vector plus a directed sweep that visits the boundaries — a random burst of interior addresses will never expose a one-address gap or a mirror.

  • One-hot over the select vector catches overlap and hole in one stroke. A single property — whenever the bus selects a subordinate, exactly one per-slave select asserts — catches both families at once: assert property (@(posedge pclk) disable iff(!presetn) psel |-> $onehot({psel_s3,psel_s2,psel_s1,psel_s0}));. Overlap makes it two; a gap makes it zero (use $onehot0 if an unmapped-but-legal address should select nothing and fall to the default slave). This is the address-side analogue of the data-side checks on incorrect-PRDATA.
  • A boundary + alias sweep catches off-by-one and aliasing — random interior reads do not. Directed-walk every window's first and last address and the first address past it (BASE, BASE+SIZE−1, BASE+SIZE) to catch off-by-one and gaps; then memory-walk the whole region — write a unique value per word, read back — to catch aliasing, because a mirrored window only reveals itself when two distinct addresses read back each other's data. Cross-check the per-slave PSEL against an expected-decode reference model (the scoreboard's map) so a wrong-but-one-hot select is flagged, not just a non-one-hot one.
  • Gate-and-stability checks catch float, stale, and byte-vs-word. Assert that no per-slave select asserts while PSEL is low (!psel |-> !(|{psel_s3..s0})) to catch the decode-floats bug; assert PADDR is stable from SETUP through ACCESS ($rose(psel) ##0 paddr |=> $stable(paddr) throughout penable) so a stale/changing PADDR is caught and the decoder is known to see one address per access; and add a coverage cross of op × reg_index over the byte-stride address space so a word-vs-byte indexing error (registers that answer at the wrong stride) shows as uncovered or duplicated cells. The combinational decode and stability checks run in RTL; like every decode property they need the boundary addresses driven, which only a directed sweep guarantees.

The point: each bug family has a named check that catches its whole class — $onehot for count, an expected-decode compare for target, a PSEL-gate for float, a PADDR-stability for stale — and each needs the boundary and the whole map visited, so an address-decode plan is "one one-hot property, one expected-decode scoreboard, one gate check, one stability check, and a boundary + memory-walk sweep," not a pile of random interior reads.

9. Interview discussion

"Walk me through the address-decode bugs you have seen" is a senior screening question because a weak answer names one ("ranges can overlap") and stops, while a strong answer reveals an organised mental catalog and the fixes — exactly the recognition speed that separates someone who has debugged an SoC bus from someone who has only read the spec.

Frame it as one promise, three failure counts: a decoder promises one slave (and one register) per address, and every bug makes it two (overlap), zero (hole), or one wrong (mis-target). Then enumerate crisply: in the count family, overlapping ranges (an inclusive <= boundary or a non-size-aligned base — two selects, a PRDATA bus fight) and gaps/holes (a missing window — PRDATA floats or the access hangs, fixed with a default slave); in the mis-target family, aliasing (too few PADDR bits compared, so a window mirrors across the map — the non-local one that catches people out, exposed by a memory-walk), off-by-one boundaries (<= vs <, MASK = SIZE vs SIZE − 1), byte-vs-word LSBs (indexing a word register with paddr[N:0] instead of paddr[N:2]), stale registered PADDR (decoding a cycle late, a back-to-back-only bug), and decode floating outside PSEL (a select not gated by PSEL, firing spurious writes in idle). Land the depth points: the fix for a count bug is structural (disjoint, size-aligned BASE+MASK windows make overlap and gaps impossible), the fix for a target bug is comparing the right bits at the right time (full high address for aliasing, paddr[N:2] for byte-vs-word, current combinational PADDR for stale), and the verification is one $onehot property over the select vector plus a boundary-and-memory-walk sweep, because random interior reads never visit the edges where these bugs live. Closing with "and the aliasing bug taught me to sweep the whole map and watch every PSEL, not just test inside the windows" signals real silicon debugging, not spec reading.

10. Practice

  1. Bin the symptom. Given three field reports — "writing 0x200 also changes the register at 0x300," "a read of 0x1F4 returns X and the write vanished," and "PRDATA is garbage whenever firmware touches 0x200" — assign each to the two-selects, zero-selects, or one-wrong-select family and name the specific bug.
  2. Fix the boundary. Given psel_s1 = psel & (paddr >= 32'h100) & (paddr <= 32'h200) and psel_s2 = psel & (paddr >= 32'h200) & (paddr <= 32'h2FF), explain why 0x200 double-selects and rewrite both as size-aligned base/mask compares.
  3. Catch the mirror. Write the SVA property that proves the per-slave select vector is one-hot, and state which decode bug it catches at a boundary address and which one it catches at an aliased address far from any window.
  4. Reason about LSBs. A 16-entry word-aligned register file indexes with reg_idx = paddr[5:0]. Explain what addresses each register answers at, which registers are unreachable, and the corrected index.
  5. Pick the sweep. For each of overlap, gap, aliasing, and stale-PADDR, state whether a single directed read inside a window can catch it, or whether it needs a boundary sweep, a memory-walk, or a back-to-back sequence — and the assertion for each.

11. Q&A

12. Key takeaways

  • A decoder promises one slave and one register per address — and every classic bug makes it two, zero, or one-wrong. Two selects is an overlap (PRDATA bus fight); zero is a hole (PRDATA floats / access hangs); one-but-wrong is a mis-target (aliasing, off-by-one, byte-vs-word, stale, or floating decode). Count the selects before you debug.
  • The count family — overlap and gap — is fixed structurally: disjoint, power-of-two-aligned BASE+MASK windows with ((paddr & ~MASK) == BASE) make overlap and one-address gaps impossible, and a default slave turns any remaining hole into a defined error response.
  • The target family is fixed by comparing the right bits at the right time: compare the full high address (no partial slice) so a window never aliases/mirrors; index word registers with paddr[N:2] because PADDR is byte-addressed; and decode the current combinational PADDR gated by PSEL, never a registered/stale copy or an ungated select that floats in idle.
  • Aliasing is non-local and stale-PADDR is back-to-back-only: a mirrored window misbehaves at an address far from the buggy decode, and a stale PADDR misroutes only when two accesses abut — so a single directed interior read clears neither. A memory-walk exposes aliasing; a back-to-back sequence exposes stale-PADDR.
  • The verification reduces to a few named checks: one $onehot/$onehot0 over the select vector (overlap + hole), an expected-decode scoreboard compare (wrong-but-one-hot target), a PSEL-gate (float), and a PADDR-stability check (stale) — all driven by a boundary + memory-walk sweep, because random interior reads never visit the edges where these bugs live (AMBA APB IHI 0024C §1.1 / §2.1, PADDR and decode).