Skip to content

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 PADDR and one PENABLE, but each peripheral has its own PSEL. The bridge must assert exactly one PSELx — the addressed peripheral's — and hold every other PSEL low. 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 becomes PADDR.
  • 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 — PADDR was 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 the PSEL fan-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 PSEL fan-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 which PSELx to 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 becomes PADDR. The decode then compares offset against 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's PSELx is 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 and decode_err is high. Two PSELx high 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/PSLVERR back to the bridge, so the bridge muxes them — and it must select using the same decode that drove PSEL. If the mux select and the PSEL decode ever disagree, a read of peripheral A returns peripheral B's data. Select and mux are two consumers of one decode; they cannot diverge.
A block and map diagram: an upstream address enters a bridge that owns a window of the system map; the bridge decoder translates the address to an offset for PADDR, asserts exactly one of PSEL0/PSEL1/PSEL2 to select Timer/UART/GPIO, and muxes the addressed peripheral's PREADY/PRDATA/PSLVERR back; an in-window unmapped offset raises decode_err to an upstream error response instead of asserting any PSEL.
Figure 1 — the bridge as the address decoder for its APB sub-system. On the left, an upstream AHB/AXI address enters the bridge, which owns one window of the system map (BRIDGE_BASE … +64 KB). The bridge decoder does three things: (1) translate — subtract BRIDGE_BASE to form the peripheral-space offset that becomes PADDR; (2) decode — compare the offset to each peripheral's window and assert exactly one PSELx (one-hot, all others low); (3) response mux — select the addressed peripheral's PREADY/PRDATA/PSLVERR back. On the right, three mapped peripherals (Timer at PSEL0, UART at PSEL1, GPIO at PSEL2) each take their own PSEL plus the shared PADDR/PENABLE/PWRITE/PWDATA. An amber path shows an in-window but unmapped offset asserting no PSEL and raising decode_err, which becomes the upstream error response. The bottom banner frames in-window-and-mapped (one PSEL, response muxed), in-window-but-unmapped (decode_err, no PSEL), and out-of-window (never reaches this bridge).

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

PeripheralBase offset (from BRIDGE_BASE)SizePSEL lineNotes
Timer0x00004 KBPSEL0Aligned to its size; one-hot match
UART0x10004 KBPSEL1Adjacent, no overlap
GPIO0x20004 KBPSEL2Adjacent, no overlap
Watchdog0x30004 KBPSEL3Adjacent, no overlap
Reserved / unmapped gap0x4000 … 0xFFFF48 KBnoneIn-window but unmapped → decode_err → upstream error; no PSEL asserts

And the structural decisions behind that map:

DecisionOption AOption BWhen to choose which
Window matchingPower-of-two, aligned, masked compareArbitrary base ≤ addr < base+size range comparePower-of-two/aligned — a masked compare is one gate level and structurally exclusive; range compares are larger and easy to overlap
Peripheral base addressesFixed localparam offsetsPer-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 offsetDecode-error in the bridge (no PSEL)Route to a dedicated default-slave peripheralBridge-internal decode-error is simplest; a default slave centralizes the error/logging if many bridges share it (illegal-address)
Response mux sourceSame select signals as PSELA separately re-derived / registered selectAlways the same source — re-deriving invites the select and the mux to diverge (the signature bug)
Map densityTightly packed peripheralsPadded with reserved gaps for growthTight 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, PADDR and the one-hot PSEL are correct — PSEL1 (UART) is the only one high, PADDR is the UART register offset. But the bridge's internal mux_sel is 2 (GPIO) at the cycle PREADY is sampled, so PRDATA is wired from prdata_i[2] (GPIO) while PSEL1 selected the UART. The select that fanned out PSEL and 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_sel that was one peripheral stale (or recomputed from a different address source) rather than from the same sel_uart/sel_gpio wires that built psel_oh. The decode and the mux had two sources of truth, and under back-to-back accesses they disagreed, so the mux returned GPIO's PRDATA for a UART read.
  • Correct RTL: drive the response mux from the identical select signals that build the PSEL fan-out — assign prdata_mux = sel_timer ? prdata_i[0] : sel_uart ? prdata_i[1] : sel_gpio ? prdata_i[2] : 32'h0; — with sel_* being the very wires in psel_oh. One decode, two consumers; never a second mux_sel. (The same fix prevents the sibling bug of overlapping windows asserting two PSELx — 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)); and assert 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's PRDATA, not a neighbor's.
  • Debug habit: when a multi-peripheral bridge returns wrong read data while PSEL/PADDR look 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 drive PSEL, 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.
A two-case debug diagram: the correct top case shows decode and response mux agreeing on the UART so a read returns UART data; the first bug shows the mux select pointing at GPIO while PSEL selects the UART, returning the wrong peripheral's data; the second bug shows two overlapping decode windows so one offset asserts two PSEL lines and a write reaches two peripherals.
Figure 2 — two decode bugs. Top (green, correct): the decode asserts PSEL1 (UART) and the response mux selects the same source, so a UART read returns UART.PRDATA. Middle (red, bug 1): the mux select comes from a different source — PSEL1 selects the UART but mux_sel = 2 selects GPIO, so a UART read returns GPIO's PRDATA (wrong peripheral's data, with a stale/wrong PREADY/PSLVERR). Bottom (red, bug 2): the UART window (0x1000…0x1FFF) and a mis-sized GPIO window (0x1800…0x27FF) overlap, so offset 0x1800 matches both comparators and asserts PSEL1 and PSEL2 together — $onehot0 is violated and a write hits two peripherals. The fix for both: drive the mux select from the same one-hot decode that drives PSEL, and make peripheral windows mutually exclusive.

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 PSELx asserted at least once, ideally at its first, last, and a middle offset), and every reserved gap between and after peripherals (the decode_err path). 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 (asserts PREADY so nothing hangs), and returns an error upstream (PSLVERR / the upstream error response) — never a stray PSEL. 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 divergent mux_sel is 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

  1. Translate an address. With BRIDGE_BASE = 0x4000_0000 and the UART at offset 0x1000, a CPU access targets 0x4000_1004. State the PADDR the peripheral sees and which PSELx asserts.
  2. 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 of PSEL0/PSEL1/PSEL3 for that same access.
  3. 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.
  4. Spot the overlap. Two peripheral windows are declared 0x1000…0x1FFF and 0x1800…0x27FF. State which offset asserts two PSELx, and rewrite the second window so the map is mutually exclusive.
  5. 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 PSEL fan-out is one-hot. Exactly one PSELx asserts (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 PSEL and the PREADY/PRDATA/PSLVERR mux 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.