Skip to content

AMBA APB · Module 13

Protocol-Conversion Mechanics

The three universal mappings every bus-to-APB bridge performs — handshake mapping, address/phase capture, and data-path (byte-strobe) routing — around a common APB transfer engine. Learn the pattern once and any specific bridge (AHB or AXI) is an instantiation with a swappable upstream adapter.

You have now seen two concrete bridges in detail: the AHB-to-APB bridge and the AXI-to-APB bridge. They look different — one collapses a two-phase pipeline, the other demultiplexes five channels — but if you squint they are doing the same three things. This chapter extracts that pattern. The single idea to carry: every bus-to-APB bridge is the same three mappings — handshake, address/phase, and data-path — wrapped around one fixed APB transfer engine, with only the upstream adapter changing. Learn the three mappings and the capture discipline once, and any specific bridge becomes an instantiation you can read, design, debug, and defend on sight.

1. Problem statement

The problem is finding the protocol-conversion pattern that is common to every bus-to-APB bridge, so that you reason about one set of mechanics instead of memorising each bridge as a special case.

A bridge takes a transaction expressed in one bus's grammar and re-expresses it in APB's grammar. Strip away the specifics of the upstream bus and exactly three translation problems remain — and they are always the same three:

  • Handshake mapping. The upstream bus flow-controls in its own dialect — AHB with HREADY, AXI with per-channel VALID/READY. APB flow-controls with the SETUP → ACCESS → PREADY sequence. The bridge must convert the upstream "go" into an APB transfer launch, hold the upstream stalled until PREADY completes, and convert the APB completion back into the upstream's "done."
  • Address/phase mapping. The upstream address arrives in a phase the bridge does not control — pipelined ahead (AHB) or on a separate channel that handshakes independently of the data (AXI). APB needs that address held stable across its whole two-phase transfer. The bridge must capture the upstream address (and write attributes) the instant it accepts the request, then drive APB from the capture.
  • Data-path mapping. Write data must be routed downstream with its byte strobes (WSTRB → PSTRB), and read data routed upstream — each aligned to the correct phase so the slave samples valid data and the master receives valid data. Validity does not align by accident; the bridge must place each beat in the right phase.

The job, abstractly, is therefore: an upstream adapter turns the bus's transaction into a captured, protocol-neutral request, a fixed APB transfer engine drives that request as one APB transfer, and a response adapter returns the result. The APB side is a fixed target; only the upstream adapter varies.

2. Why previous knowledge is insufficient

You have two fully worked bridges, but each was taught as a specific block, and that is exactly what this chapter must transcend.

  • The AHB-to-APB chapter taught one upstream dialect. 13.1 framed the bridge as an AHB slave that captures the pipelined address phase and stalls HREADY. That is the handshake and address mappings — but specialised to AHB's pipeline (AHB's two-phase pipeline, what HREADY means). You have not yet abstracted "capture the pipelined address" into the general rule "capture whatever phase the upstream uses."
  • The AXI-to-APB chapter taught a different upstream dialect. 13.2 framed the same job around AXI's five independent channels and VALID/READY handshake — capturing AW/AR + W into a request and serialising onto APB. Seen alone it looks like a new problem; seen beside 13.1 it is the same three mappings with a different adapter. Without putting them side by side you cannot see that the APB transfer engine in both is byte-for-byte the same.
  • You have not yet named the invariant. Neither prior chapter said out loud: the APB side never changes; only the upstream adapter does. That invariant is the whole point of an abstraction chapter — it is what lets you build a reusable APB engine, write one bridge checker, and treat a new upstream bus as "write a new adapter," not "design a new bridge."
  • The two hard mappings are deferred to dedicated chapters. The depth of the handshake mapping — how an APB wait actually propagates upstream beat by beat — lives in wait-state propagation (13.4). The depth of the error mapping — how PSLVERR becomes the upstream error response — lives in error propagation (13.5). This chapter establishes the structure; those two drill the mechanics.

So the model to add is the abstraction itself: the three mappings, the captured generic request, and the fixed-engine / swappable-adapter split that makes any specific bridge an instantiation.

3. Mental model

The model: a bridge is a translation pipeline with a universal core and a swappable front-end — like a power adapter whose internal regulator is always the same and only the wall plug changes per country. The wall plug (the upstream adapter) speaks AHB or AXI; the regulator (the APB transfer engine) is identical everywhere; in between sits a normalised "DC bus" — the captured generic request — that hides which plug was used.

Three refinements make it precise:

  • Normalise to a captured generic request. Whatever the upstream bus, the adapter reduces an accepted transaction to one protocol-neutral record — {addr, write, wdata, wstrb} — and freezes it. This capture is the linchpin: it is where the address/phase mapping happens, and it decouples the upstream's pipelining or channelisation from APB's serial cadence. After capture, the downstream engine never sees AHB or AXI — only a stable request.
  • The APB transfer engine is fixed and reusable. It runs the unchanging IDLE → SETUP → ACCESS FSM, drives PSEL/PENABLE/PADDR/PWRITE/PWDATA/PSTRB from the captured request, and samples PREADY/PRDATA/PSLVERR. It does not know or care what produced the request. Because APB is the fixed two-phase target, this engine is byte-for-byte identical across the AHB and AXI bridges — that is why it can be reused.
  • The three mappings are the only moving parts. Handshake: upstream "go" → APB launch, and PREADY → upstream "done," with the upstream stalled in between. Address/phase: capture the upstream address into the request, immune to whatever the upstream phase does next. Data-path: wdata/wstrbPWDATA/PSTRB downstream, PRDATA → read data upstream, each in the right phase. Every bridge is exactly these three mappings around the fixed engine.
A four-stage block diagram of a generic bus-to-APB bridge: a swappable upstream adapter (AHB or AXI) feeding a captured protocol-neutral request {addr, write, wdata, wstrb}, into a fixed APB transfer engine running IDLE/SETUP/ACCESS and driving PSEL/PENABLE/PADDR/PWRITE/PWDATA/PSTRB, then a response adapter mapping PRDATA/PREADY/PSLVERR back upstream; three labelled bands name the handshake, address/phase, and data-path (WSTRB→PSTRB) mappings.
Figure 1 — the generic bus-to-APB bridge as four stages, with the three universal mappings called out. On the left, the upstream adapter is a swappable slot that can hold an AHB adapter (translating the HREADY pipeline, capturing the pipelined address phase) or an AXI adapter (translating VALID/READY across five channels, capturing AW/AR plus W); this stage performs the handshake mapping and the address-phase capture. In the centre-left, the captured generic request holds a protocol-neutral record {addr, write, wdata, wstrb} frozen stable for the whole downstream transfer — the capture discipline that hides the upstream's pipelining or channelisation. In the centre-right, the fixed APB transfer engine runs the unchanging IDLE → SETUP → ACCESS FSM, drives PSEL/PENABLE/PADDR/PWRITE/PWDATA/PSTRB, and samples PREADY/PRDATA/PSLVERR; this engine is identical for every upstream and is the bridge's reusable core. On the right, the response adapter routes PRDATA upstream as read data and maps PREADY/PSLVERR back into the upstream's completion and error semantics. The three bands across the bottom name the universal mappings: handshake, address/phase, and data-path including WSTRB→PSTRB. The teaching point is that the upstream adapter varies while the APB side is a fixed target, so the three mappings learned once let you read any specific bridge as an instantiation.

4. Real SoC implementation

In RTL the abstraction is literal: an upstream adapter produces a captured generic request struct, a fixed APB transfer engine consumes it, and a response adapter maps the result back. Swapping AHB for AXI swaps only the adapter — the engine below is untouched.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ============================================================================
// Generic bus-to-APB bridge: swappable upstream adapter -> FIXED APB engine.
// The engine never knows which upstream produced the request.
// ============================================================================
 
// --- Protocol-neutral captured request: the normalised "DC bus" ---------------
typedef struct packed {
  logic [31:0] addr;     // captured upstream address  (address/phase mapping)
  logic        write;    // 1 = write, 0 = read
  logic [31:0] wdata;    // captured write data        (data-path mapping)
  logic [3:0]  wstrb;    // captured byte strobes -> PSTRB (data-path mapping)
} apb_req_t;
 
// --- Upstream adapter (THIS is the only part that varies per bus) -------------
// Its whole job: do the HANDSHAKE mapping (accept the upstream "go"),
// the ADDRESS/PHASE mapping (CAPTURE addr/write before the phase moves on),
// and present a stable apb_req_t + a one-cycle 'req_valid' pulse to the engine.
// For AHB: req_valid = hsel & hready_in & htrans[1]; capture haddr/hwrite/hwdata.
// For AXI: req_valid = (awvalid & wvalid) | arvalid; capture ax_addr/wdata/wstrb.
apb_req_t req;          // <-- driven by the adapter, held stable until 'done'
logic     req_valid;    // <-- one transfer offered
logic     done;         // <-- engine pulses this when PREADY completes
// (the adapter de-asserts upstream ready / advances its channel on 'done')
 
// --- FIXED APB transfer engine: identical for AHB, AXI, or any future bus ------
typedef enum logic [1:0] {IDLE, SETUP, ACCESS} apb_state_t;
apb_state_t state, nstate;
 
always_comb begin
  nstate = state;
  unique case (state)
    IDLE:   if (req_valid) nstate = SETUP;     // HANDSHAKE: upstream "go" -> launch
    SETUP:                 nstate = ACCESS;     // SETUP is always exactly one cycle
    ACCESS: if (pready)    nstate = IDLE;       // hold here until APB completes
  endcase
end
always_ff @(posedge pclk or negedge presetn)
  if (!presetn) state <= IDLE; else state <= nstate;
 
// APB outputs driven straight from the CAPTURED request (never live upstream sigs)
assign psel    = (state == SETUP) || (state == ACCESS);
assign penable = (state == ACCESS);
assign paddr   = req.addr;                       // address/phase mapping result
assign pwrite  = req.write;
assign pwdata  = req.wdata;                       // data-path: write data downstream
assign pstrb   = req.wstrb;                       // data-path: WSTRB -> PSTRB, 1:1
 
// Completion pulse the adapter waits on (HANDSHAKE mapping, return direction)
assign done    = (state == ACCESS) && pready;
 
// --- Response adapter: map the APB result back into the upstream's grammar -----
// Read data goes up; PSLVERR becomes the upstream error response.
// (The engine just exposes the raw APB result; the adapter re-dresses it.)
assign rdata_up = prdata;                         // data-path: read data upstream
assign err_up   = done && pslverr;                // -> HRESP=ERROR / RRESP/BRESP=SLVERR

Two facts make this the canonical pattern. First, the captured request is the abstraction boundary: above it the adapter speaks AHB or AXI; below it the engine speaks only APB. Because the engine reads exclusively from req (never from live upstream signals), it is literally the same module in both bridges — which is the whole claim of this chapter made concrete. Second, the three mappings are localised to three places: the handshake mapping is the req_valid/done contract between adapter and engine (its depth is wait-state propagation); the address/phase mapping is the capture inside the adapter; the data-path mapping is wstrb → pstrb plus prdata routing. Change the upstream and you rewrite only the adapter — the engine and the data-path 1:1 strobe map are invariant.

5. Engineering tradeoffs

Each mapping is realised differently depending on the upstream bus, but the structure of the choice is identical. This table is the heart of the abstraction: the three mappings across an AHB upstream, an AXI upstream, and the fixed APB downstream.

MappingAHB upstreamAXI upstreamAPB downstream (fixed)
HandshakeStall by holding HREADY=0 from launch until PREADY; accept on HSEL & HREADY & HTRANS[1]Hold AWREADY/ARREADY/WREADY low until the engine can take the beat; signal completion on BVALID/RVALID; accept when AW+W (or AR) handshakeSETUP → ACCESS, wait for PREADY; one transfer at a time, no pipeline — the engine's req_valid/done contract
Address/phaseCapture the pipelined address phase (HADDR/HWRITE/HSIZE) on accept — it advances to the next transfer otherwiseCapture the channel address (AWADDR/ARADDR) when its channel handshakes, independently of the data beatPADDR/PWRITE held stable across both phases — driven from the captured req, never live upstream
Data-pathCapture HWDATA in the AHB data phase; route HRDATA up; map AHB byte lanes / HSIZE to PSTRBCapture WDATA/WSTRB from the W channel; route RDATA up; WSTRB → PSTRB directly (both are byte strobes)PWDATA/PSTRB driven in SETUP/ACCESS from req; PRDATA sampled at PREADY; PSTRB is the same byte-strobe idea on both sides

The throughline: read the table by column and you see two different upstream adapters; read it by row and you see three problems that are conceptually identical regardless of upstream. The APB column never changes — that is the fixed engine. Designing a bridge for a new upstream bus is filling in a fourth column for that bus, against the same three rows; you never touch the APB column or the row definitions.

6. Common RTL mistakes

7. Debugging scenario

The signature generic-mapping failure is a broken data-path mapping that drops the byte strobe — the bridge launches a clean APB transfer with the right address and handshake, but PSTRB does not carry WSTRB, so a sub-word write corrupts the bytes the master never meant to touch.

  • Observed symptom: a driver does a byte write to a packed peripheral register — say it updates only the control byte of a 32-bit register — and the other three bytes of the same register come back changed on the next read. Full-word writes work perfectly; only sub-word (byte/half-word) writes corrupt neighbouring fields. It reproduces on both the AHB and AXI bridges, which points at the shared engine, not an upstream adapter.
  • Waveform clue: on the upstream side WSTRB (or the AHB lane/HSIZE encoding) shows 0010 — one byte enabled. On the APB side, in the same ACCESS phase, PSTRB reads 1111. Every other signal is correct: PADDR is the right register, PSEL/PENABLE/PREADY sequence cleanly, PWDATA carries the byte in the right lane. Only PSTRB disagrees with WSTRB.
  • Root cause: the data-path mapping dropped the strobe. The engine drove pstrb = '1 (a hard-tied full-word strobe) instead of pstrb = req.wstrb, so the slave treated every transfer as a full 32-bit write and clobbered the three lanes the master left disabled with whatever the unused PWDATA lanes held. The handshake and address mappings were flawless, which is exactly why no protocol checker fired.
  • Correct RTL: carry the captured strobe through the engine unchanged — assign pstrb = req.wstrb; — and ensure the adapter populates req.wstrb from the real upstream strobe (WSTRB on AXI; the byte-lane/HSIZE-derived strobe on AHB). The byte-strobe map is part of the fixed engine's contract, not an upstream afterthought.
  • Verification assertion: assert the strobe is mapped 1:1 on every write, and that a sub-word write never asserts lanes the master did not — assert property (@(posedge pclk) disable iff(!presetn) (psel && pwrite && penable) |-> (pstrb == req.wstrb)); paired with a byte-accurate scoreboard check that disabled lanes of the target register are $stable across the write.
  • Debug habit: when a write succeeds (right address, clean handshake, no error) but neighbouring bytes corrupt, suspect the data-path mapping, specifically the byte strobe — not the handshake or address. Diff WSTRB against PSTRB in the same phase: if a sub-word strobe became all-ones, the mapping dropped it. A clean handshake with corrupted data is the fingerprint of a data-path bug, which per-protocol handshake monitors will pass right over.
Two stacked timelines of one sub-word write across a bus-to-APB bridge. Top correct case in green: WSTRB=0010 maps to PSTRB=0010, only byte 1 of the register updates, other bytes preserved. Bottom buggy case in red: WSTRB=0010 but PSTRB is tied to 1111, so all four bytes are overwritten and three are clobbered; handshake and address signals are identical between the two cases, isolating the bug to PSTRB.
Figure 2 — the data-path mapping bug, on one sub-word write (master writes only byte lane 1, WSTRB=0010). Top (correct, green): the bridge carries the strobe, PSTRB = WSTRB = 0010, the APB slave updates only byte 1, and B3/B2/B0 of the target register are preserved. Bottom (bug, red): the data-path mapping dropped the strobe and PSTRB is hard-tied to 1111, so the slave performs a full-word write and overwrites all four bytes — B3, B2, and B0 are clobbered with stale/zero contents of the unused write-data lanes, silently corrupting the register. Both timelines share identical SETUP/ACCESS phases, the same PREADY completion edge, and identical address and handshake signals; only PSTRB differs, isolating the defect to the single byte-strobe mapping. The teaching point is that WSTRB→PSTRB is part of the data-path mapping and must be carried faithfully — a dropped or hard-wired strobe turns a legal sub-word write into byte corruption that no handshake check catches.

8. Verification perspective

A bridge is verified as two compliant interfaces plus the three mappings between them — and the abstraction pays off here: because the mappings are universal, you write one reusable checker and re-target it at any upstream.

  • Verify each mapping independently, not just "the transfer worked." A transfer can pass end-to-end while a mapping is subtly broken. (1) Handshake mapping: assert the upstream is stalled for the full APB transfer and released only at PREADY, and that the engine never launches a second APB transfer before the first completes. (2) Address/phase mapping: assert PADDR/PWRITE are $stable across the whole APB transfer and equal the captured request. (3) Data-path mapping: assert PSTRB == req.wstrb and PWDATA == req.wdata on writes, and that PRDATA is sampled and forwarded at PREADY on reads. Splitting the checks isolates which mapping failed when a directed test corrupts data.
  • Assert captured-request stability — the capture discipline is testable. The address/phase mapping's correctness reduces to one property: once the engine accepts a request, the request it drives onto APB does not change until done. assert property (@(posedge pclk) disable iff(!presetn) (state != IDLE) |-> $stable(req)); This single property catches the entire class of "wired live upstream signal through instead of capturing" bugs, regardless of upstream — it is the abstraction's payoff in one line.
  • Build a reusable, upstream-agnostic bridge checker. Because the engine consumes only the generic request and produces only APB outputs, a checker bound to the req → APB boundary works unchanged for the AHB bridge, the AXI bridge, and any future upstream. It needs an abstract transaction model ({addr, write, wdata, wstrb}) and the APB monitor; the upstream-specific VIP sits above it. One scoreboard, re-targeted by swapping the upstream driver, verifies every bridge's core — exactly mirroring the RTL's swappable-adapter / fixed-engine split.

The point: verify the three mappings separately, assert the captured request is stable, and write the boundary checker against the generic request so it is reusable — the universality of the mappings is what makes one verification environment cover every bus-to-APB bridge.

9. Interview discussion

"What's common to an AHB-to-APB and an AXI-to-APB bridge?" — or "design a generic bridge to APB" — separates candidates who memorised two bridges from those who understand the pattern.

Lead with the structure: every bus-to-APB bridge is an upstream adapter producing a captured generic request, a fixed APB transfer engine, and a response adapter — and the APB side is a fixed target while only the adapter varies. Then name the three mappings crisply: handshake (upstream flow-control ↔ APB SETUP/ACCESS/PREADY, with the upstream stalled until completion), address/phase (capture the upstream address — pipelined for AHB, channelised for AXI — and hold it stable across APB's two phases), and data-path (write data + WSTRB → PSTRB downstream, read data upstream, each aligned to the right phase). The depth that marks a senior answer: explain why capture is mandatory (the upstream phase moves before APB completes), point out that WSTRB → PSTRB is one byte-strobe mapping not two problems, and state that the APB engine must read only the captured request so it is reusable. Close with the payoff — "because the engine and the mappings are invariant, you write the APB core and the bridge checker once; a new upstream is a new adapter, not a new bridge" — and note that the hard parts, wait-state and error propagation, are where the handshake and error mappings get their full depth. That framing shows you can abstract, which is exactly what the question tests.

10. Practice

  1. Name the four stages. For a generic bus-to-APB bridge, list the four pipeline stages in order and state which one is fixed across all upstreams and which one varies.
  2. List the three mappings. Write the three universal mappings and, for each, give the upstream side (pick AHB or AXI) and the APB side.
  3. Justify the capture. Explain, in terms of the upstream phase, why the address/phase mapping must capture rather than wire through — and give one concrete way it breaks if you don't.
  4. Map the strobe. State what WSTRB becomes on the APB side, why it is one mapping rather than two, and what corrupts if the bridge ties the downstream strobe to all-ones.
  5. Reuse the checker. Explain why a checker bound to the captured-request → APB boundary works unchanged for both the AHB and AXI bridges, and what part of the environment you do have to swap per upstream.

11. Q&A

12. Key takeaways

  • Every bus-to-APB bridge is one pattern: an upstream adapter → a captured generic request {addr, write, wdata, wstrb} → a fixed APB transfer engine → a response adapter. The APB side is a fixed target; only the upstream adapter varies.
  • There are exactly three universal mappings: handshake (upstream flow-control ↔ APB SETUP/ACCESS/PREADY), address/phase (capture the upstream address and hold it stable across APB's two phases), and data-path (WSTRB → PSTRB and write data down, read data up, each aligned to the right phase).
  • Capture is the linchpin. The upstream address lives in a phase you don't control — pipelined (AHB) or channelised (AXI) — so it must be registered into the generic request; the engine drives APB only from the capture, never from live upstream signals. This both prevents corruption and enables reuse.
  • WSTRB → PSTRB is one byte-strobe mapping, not two problems. Carry it 1:1; dropping it (tying PSTRB high) silently turns sub-word writes into byte corruption that no handshake checker catches.
  • The engine is reusable because it reads only the generic request. Any upstream-awareness in the engine is a leak; the abstraction is what lets you write the APB core, the stability/strobe assertions, and the bridge checker once and re-target them per upstream.
  • The specific bridges are instantiations. AHB-to-APB and AXI-to-APB differ only in the adapter; the hard mappings get their full depth in wait-state propagation and error propagation.