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
PSELxmeans 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 drivePREADY. - The manager is committed to waiting. Once the access phase begins (
PENABLEhigh), the manager holds the cycle until it samplesPREADYhigh. IfPREADYnever 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
PSELxis 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
PENABLEand waits forPREADY. 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. - "
PSLVERRis how you report it" is true but incomplete here. Yes, the end result is aPSLVERR. 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 realPSELxasserts, 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) andPSLVERR(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.
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.
// 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 exclusive — psel_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.
| Approach | What it does on an unmapped access | Hang risk | Cost | When to choose |
|---|---|---|---|---|
| No default slave | No PSELx asserts; PREADY never comes | Severe — bus locks up on any illegal address | Zero gates, but a latent system killer | Never in a real SoC — only "safe" if the map is provably 100% dense (it almost never is) |
| Dedicated default/error slave | Decoder routes no-hit to a tiny slave that asserts PREADY=1, PSLVERR=1 | None | One small slave + one decode term + a mux input | The standard, recommended approach for any multi-slave bridge |
| Decoder-driven error completion | Decoder itself drives PREADY=1/PSLVERR=1 on a no-hit (no separate slave block) | None | Smallest area; logic folded into the decoder | Area-tight bridges, or simple single-master fabrics where a separate slave is overkill |
| Per-slave "catch-all" range | Each slave claims a wide range and errors internally on its own sub-gaps | Low within a slave, but gaps between slaves still hang | Pushes error logic into every peripheral; easy to leave inter-slave holes | Rarely — 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, noPSLVERRis 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 andPENABLEis high — the access is in its access phase — but every per-slavePSELxis low, andPREADYstays low forever. The bus sits in the access phase indefinitely; there is no completion edge. The address0x4000_2400falls in the reserved gap betweenTIMER(ends0x4000_1FFF) andGPIO(starts0x4000_3000). - Root cause: the decoder had a gap — its ranges covered the three peripherals but
0x4000_2000–0x4000_2FFFwas reserved and not routed anywhere, and the bridge had no default slave. So on this addresshit_anywas0, no select asserted, and nothing in the system was responsible for drivingPREADY. The manager did exactly what the protocol says — wait forPREADY— 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 muxdef_pready/def_pslverrback into the bridgePREADY/PSLVERR. Now0x4000_2400completes 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 MentorSnippet
// 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
PADDRat the freeze, check whether anyPSELxasserted, and check whetherPREADYever came. A hang withPSEL/PENABLEhigh but everyPSELxlow andPREADYstuck 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.
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 assertspsel_defaultand returnsPSLVERR=1. Together these prove the decode is complete (noPADDRfalls 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,PREADYmust 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 becausePREADYnever 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
PSLVERRvia 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
- Walk the no-hit. Given the map UART
0x4000_0000–0FFF, TIMER0x4000_1000–1FFF, GPIO0x4000_3000–3FFF, trace an access to0x4000_2400: state whichhit_*terms are true, whatpsel_defaultis, and what drivesPREADY/PSLVERR. - Cause the hang. Delete the
psel_defaultterm from the decoder and describe, cycle by cycle, what the manager sees on that same0x4000_2400access and why the bus never completes. - 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.
- Write the default slave. From memory, write the registered default-slave block that completes on the access phase with
PSLVERR=1and drives a defined0otherwise, and explain theelsebranch. - Find the missing wire. A decoder generates
psel_defaultcorrectly 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:
PADDRmatches no mapped range, so noPSELxasserts — and the only thing that ever drivesPREADYis a selected slave. No select means nobody answers. - No default slave = the classic bus hang. With nothing selected and no fallback,
PREADYnever 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_defaultthat is generated but not muxed back into the bridge'sPREADY/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.