AMBA APB · Module 13
Bridge Address Mapping
The bridge as the address decoder for its APB sub-system — translating the upstream system address to a peripheral offset, asserting exactly one PSEL in a one-hot fan-out across N peripherals, muxing the shared PREADY/PRDATA/PSLVERR response back, and decode-erroring anything unmapped.
The bridge is the address decoder for its APB sub-system. It sits at a base address, owns one window of the system map, and inside that window it must pick exactly one peripheral. The single idea to carry through this chapter: subtract the bridge base to get a peripheral offset (that becomes PADDR), compare that offset against each peripheral's window to assert exactly one PSELx, mux the response of the addressed peripheral back, and decode-error anything in the window that maps to no peripheral. The bridge FSM (13.7) sequences the two-phase handshake; this chapter is about where that handshake goes.
1. Problem statement
The problem is mapping a single upstream address onto the right one of several APB peripherals — generating the per-peripheral PSEL and the translated PADDR, while making any unmapped address a clean error rather than a silent fault.
A real bridge does not face one peripheral; it faces a cluster — a timer, a UART, a GPIO block, a watchdog, all hung off the same downstream APB bus, sharing PADDR/PWRITE/PWDATA/PENABLE. When a CPU access arrives carrying a single system address, the bridge must answer three questions in one decode:
- Which peripheral? The shared APB bus has one
PADDRand onePENABLE, but each peripheral has its ownPSEL. The bridge must assert exactly onePSELx— the addressed peripheral's — and hold every otherPSELlow. Asserting two would drive two peripherals at once; asserting none would silently drop the access. - What offset? The peripheral does not want the raw system address — it wants the offset into its own space. The bridge owns a window starting at
BRIDGE_BASE; the peripheral-space offset is the upstream address minus that base. That offset (further reduced to a per-peripheral local offset if the peripheral is byte-addressed from zero) is what becomesPADDR. - What if it maps to nothing? An address can land inside the bridge's window but in a gap — a reserved or unpopulated region between peripherals. That access must not float, must not pick a wrong peripheral, and must not hang the bus. It has to become a decode error returned upstream.
So the job is a one-hot address decoder with translation and a defined error path — the bridge as the routing fabric of its own little APB subsystem.
2. Why previous knowledge is insufficient
You have already met every ingredient of this decode in earlier chapters, but never assembled at the bridge.
- You decoded inside one slave, not across peripherals. The slave address decoder chose a register within a single peripheral —
PADDRwas already pointed at that slave, and the decode picked a field. Here the decode happens one level up: it chooses which peripheral gets selected at all, producing thePSELfan-out before any peripheral's internal decode runs. Same comparator idea, completely different scope. - You met the default slave as a concept, not as bridge RTL. Illegal-address access taught that an unmapped address needs a default/decode-error response so the bus never hangs. This chapter is where that lives: the bridge's decoder is the thing that detects "in my window but mapped to no peripheral" and raises the error — wiring the default-slave concept into the bridge's
PSELfan-out. - You laid out a register map, not a peripheral map. Address-allocation strategy covered organizing addresses within a peripheral's space (alignment, power-of-two windows, reserved holes). The same discipline now applies one level up — laying out peripherals under the bridge window so their decode ranges are aligned, power-of-two, and mutually exclusive. The strategy is identical; the unit is a peripheral, not a register.
- You built the bridge as a translator, not a router. The AHB-to-APB bridge chapter framed the bridge as a single AHB-slave-to-APB-manager translation, implicitly facing one peripheral. The real bridge fans out to N. The decode, the one-hot
PSEL, and the response mux are exactly what that single-peripheral picture left out — and the bridge FSM chapter that follows assumes this decode already exists to tell it whichPSELxto drive.
So the model to add is the bridge-as-decoder: translate the address to an offset, one-hot the PSEL, mux the response, and decode-error the gaps.
3. Mental model
The model: the bridge is the front desk of an office building. The street address (the system address) gets the visitor to the building (the bridge window). Inside, the front desk subtracts the street number to find the suite number (the offset), looks it up on a directory board, and lights up exactly one office's "occupied" lamp (one PSELx) — never two, never none. The visitor walks to that office; the office's reply comes back through the same front desk (the response mux selects that office's answer). And if the suite number on the directory is blank — a floor under renovation — the desk doesn't send the visitor wandering; it returns a "no such office" notice (decode error).
Three refinements make it precise:
- Translation is a subtraction, then a select.
offset = upstream_addr − BRIDGE_BASE. That offset is the peripheral-space address and becomesPADDR. The decode then comparesoffsetagainst each peripheral's window[base_i, base_i + size_i)— typically a masked compare against an aligned power-of-two range — and the matching peripheral'sPSELxis the one that asserts. - The select is strictly one-hot (or all-zero). Across all
PSELx, at most one is high. If the offset matches a peripheral, exactly one is high; if it matches nothing, all are low anddecode_erris high. TwoPSELxhigh simultaneously is always a bug (overlapping windows) and is the property you assert against:$onehot0(psel). - The response mux must track the same select. The peripherals share
PREADY/PRDATA/PSLVERRback to the bridge, so the bridge muxes them — and it must select using the same decode that drovePSEL. If the mux select and thePSELdecode ever disagree, a read of peripheral A returns peripheral B's data. Select and mux are two consumers of one decode; they cannot diverge.
4. Real SoC implementation
In RTL the bridge decode is a small combinational block: translate to an offset, compare against each peripheral window to build a one-hot PSEL, detect the unmapped case, and mux the shared response from the selected peripheral. The FSM (13.7) gates PSEL/PENABLE in time; the code below is the spatial decode it consumes.
// Bridge address decode + PSEL fan-out + response mux for an APB peripheral cluster.
// Combinational decode; the FSM (separate) qualifies psel/penable with SETUP/ACCESS.
// The bridge owns one system-map window; peripherals live at fixed offsets inside it.
localparam int N = 3; // Timer, UART, GPIO
localparam logic [31:0] BRIDGE_BASE = 32'h4000_0000;
// Per-peripheral local base offsets inside the bridge window, all 4 KB, aligned.
localparam logic [31:0] OFF_TIMER = 32'h0000_0000; // PSEL[0]
localparam logic [31:0] OFF_UART = 32'h0000_1000; // PSEL[1]
localparam logic [31:0] OFF_GPIO = 32'h0000_2000; // PSEL[2]
localparam logic [31:0] P_SIZE = 32'h0000_1000; // 4 KB each (power of two)
localparam logic [31:0] WIN_SIZE = 32'h0001_0000; // 64 KB bridge window
// --- 1. TRANSLATE: upstream system address -> peripheral-space offset -> PADDR ---
// Subtract the bridge base. The offset IS the peripheral address; never pass haddr through.
wire in_window = (haddr & ~(WIN_SIZE-1)) == BRIDGE_BASE;
wire [31:0] offset = haddr - BRIDGE_BASE; // = peripheral-space offset
assign paddr = offset; // translated address to the APB bus
// A peripheral matches if the offset falls in its aligned power-of-two window.
// Masked compare: offset's high bits equal the window base (size is power of two).
function automatic logic hit(input logic [31:0] off, input logic [31:0] base);
return ((off & ~(P_SIZE-1)) == base);
endfunction
// --- 2. DECODE: build a ONE-HOT select, asserted only when in the bridge window ---
wire sel_timer = in_window && hit(offset, OFF_TIMER);
wire sel_uart = in_window && hit(offset, OFF_UART);
wire sel_gpio = in_window && hit(offset, OFF_GPIO);
wire [N-1:0] psel_oh = {sel_gpio, sel_uart, sel_timer}; // one-hot peripheral select
// The FSM turns these into the actual PSEL lines during SETUP+ACCESS (penable in ACCESS).
assign psel = psel_oh & {N{access_phase}}; // access_phase from the bridge FSM
// --- 3. DECODE ERROR: in the window but matching no peripheral (a gap) ---
// No PSEL asserts; the bridge itself completes the transfer with an error upstream.
wire decode_err = in_window && (psel_oh == '0); // unmapped offset -> error, not silence
// --- 4. RESPONSE MUX: select the addressed peripheral's reply (SAME decode source) ---
// Each peripheral drives its own pready_i/prdata_i/pslverr_i; the bridge muxes one back.
assign pready_mux = decode_err ? 1'b1 // decode error completes immediately
: sel_timer ? pready_i[0]
: sel_uart ? pready_i[1]
: sel_gpio ? pready_i[2]
: 1'b1; // no select -> don't hang the bus
assign prdata_mux = sel_timer ? prdata_i[0]
: sel_uart ? prdata_i[1]
: sel_gpio ? prdata_i[2]
: 32'h0;
assign pslverr_mux = decode_err ? 1'b1 // unmapped -> error response upstream
: sel_timer ? pslverr_i[0]
: sel_uart ? pslverr_i[1]
: sel_gpio ? pslverr_i[2]
: 1'b0;Two facts make this the canonical pattern. First, the upstream address is translated, not forwarded: PADDR is haddr − BRIDGE_BASE, the peripheral-space offset — the peripheral has no idea where the bridge sits in the system map, it only sees its own local space. Second, the PSEL decode and the response mux are driven from the same select signals (sel_timer/sel_uart/sel_gpio): the comparator that lights a PSELx is the comparator that steers the response mux, so a read can never select one peripheral and return another's data. The decode_err term closes the gap — an in-window unmapped offset asserts no PSEL, the bridge completes the transfer itself with an error, and the access is reported upstream rather than dropped (error propagation).
5. Engineering tradeoffs
The decode's design choices trade timing, area, flexibility, and how cleanly the unmapped case is handled. The table below is the bridge's APB sub-map — the peripheral layout under the window, plus the reserved row that must decode-error.
| Peripheral | Base offset (from BRIDGE_BASE) | Size | PSEL line | Notes |
|---|---|---|---|---|
| Timer | 0x0000 | 4 KB | PSEL0 | Aligned to its size; one-hot match |
| UART | 0x1000 | 4 KB | PSEL1 | Adjacent, no overlap |
| GPIO | 0x2000 | 4 KB | PSEL2 | Adjacent, no overlap |
| Watchdog | 0x3000 | 4 KB | PSEL3 | Adjacent, no overlap |
| Reserved / unmapped gap | 0x4000 … 0xFFFF | 48 KB | none | In-window but unmapped → decode_err → upstream error; no PSEL asserts |
And the structural decisions behind that map:
| Decision | Option A | Option B | When to choose which |
|---|---|---|---|
| Window matching | Power-of-two, aligned, masked compare | Arbitrary base ≤ addr < base+size range compare | Power-of-two/aligned — a masked compare is one gate level and structurally exclusive; range compares are larger and easy to overlap |
| Peripheral base addresses | Fixed localparam offsets | Per-peripheral programmable base-address registers (BARs) | Fixed for a static SoC (smaller, faster); BARs only when peripherals must be relocatable (rare in APB land) |
| Unmapped offset | Decode-error in the bridge (no PSEL) | Route to a dedicated default-slave peripheral | Bridge-internal decode-error is simplest; a default slave centralizes the error/logging if many bridges share it (illegal-address) |
| Response mux source | Same select signals as PSEL | A separately re-derived / registered select | Always the same source — re-deriving invites the select and the mux to diverge (the signature bug) |
| Map density | Tightly packed peripherals | Padded with reserved gaps for growth | Tight saves window space; padding (aligned to large powers of two) eases future peripherals and keeps the masked compare clean (allocation strategy) |
The throughline: keep peripheral windows aligned, power-of-two, and mutually exclusive so the decode is a cheap masked compare that is structurally one-hot, drive both PSEL and the response mux from that one decode, and give every in-window gap an explicit decode-error. The temptations that cause bugs are overlapping ranges and a second, divergent select for the mux.
6. Common RTL mistakes
7. Debugging scenario
The signature bridge-mapping bug is a read that returns the wrong peripheral's data because the response mux select was derived separately from the PSEL decode — the select that drives PSEL and the select that drives the mux drifted apart.
- Observed symptom: firmware reads the UART line-status register and gets a value that looks like GPIO state — a plausible but wrong number. Writes to the UART work (the byte appears on the wire), but every read returns data that belongs to a different peripheral. Single-peripheral systems never showed it; it only appears once the bridge fans out to several peripherals.
- Waveform clue: on the APB bus,
PADDRand the one-hotPSELare correct —PSEL1(UART) is the only one high,PADDRis the UART register offset. But the bridge's internalmux_selis2(GPIO) at the cyclePREADYis sampled, soPRDATAis wired fromprdata_i[2](GPIO) whilePSEL1selected the UART. The select that fanned outPSELand the select that steered the mux hold different values on the same edge. - Root cause: the response mux was driven from a separately computed select — a registered or re-decoded
mux_selthat was one peripheral stale (or recomputed from a different address source) rather than from the samesel_uart/sel_gpiowires that builtpsel_oh. The decode and the mux had two sources of truth, and under back-to-back accesses they disagreed, so the mux returned GPIO'sPRDATAfor a UART read. - Correct RTL: drive the response mux from the identical select signals that build the
PSELfan-out —assign prdata_mux = sel_timer ? prdata_i[0] : sel_uart ? prdata_i[1] : sel_gpio ? prdata_i[2] : 32'h0;— withsel_*being the very wires inpsel_oh. One decode, two consumers; never a secondmux_sel. (The same fix prevents the sibling bug of overlapping windows asserting twoPSELx— make windows aligned and mutually exclusive.) - Verification assertion: assert the select is one-hot and that the response mux matches the selected peripheral —
assert property (@(posedge pclk) disable iff(!presetn) $onehot0(psel));andassert property (@(posedge pclk) disable iff(!presetn) (psel[1] && penable && pready) |-> (prdata == prdata_i[1]));(one such read-data check per peripheral) — so the mux is proven to return the addressed peripheral'sPRDATA, not a neighbor's. - Debug habit: when a multi-peripheral bridge returns wrong read data while
PSEL/PADDRlook right, suspect a second select feeding the response mux. Trace the mux select back to its source: if it is anything other than the exact wires that drivePSEL, you have two sources of truth, and they will diverge under back-to-back traffic. One decode must drive both the fan-out and the mux.
8. Verification perspective
The bridge decode is verified as a routing function: prove the map is complete and mutually exclusive, prove exactly one peripheral is ever selected, prove the response comes from that peripheral, and prove every gap errors. The highest-value checks sit on the one-hot property and the mux/select agreement.
- Assert the select is one-hot every cycle.
$onehot0(psel)(zero or one bit high) is the core invariant: at most one peripheral is selected. Bind it for the whole simulation. A failure means overlapping decode windows — the bug that silently writes two peripherals. - Cover the full peripheral map and the gaps. Functional coverage must hit every peripheral's window (each
PSELxasserted at least once, ideally at its first, last, and a middle offset), and every reserved gap between and after peripherals (thedecode_errpath). A coverage model that only ever touches the mapped peripherals has a hole exactly where the bridge's error path lives — bins for "in-window-unmapped" are mandatory. - Inject decode errors deliberately. Drive in-window-but-unmapped offsets and verify the bridge asserts
decode_err, completes the transfer (assertsPREADYso nothing hangs), and returns an error upstream (PSLVERR/ the upstream error response) — never a strayPSEL. Also drive out-of-window addresses to confirm the bridge stays inert (it should never have been selected at the system level; the system decoder is checked separately). - Assert the response mux matches the selected peripheral. For each peripheral i, prove
psel[i] && penable && pready |-> (prdata == prdata_i[i]) && (pslverr == pslverr_i[i]). This is the property that fails for the wrong-mux bug: it ties the muxed response to the addressed peripheral's actual outputs, so a divergentmux_selis caught immediately rather than as a confusing firmware misread.
The point: verify the bridge decode as a complete, mutually-exclusive, gap-checked map — one-hot PSEL, full-map plus gap coverage, deliberate decode-error injection, and a mux-matches-selected-peripheral assertion — because a per-peripheral test that only reads mapped addresses will pass a bridge that still routes reads to the wrong peripheral and swallows unmapped accesses.
9. Interview discussion
"How does the bridge pick which APB peripheral to talk to?" is a common SoC-integration question, and the strong answer leads with decode, translate, one-hot, mux, error — not a wire list.
Frame it as the bridge being the address decoder for its APB cluster: it owns one window of the system map, subtracts BRIDGE_BASE to get the peripheral-space offset that becomes PADDR (translation, not forwarding the raw address), compares that offset against each peripheral's aligned power-of-two window, and asserts exactly one PSELx — a one-hot fan-out, every other PSEL low. Then deliver the depth that separates senior answers: the response mux must be driven from the same decode (or a read returns the wrong peripheral's data — the classic silent-corruption bug), and an in-window-but-unmapped offset must decode-error (no PSEL, the bridge completes the transfer itself, an error goes upstream — otherwise the bus hangs or the fault is swallowed). Distinguish this from the slave's internal decoder, which picks a register once PSEL is already on — different scope. Mentioning $onehot0(psel) as the verification invariant and that aligned power-of-two windows make overlap unrepresentable shows you've actually built one. Closing with "programmable base-address registers buy relocatability at the cost of a bigger, slower decode — most static APB SoCs use fixed offsets" signals breadth.
10. Practice
- Translate an address. With
BRIDGE_BASE = 0x4000_0000and the UART at offset0x1000, a CPU access targets0x4000_1004. State thePADDRthe peripheral sees and whichPSELxasserts. - Build the one-hot. For the four-peripheral map in the table, write the boolean for
PSEL2(GPIO) as a masked compare on the offset, and state the value ofPSEL0/PSEL1/PSEL3for that same access. - Find the gap. Given the map (peripherals at
0x0000/0x1000/0x2000/0x3000, 4 KB each, 64 KB window), name an in-window offset that maps to no peripheral and state what the bridge must do with it. - Spot the overlap. Two peripheral windows are declared
0x1000…0x1FFFand0x1800…0x27FF. State which offset asserts twoPSELx, and rewrite the second window so the map is mutually exclusive. - Wire the response. Explain, in terms of "one decode, two consumers," why the response mux select must be the same signal that drives
PSEL, and what symptom appears if they diverge.
11. Q&A
12. Key takeaways
- The bridge is the address decoder for its APB sub-system. It owns one window of the system map and, inside it, routes each access to exactly one peripheral.
- Translate, don't forward.
PADDR = upstream_addr − BRIDGE_BASE— the peripheral-space offset. The peripheral sees its own local space, never the raw system address. - The
PSELfan-out is one-hot. Exactly onePSELxasserts (or none);$onehot0(psel)is the invariant. Two-high means overlapping windows — keep peripheral windows aligned, power-of-two, and mutually exclusive so a masked compare is structurally exclusive. - The response mux must share the decode. Drive
PSELand thePREADY/PRDATA/PSLVERRmux from the same select signals — one decode, two consumers — or a read returns the wrong peripheral's data. - Every in-window gap must decode-error. An unmapped offset asserts no
PSEL; the bridge completes the transfer itself and returns an error upstream — never a hang, never a silent drop. - Verify it as a routing function: one-hot
PSEL, full-map plus gap coverage, deliberate decode-error injection, and a mux-matches-selected-peripheral assertion — a mapped-only read test will pass a bridge that still misroutes.