AMBA APB · Module 13
Wait-State Propagation
How an APB peripheral's wait state propagates upstream through the bridge to stall the AHB or AXI master — the back-pressure chain, the latency multiplication a CPU sees, and the unbounded-wait hang hazard that makes a bridge-level timeout essential.
A peripheral wait does not stay on the peripheral. When an APB slave holds PREADY low, the bridge has no buffer to hide behind — APB does not pipeline, so the bridge holds its upstream handshake deasserted too, and the back-pressure walks all the way to the CPU. This chapter is the back-pressure chain: how one peripheral wait cycle becomes one upstream wait cycle plus the bridge's own two-phase overhead, why an unbounded wait hangs the whole system, and why a serious bridge needs a timeout. The throughline: a bridge makes APB back-pressure visible upstream — a peripheral wait becomes a CPU stall — which is correct flow control but also a system-latency and hang risk.
1. Problem statement
The problem is carrying APB's wait state across the protocol boundary so a slow peripheral correctly stalls the upstream master — without losing the access, and without letting a non-responding peripheral hang the entire bus.
An APB peripheral asserts PREADY=0 to say "I'm not done — hold the transfer." On a standalone APB bus that wait stalls only the APB manager. But behind a bridge, the APB manager is the bridge, and the bridge is servicing a live AHB (or AXI) transaction on its other face. The bridge cannot tell the upstream master "done" until APB is actually done. So the wait has to propagate:
- The bridge has nowhere to put the wait. APB has no pipelining and no write buffer in the base bridge — there is exactly one in-flight transfer. While
PREADY=0, the bridge has no result to return, so it must hold the upstream handshake deasserted (HREADYlow on AHB; the response channel notVALIDon AXI) and stall the master cycle-for-cycle with the peripheral. - The cost multiplies into a CPU-visible latency. The CPU does not just pay the peripheral's wait cycles — it pays the bridge's
SETUP+ACCESSoverhead on top, every access, plus the wait. One slow register read can stall the core for many cycles, and the bridge serialises, so anything queued behind it waits too. - An unbounded wait is a system hang. APB allows a peripheral to hold
PREADYlow arbitrarily long. If it never assertsPREADY— a gated clock, a wedged FSM, a bad address that hits dead space — the bridge waits forever, the upstream master never retires, and the whole CPU (and everything serialised behind the bridge) locks up. The bridge needs a timeout to convert that hang into a recoverable bus error.
So the job is not "wire PREADY to HREADY." It is to propagate the wait faithfully, account for the latency the master pays, and bound the wait so a dead peripheral cannot kill the bus.
2. Why previous knowledge is insufficient
You already know each piece in isolation — but not the chain, and not its failure mode.
- 13.1 (the AHB-to-APB bridge) established the mapping; this chapter drills it as a chain. 13.1 said "
PREADY=0becomesHREADY=0" as one of two cross-boundary translations. That sentence hides a whole system story: how far the back-pressure travels, how much latency it costs the CPU, and what happens when the wait never ends. This chapter is that story. - Module 8's multi-wait / unbounded waits were a one-bus phenomenon. There,
PREADYlow for N cycles just extended the APB ACCESS phase — bounded or unbounded, it only stalled the APB manager. Across a bridge the same unbounded wait now stalls a CPU and freezes a system bus, so "unbounded is allowed" stops being a benign spec note and becomes a hang hazard that demands a timeout. - AHB wait states were the AHB slave's own choice. You learned that an AHB slave extends the data phase by holding
HREADYlow. Here the bridge is that AHB slave — but it does not choose its wait length; it inherits it from APB. The wait length is set on the far side of the boundary, which is new. - Error propagation is the sibling, owned next door. This chapter is wait propagation only — the peripheral is working, just slow. 13.5 covers what happens when the peripheral fails (
PSLVERR → HRESP=ERROR). Keep them separate: a wait stalls; an error aborts.
So the model to add is the propagation chain: a single peripheral wait, traced from PREADY low, through the bridge, to the exact cycle the CPU stalls — and the bound that keeps it from becoming a hang.
3. Mental model
The model: the bridge is a single-lane checkout with no holding area. The peripheral is the customer fumbling for change at the register; the bridge is the cashier; the upstream master is the next person in line — and the whole CPU is everyone behind them. The cashier cannot wave the next person forward until the current customer pays, and there is no second lane to absorb the delay. Every second the customer fumbles, the entire line waits. And if the customer freezes and never pays, the line never moves — unless the store has a rule: after a set time, void the sale and move on.
Three refinements make it precise:
- The wait propagates one-for-one, plus the bridge's overhead. While
PREADY=0, the bridge holds the upstream handshake deasserted for exactly those cycles. But the upstream master also pays the bridge'sSETUP+ACCESSframing on every access. So the CPU-visible latency isSETUP (1) + ACCESS (1) + N wait cycles— the wait is added to, not absorbed by, the bridge's own cost. This is the latency multiplication: a peripheral that waits N cycles makes the CPU wait N cycles and pay the two-phase tax. - AHB and AXI propagate the same wait through different signals. On AHB, the bridge holds
HREADYlow — it extends the data phase of the AHB transfer (data-phase extension) for the whole APB transfer. On AXI, there is no single ready line; instead the bridge simply does not assert the response — the read data on the R-channel (R-channel) or the write response on the B-channel (B-channel) does not goVALIDuntil APB completes. Different mechanics, identical effect: the master is stalled untilPREADYrises. - Unbounded propagation is a hang; the bridge must bound it. Because the wait length comes from the far side of the boundary, a peripheral that never finishes propagates an infinite stall upstream. The bridge cannot trust the peripheral to terminate, so a robust bridge runs a watchdog: count the wait cycles, and on reaching a limit, abort the APB transfer, release the upstream handshake, and return an error upstream — turning a fatal hang into a recoverable fault.
4. Real SoC implementation
In RTL the propagation is almost trivial — the upstream ready is just a function of the APB completion — but the robust version pairs it with a timeout watchdog so a non-responding peripheral cannot hang the bus. Below, the AHB side holds HREADY low until APB completes, and a counter aborts a stuck transfer and returns an error upstream.
// Wait propagation + timeout watchdog in an AHB-to-APB bridge.
// HREADY upstream is held low until APB completes; a watchdog bounds the wait.
typedef enum logic [1:0] {IDLE, SETUP, ACCESS} state_t;
state_t state, nstate;
localparam int WAIT_LIMIT = 1024; // max APB wait cycles before abort (tune per SoC)
logic [10:0] wait_cnt; // counts cycles spent waiting in ACCESS
logic timeout;
assign timeout = (wait_cnt == WAIT_LIMIT); // watchdog has expired
// --- count wait cycles while we sit in ACCESS with PREADY still low ---
always_ff @(posedge hclk or negedge hresetn)
if (!hresetn) wait_cnt <= '0;
else if (state == ACCESS && !pready) wait_cnt <= wait_cnt + 1'b1; // still waiting
else wait_cnt <= '0; // completed or left ACCESS
// --- the transfer ends when APB completes (pready) OR the watchdog fires (timeout) ---
wire apb_done = pready || timeout;
always_comb begin
nstate = state;
unique case (state)
IDLE: if (ahb_accept) nstate = SETUP;
SETUP: nstate = ACCESS;
ACCESS: if (apb_done) nstate = IDLE; // leave on real completion OR timeout
endcase
end
always_ff @(posedge hclk or negedge hresetn)
if (!hresetn) state <= IDLE; else state <= nstate;
// --- WAIT PROPAGATION: hold HREADY low for the whole APB transfer ---------
// HREADY is high only when idle, or when the APB transfer actually finishes.
// While PREADY is low, this stays low -> the AHB master is stalled.
assign hready_out = (state == IDLE) || (state == ACCESS && apb_done);
// --- the wait becomes an ERROR only if the watchdog aborted it -------------
// A clean completion is OKAY; a timeout returns ERROR so the CPU faults
// instead of hanging. (PSLVERR-driven errors are covered in 13.5.)
assign hresp = (state == ACCESS && timeout) ? HRESP_ERROR : HRESP_OKAY;
assign hrdata = prdata; // read data, valid on real completionTwo facts make this the canonical pattern. First, wait propagation is a pure consequence of having no buffer: hready_out is low for the entire ACCESS phase until apb_done, so every cycle the peripheral waits is a cycle the AHB master is stalled — the bridge does not, and cannot, absorb it. Second, the timeout is not optional in a hardened bridge: without it, pready low forever means hready_out low forever, and the CPU never retires the instruction — a permanent hang. The watchdog converts an unbounded APB wait (the multi-wait/unbounded concept) into a bounded upstream stall that ends in a clean bus error. On the AXI side the same logic appears differently: there is no hready_out to hold low — instead the bridge withholds RVALID/BVALID (and may withhold AWREADY/WREADY/ARREADY so the master cannot even launch the next access) until apb_done, then drives RRESP/BRESP = SLVERR on a timeout. The forward question — what HRESP=ERROR (or SLVERR) means and how the master reacts — is error propagation, the sibling chapter.
5. Engineering tradeoffs
The wait propagates the same way everywhere, but the signal it travels on, the latency it costs, and the hang risk differ by upstream protocol — and the timeout policy is a real design knob.
| Upstream | How the wait propagates (PREADY=0 →) | Upstream stall mechanism | Latency the master pays | Hang risk / mitigation |
|---|---|---|---|---|
| AHB | Bridge holds HREADY low | Data-phase extension: the AHB transfer's data phase is stretched, master frozen | SETUP + ACCESS + N cycles, surfaced as AHB wait states | HREADY low forever ⇒ master never retires; bound with a watchdog, return HRESP=ERROR on timeout |
| AXI (read) | Bridge withholds RVALID | R-channel handshake never completes; master's read outstanding stays open | SETUP + ACCESS + N cycles before RVALID; may also stall ARREADY so no new reads launch | RVALID never asserted ⇒ read hangs; timeout drives RRESP=SLVERR to release it |
| AXI (write) | Bridge withholds BVALID (and may hold WREADY low) | B-channel response never returned; write transaction never acknowledged | SETUP + ACCESS + N cycles before BVALID | BVALID never returned ⇒ write hangs; timeout drives BRESP=SLVERR |
| Timeout limit | WAIT_LIMIT small | Fails fast — quick recovery, but may abort a legitimately slow peripheral | n/a | Too small ⇒ false aborts; too large ⇒ long visible stall before recovery |
The throughline: the wait is correct flow control on every upstream — a slow peripheral must stall its master — but on every upstream it is also a latency cost (the master pays the bridge's two-phase overhead plus the wait) and a hang risk (an unterminated wait propagates an infinite stall). AHB carries it on one HREADY line by stretching the data phase; AXI carries it by simply not asserting the response VALID. The single defence against the hang is a bridge-level timeout, and its limit trades recovery speed against false aborts.
6. Common RTL mistakes
7. Debugging scenario
The signature wait-propagation bug is a whole-SoC hard hang caused by an unbounded peripheral wait with no bridge timeout — a peripheral never asserts PREADY, so HREADY stays low forever and the CPU never retires the instruction.
- Observed symptom: the entire SoC freezes. The CPU stops executing — no further instructions retire, no interrupts service, the watchdog (if any) eventually resets the chip. It is reproducible: it happens right after software accesses one specific peripheral, often one that was just clock-gated or power-gated for low-power, then poked without re-enabling its clock.
- Waveform clue: on the APB face,
PSELandPENABLEare high (the bridge is inACCESS) butPREADYis stuck low forever — and the peripheral'sPCLKis flatlined (gated off), so itsPREADYlogic never even runs. On the AHB face,HREADYis low and never rises;HTRANSis frozen and the AHB master's address phase is stalled indefinitely. There is no error, no completion — just a flat stall on both faces. - Root cause: the bridge propagated the wait correctly (
HREADYlow whilePREADYlow) but had no upper bound on the wait. The peripheral's clock was gated, so it could never assertPREADY; with no timeout, the bridge sat inACCESSforever, holdingHREADYlow forever, so the AHB master never completed its data phase and the CPU never retired the access — a permanent, system-wide hang. - Correct RTL: add a watchdog that bounds the wait and aborts a stuck transfer —
if (state==ACCESS && !pready) wait_cnt <= wait_cnt + 1; ... wire apb_done = pready || (wait_cnt==WAIT_LIMIT);— releaseHREADYonapb_doneand driveHRESP=ERRORwhen the limit is hit, so a non-responding peripheral becomes a recoverable bus fault instead of a hang. (Belt-and-braces: also keep peripheral clocks enabled while their address space is accessible.) - Verification assertion: assert that the bridge always completes the upstream handshake within a bounded number of cycles once it starts a transfer —
assert property (@(posedge hclk) disable iff(!hresetn) (state==ACCESS) |-> ##[1:WAIT_LIMIT+1] (hready_out));— i.e. once in ACCESS,HREADYmust rise withinWAIT_LIMIT+1cycles (by real completion or by timeout). A liveness/s_eventuallyform catches the "never rises" hang directly. - Debug habit: when an SoC hard-hangs on a peripheral access, look at the bridge's downstream handshake first. If
PSEL/PENABLEare asserted andPREADYis stuck low — especially with a deadPCLK— you have an unbounded wait propagating upstream. Then ask the structural question every bridge must answer: is there a timeout? A bridge that propagates waits but cannot bound them turns any wedged peripheral into a chip-killer; the fix is always a watchdog plus an error-return path.
8. Verification perspective
Wait propagation is verified as a liveness property plus latency accounting — the classic per-protocol monitors will happily pass a bridge that hangs on an unterminated wait, so the boundary checks must be explicit.
- Assert the back-pressure mapping holds every cycle. While the APB transfer is incomplete, the upstream master must be stalled: assert that
PREADY=0during the bridge'sACCESSkeepsHREADYlow ((state==ACCESS && !pready) |-> !hready_out), and on AXI that the corresponding response stays non-VALID. The dual is just as important —HREADYmust not rise beforeapb_done, or the master samples stale data. This pins the wait to the boundary so a coverage hole can't let it leak early. - Assert bounded upstream completion (the anti-hang check). This is the property a pure protocol monitor misses: once the bridge starts a transfer it must finish within a bound —
(state==ACCESS) |-> ##[1:WAIT_LIMIT+1] hready_out, or as liveness,s_eventually hready_out. Pair it with a check that a timeout producesHRESP=ERROR(not a silent completion) so the abort is observable to the master. - Cover the latency distribution: 0, 1, and N waits — and the timeout firing. Functional coverage must include an APB transfer with zero wait states (the fast path), one wait (the minimal stall), and N waits up to the timeout limit (the slow path), so the propagation is exercised across its whole range. Crucially, add a bin for the timeout actually firing — drive a peripheral that never asserts
PREADYand confirm the watchdog aborts and the master recovers. A bridge tested only with fast, always-PREADYperipherals has its biggest hazard — the hang — completely uncovered. - Add hang detection to the test environment itself. Beyond formal assertions, the simulation should have a global activity watchdog: if no upstream transaction retires for far longer than
WAIT_LIMIT, fail the test with a hang report rather than letting it time out silently. This catches not just the modelled timeout path but any unmodelled deadlock in the propagation logic.
The point: verify the wait as a bounded liveness property — it must propagate fully (master stalled while the peripheral waits) and terminate (within WAIT_LIMIT, by completion or by a visible error) — and cover the 0/1/N wait distribution plus the timeout-fires case. The per-protocol monitors check the handshake shape; only these boundary checks catch the hang.
9. Interview discussion
"What happens when an APB peripheral is slow — and what if it never responds?" is a favourite SoC-integration question, and the strong answer traces the chain and names the hang hazard, not just "PREADY becomes HREADY."
Lead with the mechanism: the bridge has no buffer, so a peripheral wait propagates straight upstream. While PREADY=0, the bridge holds the upstream handshake deasserted — HREADY low on AHB (stretching the data phase), or the R/B-channel response simply not VALID on AXI — so the master is stalled cycle-for-cycle with the peripheral. Then deliver the depth that separates senior answers: the latency multiplication — the CPU pays the peripheral's N wait cycles plus the bridge's SETUP+ACCESS overhead on every access, and because the bridge serialises, anything behind that access waits too (head-of-line blocking). Then raise the hang hazard yourself: an unbounded wait — a gated clock, a wedged peripheral — propagates an infinite stall, hard-hanging the CPU and everything behind the bridge, so a hardened bridge needs a timeout watchdog that aborts the stuck transfer and returns a bus error. Closing notes that mark breadth: "AHB and AXI propagate the same wait — AHB on HREADY, AXI by withholding the response VALID," and "this is wait propagation; the error response the timeout returns is the sibling problem." That arc — propagate, multiply, bound — is exactly what an interviewer is listening for.
10. Practice
- Trace the chain. For one AHB read to a peripheral that inserts three APB wait states, mark on a timeline when
PREADYgoes low, whenHREADYgoes low, and the exact cycle both rise — and count the AHB wait states the CPU sees. - Cost the access. With a two-cycle (
SETUP+ACCESS) bridge and N peripheral wait cycles, write the CPU-visible latency as a formula, then evaluate it for N = 0, 1, and 4. - Map AHB vs AXI. State, for the same
PREADY=0, what signal the bridge manipulates on an AHB upstream versus an AXI read upstream, and confirm the master-visible effect is identical. - Design the bound. Sketch the watchdog: which state it counts in, what it counts, what it does at the limit, and what response it returns upstream — and explain why the limit trades recovery speed against false aborts.
- Reason about head-of-line blocking. Explain why a single slow peripheral stalls not just its own access but everything queued behind the bridge, and why that follows from APB being unpipelined.
11. Q&A
12. Key takeaways
- A peripheral wait propagates straight upstream. The bridge has no buffer — APB doesn't pipeline — so while
PREADY=0the bridge holds its upstream handshake deasserted and the master is stalled cycle-for-cycle with the peripheral. - The latency multiplies. The CPU pays the N peripheral wait cycles plus the bridge's
SETUP+ACCESSoverhead on every access, and because the bridge serialises, everything queued behind that access waits too (head-of-line blocking). - AHB and AXI carry the same wait differently. AHB holds
HREADYlow (data-phase extension); AXI withholds the R/B-channel responseVALID. Identical effect — the master stalls — different signal. - An unbounded wait is a hang hazard. A peripheral that never asserts
PREADYpropagates an infinite stall, hard-hanging the CPU and everything behind the bridge. The spec allows unbounded waits; a system cannot. - A timeout makes propagation safe. A watchdog that bounds the wait, aborts a stuck transfer, and returns a bus error converts a non-responding peripheral from a fatal lock-up into a recoverable fault.
- Verify it as bounded liveness. Assert the wait propagates fully (master stalled while the peripheral waits) and terminates within
WAIT_LIMIT(by completion or visible error), and cover the 0/1/N wait distribution plus the timeout-fires case — the hang is the corner per-protocol monitors miss.