Skip to content

Almost every APB subsystem that returns garbage data or hangs on a legal address has a PSEL fanout bug — and they all trace back to one broken invariant: the decoder must produce exactly one PSEL per access. You have learned that PSEL is the per-subordinate select a decoder derives from PADDR, and that the addressed subordinate's PREADY and PRDATA come back through a shared return path. This chapter inverts that knowledge into a debugging instrument. It is a catalog of the classic PSEL distribution bugs — the recurring mistakes in the decoder and in slave PSEL handling that pass a quick directed sim and ship — each as a named bug with a symptom on the bus, the decoder/RTL root cause, the wrong RTL that produces it, and the correct one-hot RTL that fixes it. The single idea to carry: when an APB access "reads contention garbage," "hangs on an address that should work," or "the wrong slave answers," you should not start from scratch — you should pattern-match against the small set of ways a one-hot PSEL vector gets broken, because the bug is almost certainly one of them.

1. Problem statement

The problem is that PSEL is a one-hot select vector that the decoder must keep exactly one-hot for every access, and a handful of recurring decoder and slave mistakes break that one-hot invariant in ways that look like different failures but trace back to the same vector.

A correct subsystem holds one and only one PSEL high during an active access: zero selects when the bus is idle, exactly one ($onehot) when an access is in flight (AMBA APB IHI 0024C §2.1, PSEL and address decode). That single bit carries two promises to the shared return path: "this subordinate, and only this one, owns this access" and, implicitly, "there is a subordinate that owns it at all." Break the first — assert two PSELs at once — and two subordinates drive the shared PREADY/PRDATA simultaneously: contention, and the manager latches corrupted or X read data. Break the second — assert no PSEL for a legal address — and nothing drives the return path: the access hangs, PREADY never rises. Every classic PSEL bug is a violation of the one-hot invariant or of who is allowed to honour it:

  • Multi-select bugs raise more than one PSEL at once: overlapping decode ranges, comparators that are not mutually exclusive, or a non-onehot encoding. Two slaves answer; the shared return path is in contention.
  • No-select bugs raise zero PSEL for a legal address: a decode hole with no else/default branch covering the gap, so a legal access selects nothing and hangs with no responder.
  • Phase and glitch bugs raise the right PSEL at the wrong time: PSEL asserted in the setup-vs-access window incorrectly, or a combinational decode glitching while PADDR ripples, briefly selecting a neighbour.
  • Slave-side bugs honour a PSEL that is not theirs: a subordinate that ignores its own psel_sN and responds to a neighbour's access, or one that mis-orders the PSEL/PENABLE deassert at the end of an access.

The engineering problem of this chapter is therefore not "what does PSEL mean" — you know that from the PSEL select-signal chapter — but "what are the specific ways a real decoder and real slaves get the PSEL fanout wrong, what does each look like on the bus, and what is the correct one-hot fix for each."

2. Why previous knowledge is insufficient

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

  • The PSEL select signal taught what PSEL is — the per-subordinate qualifier the manager phase relies on. That chapter is the definition; here PSEL is the thing that breaks, and knowing its definition does not give you the taxonomy of every way the decoder mis-drives it.
  • The address decoder and address allocation strategy taught how PADDR maps to a select and how to lay out non-overlapping, gap-free regions. That is the correct construction — but it assumed the construction is right. This chapter is about what happens when ranges do overlap, when a gap is left, and when the map and the RTL disagree.
  • The read-data mux taught how the addressed subordinate's PRDATA is selected back to the manager. That explains the mechanism a multi-select bug corrupts — two drivers into the same mux — but it assumed a one-hot select feeding it. This chapter is about the select itself going non-onehot.

The gap is this: prior chapters taught the correct behaviour and the correct construction. They did not assemble the failure taxonomy — the named set of PSEL fanout bugs with wrong-versus-right RTL — that lets you debug a real access by recognition instead of derivation. Building that taxonomy is this chapter; the protocol rules those bugs violate live in the legal/illegal transition rules and are checked continuously by APB assertions.

3. Mental model

The model: PSEL is a one-hot vector across an access, and every classic bug breaks the one-hot count or the ownership it implies — so count the selects first, then pick the bug.

The bins are the ways a one-hot PSEL contract fails:

  • Too many selects (count > 1). Two or more PSELs high at once. On the bus you see contention — two subordinates driving the shared PREADY/PRDATA, so the read data is corrupted or X and the completion is ambiguous. The cause is always the decode ranges are not mutually exclusive: overlapping address windows, comparators that both fire, or a glitch that briefly co-asserts a neighbour during PADDR ripple.
  • Too few selects (count = 0 for a legal address). No PSEL high when one should be. On the bus you see a hangPSEL/PENABLE would-be high but nothing answers, PREADY never rises. The cause is always a hole in the decode: a legal address that no branch covers, no else/default to catch it, so the access selects nothing.
  • Right select, wrong time or wrong owner. The count is one-hot but the timing or honouring is wrong: PSEL asserted in the wrong phase, a decode glitch selecting the wrong slave mid-ripple, or a subordinate that ignores its own select and answers a neighbour's access. The bus symptom is the wrong slave's data, or a completion in the wrong phase.

The discipline this model buys you: count before you debug. "Read data is X / contention" instantly points at too-many-selects; "it hangs on one specific address range" points at too-few-selects; "the wrong register answers" points at the timing/ownership bin. Three refinements sharpen it:

  • A multi-select is a safety failure; a no-select is a liveness failure. Multi-select commits bad data (something wrong happens); no-select never completes (something good never happens). They need different assertions — a one-hot property for the first, a bounded-completion-or-error property for the second.
  • A decode glitch is a multi-select that exists only in time. While PADDR ripples through a combinational decoder, the select can momentarily land on a neighbour before settling. It is one-hot at every settled edge yet non-onehot between edges — invisible in zero-delay RTL, real in gate-level. Bin it with multi-select, but suspect it only when the steady-state decode is provably clean.
  • A slave that answers a neighbour's access is a one-hot decode betrayed downstream. The vector can be perfectly one-hot and the bug still happens — because a subordinate qualified its response on PENABLE alone, or on a stale latched select, instead of on its own live psel_sN. Bin it as ownership, and check the slave, not the decoder.
A block diagram of an APB subsystem. The manager drives PADDR into a single address decoder, which produces a one-hot vector psel_s0, psel_s1, psel_s2 fanning out to three subordinates, with PENABLE broadcast in common. The addressed subordinate's PREADY and PRDATA return through a one-hot mux back to the manager. The decoder is annotated with the one-hot requirement and two marked failure points: two PSELs high at once causing contention, and a decode hole asserting no PSEL causing a hang.
Figure 1 — the APB PSEL fanout structure and its two failure points. The manager drives PADDR into a single address decoder that must produce a one-hot select vector {psel_s0, psel_s1, psel_s2}, one PSEL per subordinate, with PENABLE broadcast in common to all. On the return side the addressed subordinate's PREADY and PRDATA are selected back to the manager through the same one-hot decode, so exactly one subordinate may drive the shared return path. The figure annotates the one-hot requirement ($onehot0: zero selects when idle, exactly one in an active access) and marks the two failure points the chapter pivots on: FAIL 1, a decoder that asserts two PSELs at once (overlapping ranges, non-onehot) so two subordinates contend on the shared return; and FAIL 2, a decode hole that asserts no PSEL for a legal address, leaving the shared PREADY ungated so the access hangs. Read the structure left-to-right as PADDR to decode to fanout, and right-to-left as the one-hot-gated PREADY/PRDATA return.

4. Real SoC implementation

In real RTL the top PSEL bugs are a few lines of plausible-looking decode, and the fixes are equally short — which is exactly why they ship. Here are the wrong-versus-right pairs for the three highest-frequency bugs — overlapping ranges (multi-select), decode hole (no-select), and a slave that honours the wrong select — using the real APB signal names.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ============================================================
// BUG 1 — MULTIPLE-SELECT: overlapping decode ranges, two PSEL high
// ============================================================
// WRONG: the two range checks are NOT mutually exclusive. paddr in the
// overlap (0x2800..0x2FFF) makes both psel_s0 and psel_s1 assert -> two
// subordinates drive the shared PREADY/PRDATA -> contention / X reads.
assign psel_s0 = sel && (paddr >= 32'h0000_2000) && (paddr <= 32'h0000_2FFF);
assign psel_s1 = sel && (paddr >= 32'h0000_2800) && (paddr <= 32'h0000_3FFF);
//                                       ^^^^ ranges overlap at 0x2800..0x2FFF
 
// CORRECT: one-hot decode from a single, mutually-exclusive case on the
// region field of paddr. Exactly one psel_sN can be high; an unmapped
// address selects NOTHING here and is steered to a default responder (below).
always_comb begin
  psel_s0 = 1'b0; psel_s1 = 1'b0; psel_s2 = 1'b0;
  if (sel) begin
    unique case (paddr[15:12])        // region field -> one branch only
      4'h2:    psel_s0 = 1'b1;        // 0x2000..0x2FFF  -> S0, exclusively
      4'h3:    psel_s1 = 1'b1;        // 0x3000..0x3FFF  -> S1
      4'h4:    psel_s2 = 1'b1;        // 0x4000..0x4FFF  -> S2
      default: /* no slave: handled by default responder, see BUG 2 */ ;
    endcase
  end
end
// 'unique case' makes overlap a tool error, not a silent runtime contention.
 
// ============================================================
// BUG 2 — NO-SELECT (decode hole): legal address selects nothing -> hang
// ============================================================
// WRONG: only the mapped ranges are decoded; a legal-but-unmapped paddr
// (e.g. 0x1800) drives NO psel_sN, nothing answers, pready never rises.
assign psel_s0 = sel && (paddr[15:12] == 4'h2);
assign psel_s1 = sel && (paddr[15:12] == 4'h3);
// ...gap at 4'h0,4'h1,4'h5.. -> no responder -> bus HANGS forever
 
// CORRECT: cover the WHOLE legal space. Unmapped addresses select a
// default error responder that completes the access with pslverr, so a
// hole returns an ERROR instead of hanging the bus.
always_comb begin
  psel_s0 = 1'b0; psel_s1 = 1'b0; psel_s2 = 1'b0; psel_err = 1'b0;
  if (sel) begin
    unique case (paddr[15:12])
      4'h2:    psel_s0  = 1'b1;
      4'h3:    psel_s1  = 1'b1;
      4'h4:    psel_s2  = 1'b1;
      default: psel_err = 1'b1;       // unmapped -> default responder, NOT a hole
    endcase
  end
end
// psel_err's subordinate drives pready=1, pslverr=1, prdata='0 -> clean error,
// no hang. (Illegal-address handling is detailed in /protocols/apb/illegal-address-access.)
 
// ============================================================
// BUG 3 — SLAVE HONOURS THE WRONG SELECT: answers a neighbour's access
// ============================================================
// WRONG: this subordinate qualifies its response on PENABLE alone (or a
// stale latched select), so it answers ANY access in its access phase --
// including a neighbour's -> the wrong slave's data on the shared bus.
always_comb begin
  pready_s2 = penable;                       // <- ignores its own psel_s2!
  prdata_s2 = reg_file[paddr[5:2]];          // drives the bus even when not selected
end
 
// CORRECT: a subordinate answers ONLY inside its own selected access, and
// must drive the return path quiet (0 / high-Z per integration) when !psel_s2.
always_comb begin
  if (psel_s2 && penable) begin
    pready_s2 = data_valid_s2;               // ready only in OUR access phase
    prdata_s2 = reg_file[paddr[5:2]];
    pslverr_s2 = 1'b0;
  end else begin
    pready_s2  = 1'b0;                        // not selected -> stay out of the
    prdata_s2  = 32'h0;                       // shared return path entirely
    pslverr_s2 = 1'b0;
  end
end
// Gate every response term on the slave's OWN psel_sN && penable, never on
// penable alone -- so one-hot ownership is honoured downstream too.

Three facts drive these fixes. First, a multi-select fix is structural one-hot construction: derive every PSEL from a single, mutually-exclusive unique case on the region field, so overlap becomes a tool error rather than a silent runtime contention. The overlap bug is not a typo — it is two independent range comparators that the author never proved disjoint. Second, a no-select fix is total coverage: the decode must cover the entire legal address space, routing any unmapped-but-legal address to a default error responder that completes the access with pslverr instead of leaving a hole that hangs the bus (the error path is covered in illegal-address access). Third, a slave-side fix is ownership: every response term — pready_sN, prdata_sN, pslverr_sN — must be gated on the slave's own psel_sN && penable, never on penable alone, so a perfectly one-hot decode is also honoured by the subordinate that receives it.

5. Engineering tradeoffs

The catalog itself is the deliverable: one row per bug, the symptom you will actually see on the bus, the root cause in the decoder or slave, and the fix. Memorise the shape of each row — count-binned, with a one-hot construction fix — and you can debug most APB select failures by recognition.

Bug nameSymptom on the busRoot causeFix
Multiple-slave-selectedRead data is X / corrupted; two subordinates contend on shared PREADY/PRDATA; ambiguous completionOverlapping decode ranges or non-mutually-exclusive comparators raise two PSELs at onceBuild the one-hot from a single unique case on the region field; make overlap a tool error, not runtime contention
No-slave-selected (decode hole)Access hangs on one specific legal address range; PREADY never rises, no responderA legal-but-unmapped address falls in a gap with no else/default branchCover the whole legal space; route unmapped addresses to a default error responder that completes with pslverr
PSEL in the wrong phaseCompletes in a setup-only phase, or a PSEL pulse with no valid PENABLE accessPSEL asserted outside the proper select-then-enable sequence; not aligned to the access phaseAssert PSEL in setup, hold through access; gate slave response on PSEL && PENABLE, never PSEL alone mid-sequence
Decode glitch (PADDR ripple)Intermittent wrong-slave selection / brief contention; passes RTL, fails gate-levelCombinational decode off a rippling PADDR momentarily co-asserts a neighbour before settlingRegister PSEL (or qualify it with a stable PENABLE); decode off a settled address, never raw rippling PADDR
Slave ignores its PSELThe wrong slave's data appears; a neighbour answers an access that was not its ownSubordinate qualifies response on PENABLE alone or a stale latched select, not its live psel_sNGate every response term on the slave's own psel_sN && PENABLE; drive the return path quiet when !psel_sN
PSEL/PENABLE deassert orderingSpurious extra select pulse or a slave that re-triggers at end of accessAt access end PENABLE and PSEL deassert in the wrong order, leaving a one-cycle PSEL-only window misread as a new setupDeassert cleanly to idle: drop PENABLE and PSEL together (or PSEL last) so no stray setup-looking pulse remains

The throughline: every row sorts into too-many-selects, too-few-selects, or wrong-time/wrong-owner, and every fix is either "construct the one-hot correctly," "cover the whole space," or "honour the select downstream." The decode-glitch and deassert-ordering rows add the level dimension — they only surface with real delays or at gate-level, which is why a clean RTL run is not proof. The multi-select and slave-ignores rows add the non-local dimension — the symptom (corruption, a wrong responder) can appear on a different slave than the one the manager addressed, which is why you suspect the whole fanout, not just the addressed subordinate.

6. Common RTL mistakes

7. Debugging scenario

Pick the multiple-slave-selected bug, because it is the most corrosive of the catalog: it passes every directed test that happens to address the non-overlapping part of each range, ships, and then surfaces as intermittent read corruption that no one can pin to a single slave.

  • Observed symptom: firmware reading a peripheral's data register intermittently gets a value that is neither slave A's nor slave B's register — it looks like a bitwise mix of two registers, or an X that the bus pulls to a random pattern. It happens only for addresses near a region boundary; reads in the middle of each region are always clean. The bus shows a completion with no protocol error and no hang — just wrong data.
  • Waveform clue: at the offending PADDR (Figure 2, middle panel), both psel_s0 and psel_s1 are high in the same access. Two subordinates assert PREADY and both drive PRDATA onto the shared return path; the captured PRDATA is the bitwise contention of S0's and S1's register values (or X if the integration resolves contention to X). The $onehot0({psel_s0,psel_s1,psel_s2}) check, if present, fires on exactly this access.
  • Root cause: the two region comparators were written as independent >=/<= range checks and were never proven disjoint. S0 was specced as 0x2000–0x2FFF and S1 as 0x2800–0x3FFF; the windows overlap at 0x2800–0x2FFF. Any address in that overlap asserts both PSELs, so both subordinates own the access and both drive the shared return — contention. Directed tests that read 0x2000 and 0x3800 (the clean halves) never expose it; only the boundary addresses do.
  • Correct RTL: derive the whole one-hot vector from a single mutually-exclusive unique case on the region field, so the overlap cannot exist and the tool flags any author who reintroduces it:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    always_comb begin
      psel_s0 = 1'b0; psel_s1 = 1'b0; psel_s2 = 1'b0;
      if (sel) unique case (paddr[15:12])   // one branch only, ever
        4'h2: psel_s0 = 1'b1;
        4'h3: psel_s1 = 1'b1;
        4'h4: psel_s2 = 1'b1;
        default: ;                          // -> default responder, never two
      endcase
    end
  • Verification assertion: prove the select vector is one-hot (or all-zero) on every cycle — the single property that catches the entire multi-select family:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    // one-hot PSEL: never two subordinates selected at once
    assert property (@(posedge pclk) disable iff (!presetn)
      $onehot0({psel_s0, psel_s1, psel_s2})
    );
  • Debug habit: when reads are wrong only near region boundaries and the bus protocol looks clean, do not chase the data path or a single slave first — chase the select vector. Capture the PSEL bus at the offending PADDR and check $onehot0. A read that is a mix of two registers is the fingerprint of two PSELs high at once, and the fix is always in the decoder ranges, not in either slave. The address allocation strategy is where you confirm the intended ranges are disjoint and gap-free.
Three stacked panels showing the PSEL decode for one APB access. The top green panel shows a legal address asserting exactly one PSEL with one-hot holding and valid data returned. The middle red panel shows overlapping ranges asserting two PSELs at once, with the bus symptom of contention and corrupted or X read data. The bottom amber panel shows a legal address in a decode gap asserting no PSEL, with the bus symptom of a hung access because nothing drives PREADY.
Figure 2 — the two PSEL count failures versus the correct one-hot decode, for one access. Top (correct, green): a legal address (0x2004) asserts exactly one PSEL (psel_s1), $onehot0 holds, the single addressed subordinate drives the shared PREADY/PRDATA, and the access completes with valid data. Middle (bug, red): overlapping ranges (S0 0x2000-0x2FFF and S1 0x2800-0x3FFF) make paddr 0x2804 assert both psel_s0 and psel_s1 — $onehot0 fails, two subordinates drive the shared return path together, and the bus symptom is contention: prdata is the bitwise mix of S0 and S1 (or X), an ambiguous completion the manager latches as garbage. Bottom (bug, amber): a legal address (0x1800) falls in a decode gap with no default branch, so no PSEL asserts, nothing drives the shared PREADY, and the access hangs forever with PSEL/PENABLE high and no completion unless a watchdog fires. The figure pairs each decode signature (the PSEL vector) with its bus symptom so you can read count-of-selects straight off a capture.

8. Verification perspective

Because the catalog splits cleanly into safety (too-many-selects: contention) and liveness (too-few-selects: hang) failures, plus a timing/ownership class, the verification plan needs one assertion class per family plus an awareness of which level each bug surfaces at — a one-hot check in RTL will never see a glitch, and a value check will never see a hang.

  • One-hot catches the multi-select family. A single safety property — the select vector is never more than one-hot — catches overlapping ranges, non-exclusive comparators, and the settled-edge case of the glitch in one stroke:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    // never two (or more) subordinates selected simultaneously
    a_psel_onehot: assert property (@(posedge pclk) disable iff (!presetn)
      $onehot0({psel_s0, psel_s1, psel_s2}));
  • No-decode-hole + bounded-completion catches the no-select family. Pair a property that some responder is selected for any legal in-flight access with a bounded-completion watchdog, so a decode hole becomes a located assertion failure instead of a silent hang:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    // a legal access must select a responder (one slave OR the default error one)
    a_no_decode_hole: assert property (@(posedge pclk) disable iff (!presetn)
      (sel && legal_addr) |-> $onehot({psel_s0,psel_s1,psel_s2,psel_err}));
    // and it must complete within N cycles, never hang
    a_bounded_complete: assert property (@(posedge pclk) disable iff (!presetn)
      (psel_any && penable) |-> ##[1:N] pready);
  • Ownership and timing need slave-level and gate-level checks. Assert that an unselected subordinate stays out of the return path (!psel_s2 |-> (prdata_s2 == '0) && !pready_s2) to catch the slave-ignores-its-PSEL bug, which directed single-slave tests cannot see. And know the level split: the one-hot, no-hole, bounded-completion, and ownership checks all run in RTL, but the decode-glitch bug (and a marginal deassert-ordering race) only appear in gate-level or delay-annotated simulation as PADDR ripples — a clean RTL run is necessary but not sufficient. Carrying every one of these into a coherent decode-and-select assertion plan is the job of the APB assertions and APB monitors work, against the legal/illegal transition rules.

The point: each bug family has a named assertion that catches its whole class — $onehot0 for contention, no-hole + bounded-completion for hangs, an unselected-quiet check for ownership — and each has a level at which it actually manifests, so a PSEL verification plan is "one one-hot property, one no-hole property, one bounded-completion, one unselected-quiet check, and a gate-level pass for the glitch," not a pile of directed reads.

9. Interview discussion

"Walk me through the PSEL decode bugs you have seen in a multi-slave APB subsystem" is a senior screening question because a weak answer names one ("two slaves can get selected") and stops, while a strong answer reveals an organised mental catalog keyed on the one-hot invariant — exactly the recognition speed that separates someone who has debugged an APB fabric from someone who has only read the spec.

Frame it as one invariant, three failure modes: the decoder must keep PSEL exactly one-hot per access, and every bug breaks the count (too many, too few) or the ownership (right count, wrong honourer). Then enumerate crisply: in the too-many family, multiple-slave-selected (overlapping ranges or non-exclusive comparators → two subordinates contend on the shared PREADY/PRDATA → corrupted/X reads — the boundary-address bug that survives directed tests) and the decode glitch (rippling PADDR into a combinational decode briefly co-asserts a neighbour, invisible in RTL, caught at gate-level); in the too-few family, no-slave-selected (a legal address in a decode gap with no default → nothing answers → hang, the non-local deadlock); in the ownership family, the slave that ignores its own PSEL (gates response on PENABLE alone and answers a neighbour) and the PSEL/PENABLE deassert-ordering race at end of access. Land the depth points: the fix for too-many is structural one-hot construction (one unique case on the region field, so overlap is a tool error not runtime contention); the fix for too-few is total coverage (a default error responder for unmapped-but-legal addresses, so a hole returns pslverr instead of hanging); the fix for ownership is downstream gating (every slave response term on its own psel_sN && PENABLE); and the verification is one $onehot0 property plus one no-decode-hole + bounded-completion property, with the glitch needing gate-level. Closing with "and the no-slave-selected bug taught me to give every legal address a responder — a default error slave — so a decode hole fails as a clean pslverr instead of a silent bus hang" signals real fabric integration, not spec reading.

10. Practice

  1. Count the selects. Given three field reports — "reads near the 0x2800 boundary return a mix of two registers," "the bus hangs only when firmware touches 0x1800," and "slave S2 answers when firmware reads S1" — assign each to the too-many, too-few, or ownership family and name the specific bug.
  2. Fix the overlap. Given psel_s0 = sel && paddr inside [0x2000:0x2FFF] and psel_s1 = sel && paddr inside [0x2800:0x3FFF], explain why an address in the overlap corrupts a read and rewrite the decode as a single mutually-exclusive unique case on the region field.
  3. Close the hole. Explain why a legal-but-unmapped address with no default branch hangs the bus, and write the decode change (a psel_err default responder) that converts the hang into a clean pslverr completion.
  4. Honour the select. Show why a subordinate gating pready_s2/prdata_s2 on penable alone answers a neighbour's access, and rewrite it to gate on its own psel_s2 && penable and stay quiet when !psel_s2.
  5. Pick the level. For each of multiple-slave-selected, no-slave-selected, decode glitch, and slave-ignores-PSEL, state whether a zero-delay RTL simulation can catch it or whether it needs gate-level / a delay-annotated run, and the assertion for each.

11. Q&A

12. Key takeaways

  • PSEL is a one-hot select vector the decoder must keep exactly one-hot per access — and every classic bug breaks the count or the ownership it implies. Too many selects → contention and garbage reads; too few → a hang; right count, wrong honourer → the wrong slave answers. Count the selects before debugging.
  • The too-many family — multiple-slave-selected (overlapping ranges) and the decode glitch — is fixed by structural one-hot construction: derive every PSEL from a single mutually-exclusive unique case on the region field, so overlap is a tool error, not silent runtime contention.
  • The too-few family — no-slave-selected (a decode hole) — is fixed by total coverage: route every unmapped-but-legal address to a default error responder that completes with pslverr, so a hole returns a clean error instead of a silent bus hang.
  • The ownership family — a slave that ignores its own PSEL, and the PSEL/PENABLE deassert-ordering race — is fixed downstream: gate every slave response term on its own psel_sN && PENABLE, keep unselected slaves quiet on the shared return, and deassert cleanly to idle. A one-hot decode is necessary but not sufficient.
  • A clean RTL run is not proof. The decode glitch and the deassert-ordering race only surface in gate-level or delay-annotated simulation as PADDR ripples; verify at the right level.
  • The verification reduces to a few named properties: one $onehot0 (safety) for contention, one no-decode-hole plus bounded-completion (liveness) for hangs, and one unselected-quiet check for ownership — carried forward into the full plan of the APB assertions and monitor work against the legal/illegal transition rules.