Skip to content

AMBA APB · Module 9

Illegal-Address Access

What happens when PADDR lands outside every mapped peripheral — no PSELx asserts and the access would hang forever — and the default (error) slave the decoder selects on a no-hit to complete the access and return PSLVERR. The decode-error source and the classic unmapped-address lock-up.

An unmapped address has no peripheral to answer it. You already know PSLVERR is a pass/fail verdict and you know exactly when it is sampled. This chapter answers a prior question: where does the error even come from when the address itself is illegal? The single idea to carry: a PADDR that matches no mapped range produces no PSELx, so no slave exists to drive PREADY — and without a default/error slave to complete that no-hit, the access hangs the bus forever. The decoder, not a peripheral, owns this case.

1. Problem statement

The problem is completing an access to an address that belongs to no peripheral — on a bus where the only thing that ever drives PREADY is a selected slave, and a no-hit selects nobody.

APB transfers are addressed by PADDR, and an APB bridge contains a decoder that turns PADDR into the per-slave select lines PSEL0..PSELn. The decoder's contract is "exactly one PSELx high for a real access." But an SoC address map is never fully dense: there are reserved regions, alignment gaps between peripherals, and addresses above the last mapped slave. When PADDR lands in one of those holes, the decoder finds no match — a no-hit — and that creates a structural problem the protocol cannot solve on its own:

  • No PSELx means no slave is listening. APB has no broadcast, no default responder built into the protocol. If no select asserts, nothing on the bus will ever drive PREADY.
  • The manager is committed to waiting. Once the access phase begins (PENABLE high), the manager holds the cycle until it samples PREADY high. If PREADY never comes, the manager waits forever — the access does not error, does not time out, does not abort. It simply never completes.
  • The error is an address fact, not a peripheral fact. There is no peripheral that "owns" the bad address to report it — that is exactly the point: the address belongs to no one. So the responsibility to detect and respond falls on the decoder/bridge, the only block that knows the whole map.

So the job is not "a peripheral reports a bad address." It is "the bridge must give every no-hit somewhere to land — a path that completes the access and stamps it PSLVERR — because the alternative is a permanently stalled bus."

2. Why previous knowledge is insufficient

Chapter 9.1 established what PSLVERR means — a pass/fail verdict sampled on the completion edge — and chapter 9.2 pinned down its timing relative to PREADY. Both assumed an error already had a source: some selected slave that decided to fail. This chapter supplies the source that precedes a selected slave — the address decode itself — and that exposes gaps those chapters never touched:

  • 9.1/9.2 assumed a slave is selected. Their whole model — "the subordinate stamps the verdict on the completion edge" — presumes a PSELx is high and a slave is in the transfer. An illegal address is the case where that assumption fails: there is no selected slave to produce any verdict at all. The error has to come from somewhere before a peripheral is in the loop.
  • The access-phase timing of Module 5 explains the wait, not the deadlock. That chapter taught that the manager holds PENABLE and waits for PREADY. On a normal access that is a bounded wait. On a no-hit it becomes an unbounded wait — the same waiting mechanism, but with nothing on the other end to ever release it. The handshake view tells you the manager will wait; it does not warn you that nobody is going to answer.
  • "PSLVERR is how you report it" is true but incomplete here. Yes, the end result is a PSLVERR. But who drives it, and how the access even gets to a completion edge, is the new content. The default/error slave is a piece of the bridge, invented precisely so an unmapped access has a completing responder — a structural mechanism 9.1 and 9.2 never needed.

So the model to add is the decode-source model: the error originates in the address decode, the no-hit has no natural responder, and the bridge must manufacture one — a default slave — or the bus locks up. (Where the peripheral itself fails an access it does own is chapter 9.5; the discipline of tracing a reported error back to whether it was a decode miss or a peripheral fault is chapter 9.6.)

3. Mental model

The model: the decoder is a switchboard operator, and the default slave is the "this number is not in service" recording. Every PADDR is a phone number; the operator (decoder) connects it to exactly one extension (PSELx). If you dial a number that belongs to no extension, a switchboard with no fallback just leaves you holding a dead line forever — that is the hang. A correct switchboard routes every unassigned number to a recorded message that always answers and tells you the number is invalid — that is the default/error slave completing the access with PSLVERR.

Three refinements make it precise:

  • A no-hit is not "select nobody and move on" — it is "select the responder-of-last-resort." The decode logic must be exhaustive: for every PADDR, either one real PSELx asserts, or the default-slave select asserts. There is no third outcome where nothing is selected, because "nothing selected" is the hang.
  • The default slave's only job is to complete. It does not store data, has no real registers. It exists to assert PREADY (so the manager's wait ends) and PSLVERR (so the manager learns the access was illegal). It is the smallest possible slave: a guaranteed completion plus an error stamp.
  • Reserved is not the same as unmapped, but both are no-hits. A reserved region is a hole the architect deliberately left for future use; an unmapped gap can be an accidental partial-decode hole. To the bus they are identical — no peripheral lives there — so both must route to the default slave. The distinction matters for the map, not for the mechanism.
A block diagram of an APB bridge decoder taking PADDR, comparing it against an address map with three mapped peripheral ranges and red unmapped/reserved gaps, asserting one green PSEL line per mapped hit, and on a no-hit asserting a red PSEL_DEFAULT into a default/error slave that completes the access with PREADY high and PSLVERR high.
Figure 1 — the APB bridge decoder, the address map, the per-slave PSEL lines, and the default error slave. PADDR enters the decoder, which compares it against the map: three mapped ranges (UART, TIMER, GPIO) each cause exactly one PSELx to assert into the matching peripheral, which then drives PREADY and answers normally (green). Between the mapped ranges lie unmapped/reserved gaps (red) where no peripheral exists. On a no-hit, none of the real PSEL lines assert; instead the decoder asserts PSEL_DEFAULT into the default/error slave, which completes the access with PREADY=1 and PSLVERR=1. The figure stresses that the decoder — not any peripheral — owns the no-hit, and that the default slave exists solely so an unmapped access completes with an error instead of hanging the bus forever.

4. Real SoC implementation

In silicon, the decoder lives in the APB bridge and is a small combinational comparator bank plus the default-slave fallback. The non-negotiable property is exhaustiveness: every PADDR resolves to exactly one select — a real peripheral on a hit, or the default slave on a no-hit. The default slave is then a trivial always-completing responder that stamps PSLVERR.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// APB bridge decoder + default (error) slave.
// PSEL_DEFAULT is asserted ONLY when PADDR matches no mapped range.
// The whole point: a no-hit must STILL be selected by SOMETHING, or no
// slave drives PREADY and the access hangs the bus forever.
 
localparam logic [31:0] UART_BASE  = 32'h4000_0000, UART_TOP  = 32'h4000_0FFF;
localparam logic [31:0] TIMER_BASE = 32'h4000_1000, TIMER_TOP = 32'h4000_1FFF;
localparam logic [31:0] GPIO_BASE  = 32'h4000_3000, GPIO_TOP  = 32'h4000_3FFF;
// note the 0x4000_2000 region is RESERVED (a deliberate gap) -> no-hit.
 
logic hit_uart, hit_timer, hit_gpio, hit_any;
 
always_comb begin
  hit_uart  = (paddr >= UART_BASE)  && (paddr <= UART_TOP);
  hit_timer = (paddr >= TIMER_BASE) && (paddr <= TIMER_TOP);
  hit_gpio  = (paddr >= GPIO_BASE)  && (paddr <= GPIO_TOP);
  hit_any   = hit_uart | hit_timer | hit_gpio;
 
  // One PSEL per mapped range; gated by the manager's psel so we only
  // route a real, in-progress access (not a deselected bus).
  psel_uart    = psel & hit_uart;
  psel_timer   = psel & hit_timer;
  psel_gpio    = psel & hit_gpio;
 
  // THE CRITICAL LINE: every no-hit access is routed to the default slave.
  // Without this, hit_any==0 leaves NO select asserted -> nobody drives
  // PREADY -> the manager waits forever. This makes the decode exhaustive.
  psel_default = psel & ~hit_any;
end
 
// Default / error slave: it owns no registers. Its only job is to COMPLETE
// the access (so the manager's wait ends) and stamp it as an error.
always_ff @(posedge pclk or negedge presetn) begin
  if (!presetn) begin
    def_pready  <= 1'b0;
    def_pslverr <= 1'b0;
  end else if (psel_default && penable) begin
    // Complete on the access phase, exactly like a zero-wait slave...
    def_pready  <= 1'b1;
    def_pslverr <= 1'b1;   // ...and flag it: this address is illegal.
  end else begin
    def_pready  <= 1'b0;
    def_pslverr <= 1'b0;   // defined 0 when not completing -> never float a spurious error
  end
end
 
// The bridge muxes PREADY/PSLVERR/PRDATA from whichever slave is selected,
// INCLUDING the default slave -- so a no-hit returns def_pready/def_pslverr.
assign pready  = psel_default ? def_pready  :
                 psel_uart    ? uart_pready :
                 psel_timer   ? tmr_pready  :
                 psel_gpio    ? gpio_pready : 1'b0;
assign pslverr = psel_default ? def_pslverr :
                 psel_uart    ? uart_pslverr :
                 psel_timer   ? tmr_pslverr :
                 psel_gpio    ? gpio_pslverr : 1'b0;

Two facts drive the real design. First, the decode must be exhaustive and mutually exclusivepsel_default = psel & ~hit_any is not an optional nicety, it is the line that converts a potential hang into a clean error; drop it and the ~hit_any case silently selects nobody. Second, the default slave must actually be wired into the response mux. A common integration miss is to generate psel_default but never route def_pready/def_pslverr into the bridge's PREADY/PSLVERR mux — so the select asserts, the slave completes internally, but the manager still sees PREADY=0 and hangs anyway. The completion is only real when the default slave's outputs reach the manager. (Getting PSLVERR to line up with PREADY on that completion edge is the timing discipline of chapter 9.2.)

5. Engineering tradeoffs

Handling a no-hit is a design choice with very different hang-risk and cost profiles. The table is the decision.

ApproachWhat it does on an unmapped accessHang riskCostWhen to choose
No default slaveNo PSELx asserts; PREADY never comesSevere — bus locks up on any illegal addressZero gates, but a latent system killerNever in a real SoC — only "safe" if the map is provably 100% dense (it almost never is)
Dedicated default/error slaveDecoder routes no-hit to a tiny slave that asserts PREADY=1, PSLVERR=1NoneOne small slave + one decode term + a mux inputThe standard, recommended approach for any multi-slave bridge
Decoder-driven error completionDecoder itself drives PREADY=1/PSLVERR=1 on a no-hit (no separate slave block)NoneSmallest area; logic folded into the decoderArea-tight bridges, or simple single-master fabrics where a separate slave is overkill
Per-slave "catch-all" rangeEach slave claims a wide range and errors internally on its own sub-gapsLow within a slave, but gaps between slaves still hangPushes error logic into every peripheral; easy to leave inter-slave holesRarely — only when slaves genuinely own large sparse spaces; still needs a decoder-level default for the spaces between them

The throughline: the dedicated default slave and the decoder-driven completion are the two correct answers — they differ only in whether the always-completing logic sits in its own block or is folded into the decoder. "No default slave" is the bug-by-omission, and "per-slave catch-all" is a trap because it leaves the inter-slave gaps — the addresses that belong to no slave — with no responder. The cost of doing it right is trivial; the cost of the hang is a dead SoC.

6. Common RTL mistakes

7. Debugging scenario

The signature illegal-address bug is a system-wide hard hang the instant software touches a particular reserved or mistyped address — the whole CPU wedges, no error handler runs, and it looks like a catastrophic crash rather than a simple bus error.

  • Observed symptom: during bring-up, the CPU hard-hangs whenever firmware writes a specific control register at 0x4000_2400. No bus-fault exception fires, no watchdog (initially) trips, no PSLVERR is ever logged — the core simply stops making forward progress and the system is dead until reset.
  • Waveform clue: at the hang point, PADDR = 0x4000_2400, PSEL (the manager's bus select) is high and PENABLE is high — the access is in its access phase — but every per-slave PSELx is low, and PREADY stays low forever. The bus sits in the access phase indefinitely; there is no completion edge. The address 0x4000_2400 falls in the reserved gap between TIMER (ends 0x4000_1FFF) and GPIO (starts 0x4000_3000).
  • Root cause: the decoder had a gap — its ranges covered the three peripherals but 0x4000_2000–0x4000_2FFF was reserved and not routed anywhere, and the bridge had no default slave. So on this address hit_any was 0, no select asserted, and nothing in the system was responsible for driving PREADY. The manager did exactly what the protocol says — wait for PREADY — forever. Not a peripheral bug: a decode completeness bug.
  • Correct RTL: make the decode exhaustive and add the default responder. psel_default = psel & ~hit_any; and a default slave that completes on the access phase: if (psel_default && penable) begin def_pready <= 1'b1; def_pslverr <= 1'b1; end — then mux def_pready/def_pslverr back into the bridge PREADY/PSLVERR. Now 0x4000_2400 completes with an error instead of hanging.
  • Verification assertion: prove the decode is always exhaustive (exactly one real select, or the default) and that no access can stall unbounded:
    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    // Exactly one of {real selects, default} is high during a selected access.
    assert property (@(posedge pclk) disable iff (!presetn)
      (psel && penable) |->
        ($onehot({psel_uart, psel_timer, psel_gpio, psel_default})));
     
    // No-hang / bounded completion: once in the access phase, PREADY must
    // arrive within MAXW cycles -- a no-hit must complete via the default slave.
    assert property (@(posedge pclk) disable iff (!presetn)
      (psel && penable) |-> ##[1:MAXW] pready);
  • Debug habit: when an SoC hard-hangs (not errors) on an access, do not start in the peripheral or the exception handler — look at the bus. Find PADDR at the freeze, check whether any PSELx asserted, and check whether PREADY ever came. A hang with PSEL/PENABLE high but every PSELx low and PREADY stuck low is the decode-gap signature: the address matched no slave and no default existed. "Locks up touching a reserved register" is almost always a missing default slave, not a broken peripheral.
Two stacked APB timing diagrams of the same unmapped access. The top green case routes the no-hit to a default slave so PSEL_DEFAULT, PENABLE, PREADY and PSLVERR all align at a completion edge and the access completes with an error. The bottom red case has no default slave: every PSELx stays low, PENABLE is held high, PREADY never asserts, and the bus hangs forever in a system lock-up.
Figure 2 — the same unmapped access, with and without a default slave. Top (correct, green): the decoder routes the no-hit to the default/error slave — PSEL_DEFAULT asserts, PENABLE rises into the access phase, the default slave drives PREADY high, and PSLVERR is high on that same completion edge, so the access completes with an error verdict and the bus returns to idle. Bottom (bug, red): there is no default slave and the map has a gap — no PSELx asserts at all, so no peripheral ever drives PREADY; PENABLE stays high in the access phase, PREADY never goes high, the manager waits for a completion edge that never comes, and the bus hangs forever. The figure shows that the only difference between a clean error and a dead bus is whether the decoder gave the no-hit somewhere to complete.

8. Verification perspective

An illegal-address bug is invisible to any functional test that only ever drives legal addresses — so verification of the decode error is fundamentally about exercising the holes, and proving the bus can never stall.

  • Cover the whole map, especially the gaps. Address-map functional coverage must hit every mapped range and every unmapped/reserved gap and the boundary addresses — the last address of each range, the first address above it, and the first address past the top of the map. Decode bugs live at boundaries (off-by-one ranges) and in reserved gaps (forgotten holes); a coverage model that only samples "somewhere inside each peripheral" misses exactly the addresses that trigger a no-hit.
  • Assert exhaustive, one-hot selection and the default path. Bind a property that during any selected access exactly one of {real selects, default} is high ($onehot(...)), and a directed check that a known-unmapped address actually asserts psel_default and returns PSLVERR=1. Together these prove the decode is complete (no PADDR falls through to "nobody selected") and that the default slave really fires on a no-hit.
  • Prove the no-hang property. The single most important assertion is bounded completion: once PSEL && PENABLE, PREADY must assert within a bounded window. This is the formal statement of "the bus never locks up," and it catches the decode gap directly — an unmapped address with no default slave violates it because PREADY never comes. Formal property checking is ideal here because it explores all addresses, not just sampled ones.
  • Inject decode errors as first-class stimulus. The error suite must deliberately issue accesses to reserved and out-of-range addresses (read and write), confirm each returns PSLVERR via the default slave, and confirm the downstream consequence fires (bus fault / status bit), exactly as a peripheral-sourced error would. An error campaign that never targets an unmapped address has not tested decode error handling at all.

The point: illegal-address verification is a completeness problem. Cover every gap and boundary, assert one-hot-or-default selection, and — above all — prove bounded completion so a no-hit can never hang the bus. (Tying a reported PSLVERR back to "decode miss vs peripheral fault" is the methodology of chapter 9.6.)

9. Interview discussion

"What happens on APB if the CPU accesses an address that isn't mapped to any peripheral?" is a sharp systems filter: the weak answer is "it returns zero" or "the peripheral flags an error." The strong answer names the mechanism and the failure mode.

Frame it as a decode no-hit with no natural responder: PADDR is compared against the address map by the bridge decoder, and an unmapped address matches no range, so no PSELx asserts. Because the only thing that ever drives PREADY is a selected slave, a no-hit means nothing drives PREADY, and the manager — committed to the access phase — waits forever: the bus hangs. Then deliver the fix: the bridge must include a default/error slave (or fold the equivalent logic into the decoder) that the decode selects on every no-hit; it completes the access by asserting PREADY and stamps it PSLVERR=1, turning a hang into a clean, reported bus error. Add the depth that separates levels: the error is owned by the decoder/bridge, not any peripheral (the address belongs to no one, so no peripheral can report it); reserved and unmapped regions are both no-hits and both must route to the default slave; and the decode must be exhaustive — for every PADDR, exactly one real select or the default asserts, never "nobody." Closing with "the classic silicon symptom is a hard system lock-up when firmware touches a reserved register — that is a missing default slave, debugged at the bus by checking that PSEL/PENABLE are high but every PSELx is low and PREADY never comes" signals you have actually brought up a fabric, not just read the spec.

10. Practice

  1. Walk the no-hit. Given the map UART 0x4000_0000–0FFF, TIMER 0x4000_1000–1FFF, GPIO 0x4000_3000–3FFF, trace an access to 0x4000_2400: state which hit_* terms are true, what psel_default is, and what drives PREADY/PSLVERR.
  2. Cause the hang. Delete the psel_default term from the decoder and describe, cycle by cycle, what the manager sees on that same 0x4000_2400 access and why the bus never completes.
  3. Reserved vs unmapped. Explain the difference between a reserved region and an accidental partial-decode gap, and argue why the bus mechanism treats them identically.
  4. Write the default slave. From memory, write the registered default-slave block that completes on the access phase with PSLVERR=1 and drives a defined 0 otherwise, and explain the else branch.
  5. Find the missing wire. A decoder generates psel_default correctly but the SoC still hangs on unmapped addresses. State the most likely integration bug and the one-line check that confirms it.

11. Q&A

12. Key takeaways

  • An illegal address is a decode no-hit: PADDR matches no mapped range, so no PSELx asserts — and the only thing that ever drives PREADY is a selected slave. No select means nobody answers.
  • No default slave = the classic bus hang. With nothing selected and no fallback, PREADY never comes; the manager waits forever and the SoC hard-locks. "Ignored" is really "hung."
  • The bridge decoder owns the error, not a peripheral. The bad address belongs to no peripheral, so the bridge — the only block that knows the whole map — must detect the no-hit and route it to a default/error slave that completes the access (PREADY=1) and flags it (PSLVERR=1).
  • The decode must be exhaustive: for every PADDR, exactly one real select or the default asserts — never "nobody." Reserved and unmapped regions are both no-hits and both must route to the default slave.
  • The integration trap is a psel_default that is generated but not muxed back into the bridge's PREADY/PSLVERR — the select asserts but the completion never reaches the manager, and the bus hangs anyway.
  • Verify completeness, not just function: cover every gap and boundary address, assert one-hot-or-default selection, and — above all — prove bounded completion so a no-hit can never hang the bus; formal beats simulation because it quantifies over all addresses.