AMBA APB · Module 8
Transfer-Extension Mechanics
The cycle-by-cycle mechanics of how PREADY=0 holds the access phase — what the manager freezes (PADDR, PWDATA, PWRITE, PSEL, PENABLE) and why, what it re-samples each held edge, and the exact completion condition. An extended transfer is one transfer stretched, not many.
You already know the manager samples PREADY once per access cycle, and that a clean PREADY value is a timing problem in its own right. This chapter is about what the held cycles actually do once PREADY is low. The single idea to carry: an extended transfer is not several transfers — it is one transfer stretched. While PENABLE is high and PREADY is low, the manager freezes the entire access-phase bus (PADDR, PWRITE, PWDATA, PSEL, PENABLE, PSTRB, PPROT), re-presents it byte-for-byte every clock, and re-samples PREADY on each rising edge — until the first edge where PSEL & PENABLE & PREADY all hold, which is the instant the access completes.
1. Problem statement
The problem is holding a single access phase frozen, correctly, for an unknown number of clocks — keeping the entire bus stable and PENABLE asserted while re-sampling PREADY each edge, so that whenever the subordinate finally raises PREADY, it completes this transfer with this address and this data.
PREADY=0 does not abort, retry, or restart the access; it suspends it for one clock and asks the manager to try the same sample again next edge. For that suspension to be safe, two things must be true on every held cycle:
- The bus must not move. The subordinate is allowed to latch address, write data, strobes, and protection on the completing edge — and it cannot know in advance which edge that is. So every signal that defines the access must be identical on every held cycle, or the subordinate may capture a value the manager never intended to commit.
PENABLEmust stay asserted. The access phase is defined byPSEL && PENABLE. If the manager dropsPENABLEmid-hold, it leaves the access phase entirely — collapsing back toward setup — and what the subordinate sees is no longer one extended access but a corrupted, restarted one.
So the job is not "wait for ready." It is "freeze a single in-flight access perfectly and re-offer it, unchanged, every clock, until ready arrives."
2. Why previous knowledge is insufficient
Module 5 access-phase timing established the access phase and the per-edge PREADY sample; chapter 8.1 explained why a subordinate needs to stall; and chapter 8.3 PREADY timing made sure the PREADY value you read at the edge is clean and trustworthy. All of that gets you a correct PREADY reading. None of it tells you what to do with a low one:
- Sampling
PREADYlow is the question, not the answer. Chapter 8.3 guaranteed you sample the rightPREADYbit. This chapter is the contract for the cycle that follows a low sample: what the manager holds, what it must not touch, and how it re-arrives at the next sample. The "clean value" view stops exactly where the "what now" view begins. - The access phase has a duration, and the bus contract spans all of it. The idealized single-cycle access hides that during a hold the same signals must be re-presented identically. A model that only checks signals at the access edge misses the obligation that they stay stable across every held edge — because the subordinate may sample on any of them, including the last.
- Completion is a precise three-term condition, not just "PREADY went high." The transfer ends on the first rising edge where
PSEL & PENABLE & PREADYare all true. Treating it as "PREADY high" alone loses the fact that droppingPENABLEorPSELduring the hold breaks completion — which is the most common way this goes wrong.
The model to add is temporal: the access phase as a held window the manager must keep frozen and re-sample, with a single, exact exit condition. (How a single held cycle becomes many — and what bounds that — is chapter 8.5, multiple wait cycles; here we master the mechanics of one hold.)
3. Mental model
The model: an extended access is a single freeze-frame the manager keeps re-offering until the subordinate accepts it. Picture the manager holding up a fully-addressed request card — address, write/read, data, byte strobes, protection — and asking "ready?" once per clock. While the subordinate answers "not yet" (PREADY=0), the manager keeps the exact same card held up, unmoved, and asks again next edge. The moment the subordinate answers "yes" (PREADY=1) on a rising edge, the card is taken — that edge, where PSEL & PENABLE & PREADY all hold, is the transfer.
The crucial intuition is that the held cycles are not new transfers — they are the same transfer, paused. The manager does not re-issue, re-arbitrate, or re-enter setup. It is stuck in the access phase, looping, freezing the bus, re-sampling PREADY. Nothing about the request changes between asks; only the answer can.
Three refinements make it precise:
- The whole access-phase bus is frozen, not just address.
PADDR,PWRITE,PWDATA,PSTRB,PPROT,PSEL, andPENABLEmust all be identical on every held edge. The subordinate can latch any of them on the completing edge, and it doesn't know which edge that is until it raisesPREADYitself. PENABLEis the proof you are still in the access phase. It must stay high for the entire hold. Drop it and you have left access — the next cycle is no longer "the same access held," it is a broken, restarted one.- Completion is one edge, one condition. The access ends on the first rising edge with
PSEL & PENABLE & PREADYall true. Before that edge: hold and re-sample. On that edge: the transfer is done and the manager may advance.
4. Real SoC implementation
In silicon the held-access contract lives in the manager's transfer FSM. The FSM has three states — IDLE, SETUP, ACCESS — and the entire wait-state behaviour is one self-loop: stay in ACCESS while !PREADY, holding everything stable, and leave only when PREADY arrives. The discipline is not in any clever logic; it is in what the manager refuses to change while it loops.
// APB manager transfer FSM — extends a single access while PREADY is low.
// The whole point: in ACCESS, FREEZE the access-phase bus and re-sample PREADY
// each edge. Nothing about the request may change until completion.
typedef enum logic [1:0] { IDLE, SETUP, ACCESS } apb_state_e;
apb_state_e state, next;
// --- Next-state: the wait is the ACCESS self-loop on !PREADY ---
always_comb begin
next = state;
unique case (state)
IDLE : if (xfer_req) next = SETUP; // a transfer is pending
SETUP : next = ACCESS; // setup is always exactly 1 cycle
ACCESS : if (psel && penable && pready) // <-- the completion condition
next = (xfer_req) ? SETUP : IDLE;
// else: PREADY low -> STAY in ACCESS (the held cycle). Do nothing else.
default: next = IDLE;
endcase
end
always_ff @(posedge pclk or negedge presetn)
if (!presetn) state <= IDLE; else state <= next;
// --- Control outputs ---
// PSEL asserted in SETUP and ACCESS; PENABLE ONLY in ACCESS and held the WHOLE time.
assign psel = (state == SETUP) || (state == ACCESS);
assign penable = (state == ACCESS); // stays high across every held cycle automatically:
// we remain in ACCESS while !PREADY, so PENABLE never drops.
// --- The frozen request registers ---
// These advance ONLY on completion. They are NOT touched in the held cycles,
// so PADDR/PWRITE/PWDATA/PSTRB/PPROT re-present identically every wait cycle.
wire completing = (state == ACCESS) && pready; // psel&penable implied by ACCESS
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) begin
paddr <= '0; pwrite <= 1'b0; pwdata <= '0; pstrb <= '0; pprot <= '0;
end else if (state == IDLE && xfer_req) begin
// Latch the new request ONCE, as we leave IDLE. After this it is frozen
// through SETUP and every ACCESS cycle until 'completing'.
paddr <= req_addr; // WHY frozen: subordinate may decode/sample on completing edge
pwrite <= req_write; // WHY frozen: read/write direction must match the captured data
pwdata <= req_wdata; // WHY frozen: subordinate latches write data on completing edge
pstrb <= req_strb; // WHY frozen: byte lanes committed must match the completing edge
pprot <= req_prot; // WHY frozen: protection is part of the same single access
end
// NOTE: there is deliberately NO branch that updates these during a held ACCESS
// cycle. If the datapath wants to advance, it must wait for 'completing'.
endTwo facts make this correct. First, PENABLE staying high across the hold is automatic only because the FSM loops in ACCESS — there is no separate "keep PENABLE high" logic to forget; the held cycle is ACCESS, so penable = (state==ACCESS) is high for the whole hold by construction. Second, the request registers are loaded once on the IDLE→SETUP edge and have no update path during ACCESS, so the bus physically cannot drift while waiting. The completion gate completing = (state==ACCESS) && pready is the same PSEL & PENABLE & PREADY condition (PSEL and PENABLE are both implied by ACCESS), and it is the only event that advances the manager. (How that single self-loop becomes a bounded vs unbounded count of held cycles is chapter 8.5; here, one loop iteration is the whole story.)
5. Engineering tradeoffs
The "tradeoff" in transfer extension is really a stability contract: for each access-phase signal, must it stay stable for the entire held window, and what breaks if it does not? This is the table to internalize.
| Access-phase signal | Stable for the whole hold? | Why / what breaks if it changes mid-hold |
|---|---|---|
PADDR | Yes — frozen | The subordinate may decode/sample the address on the completing edge. Drift it and the access lands at the wrong register. |
PWRITE | Yes — frozen | Direction must match the captured data and the subordinate's read/write logic. Flipping it mid-hold corrupts the operation type. |
PWDATA | Yes — frozen (writes) | The subordinate latches write data on the completing edge; drift and it commits the wrong value (the classic silent write-corruption bug). |
PSTRB | Yes — frozen (writes) | Byte-lane enables committed must match the completing edge. Change them and the wrong bytes are written. |
PPROT | Yes — frozen | Protection attributes are part of the same access; changing them can flip a permission check on the completing edge. |
PSEL | Yes — held high | PSEL deasserting drops the access entirely (and the muxed PREADY). The transfer can never complete. |
PENABLE | Yes — held high | PENABLE is the access-phase marker. Dropping it leaves access and restarts/aborts the transfer — the single most common hold bug. |
PREADY | No — re-sampled | This is the one signal allowed to change: low means hold and re-sample, high (with PSEL/PENABLE) completes. It is the answer, not the request. |
PRDATA | Only valid at completion | The subordinate need only present read data on the completing edge; the manager captures it there. Intermediate values are don't-care. |
The throughline: everything that defines the request is frozen; only PREADY moves. The manager's job during a hold is pure stillness — re-present the identical access and re-sample. The temptation to "make progress" during the wait (advance data, re-decode, pulse PENABLE) is exactly what corrupts the transfer.
6. Common RTL mistakes
7. Debugging scenario
A write that lands intermittently wrong — usually only when the targeted peripheral happens to insert a wait state — is the signature of a manager that does not truly freeze the bus during the hold.
-
Observed symptom: a peripheral register occasionally ends up with the wrong written value, but only for writes to a slow target (one that asserts
PREADY=0for a cycle). Writes to always-ready targets are always correct, and the same write to the slow target sometimes works — it depends on whether a wait state happened to occur. The data captured is "off by one transaction," as if the next write's data leaked in. -
Waveform clue: in the access waveform (Figure 2),
PENABLEis high andPREADYis still low — clearly mid-hold — yetPWDATAchanges inside that window, drifting fromD0toD1. On the completing edge (PSEL & PENABLE & PREADY) the subordinate latches whatever is on the bus now, which is the driftedD1, not the intendedD0. -
Root cause: the manager's
PWDATA(and oftenPADDR) register has an update path that fires during the heldACCESScycle — typically the datapath/pipeline advanced on every clock instead of only on completion, so a lowPREADYcycle let the next transaction's data overwrite the in-flight one before it was accepted. -
Correct RTL: gate every request register on the completion event, never on a bare clock. The request loads once on
IDLE→SETUPand advances only oncompleting = (state==ACCESS) && pready; there must be no branch that writespwdata/paddr/pstrbduring a heldACCESScycle — exactly the FSM in Beat 4, where the registers have no held-cycle update path. -
Verification assertion: assert that the entire access-phase bus is stable from the first access edge until completion. For example:
Azvya Education Pvt. Ltd.VLSI MentorSnippet// PWDATA (and PADDR/PWRITE/PSTRB/PPROT) must not change while the access is held. property held_bus_stable; @(posedge pclk) disable iff (!presetn) (psel && penable && !pready) |=> $stable(pwdata) && $stable(paddr) && $stable(pwrite) && $stable(pstrb) && $stable(pprot) && $stable(psel) && penable; endproperty assert property (held_bus_stable);This fires the instant any held-access signal moves before completion — including
PENABLEdropping. -
Debug habit: when a write to a slow target is intermittently wrong but the same write to a fast target is always right, suspect the hold, not the data path. Pull up the access window, confirm
PENABLEhigh andPREADYlow (you are mid-hold), and watch every request signal for a transition before the completing edge. The fix is almost always to gate the request registers on completion instead of on the clock.
8. Verification perspective
Transfer-extension bugs are stability and completion bugs, so the verification plan is built from two assertion families plus FSM coverage of the held loop — and most of it catches in plain RTL simulation, not just gate-level.
- Assert held-window stability. The core property is that the entire access-phase bus is
$stablewhile the access is held (psel && penable && !pready |=> $stable(...)), coveringPADDR,PWRITE,PWDATA,PSTRB,PPROT, andPSEL, plusPENABLEstaying high. This is the single most valuable APB wait-state assertion: it fires on any drift and on any mid-holdPENABLEdrop. It runs in RTL — no delays needed — because it is a cycle-level functional property, not a timing one. - Assert the completion condition exactly. Check that the access advances if and only if
PSEL & PENABLE & PREADYheld on a rising edge: the manager must not advance while!pready, and must advance on the first edge all three are true. A complementary "no spurious completion" property (the FSM never leavesACCESSwhile!pready) closes the other half. Together these pin the exit point precisely. - Cover the ACCESS self-loop. Functional coverage must hit the hold itself: zero held cycles (complete on the first access edge), exactly one held cycle, and — forward to multiple-wait-cycles — many. A suite that only ever exercises always-ready subordinates never enters the
ACCESSself-loop, so it never tests the freeze; this is the classic coverage hole where drift bugs hide. Cross the held-cycle count with read vs write so thePWDATA/PSTRBstability path is exercised under a real hold.
What RTL catches versus gate: the stability and completion logic here is functional and visible in RTL — drift, a dropped PENABLE, an early completion all show up in a zero-delay run. (The PREADY-value glitch class from chapter 8.3 is the part that needs gate-level.) So the bulk of transfer-extension verification is RTL assertions plus loop coverage; reserve gate-level for the PREADY integrity it complements.
9. Interview discussion
"What exactly happens, cycle by cycle, when PREADY is low?" separates engineers who have only read the handshake from those who have written or debugged the FSM. A weak answer says "the manager waits." A strong answer states the freeze contract and the exact exit.
Frame it as freeze and re-sample: while PENABLE is high and PREADY is low, the manager is stuck in the access phase, holding the entire access-phase bus — PADDR, PWRITE, PWDATA, PSTRB, PPROT, PSEL, PENABLE — byte-identical, re-presenting it every clock and re-sampling PREADY on each rising edge. Then deliver the depth points: it is one transfer stretched, not many (the manager never re-enters setup or re-arbitrates — it loops in ACCESS); the bus must be stable across the whole window because the subordinate may latch on the completing edge, which the manager cannot predict; and completion is the precise condition PSEL & PENABLE & PREADY on a rising edge — so the two killer bugs are letting a request signal drift mid-hold (silent write corruption) and momentarily dropping PENABLE (which leaves the access phase and restarts it). Closing with "in the FSM I keep PENABLE high automatically by staying in the ACCESS state, and I gate the request registers to advance only on completion so the bus physically can't drift" signals you have actually built one.
10. Practice
- Trace the loop. On a write extended by two wait states, mark every
PCLKrising edge, state the FSM state at each, and identify the single edge wherePSEL & PENABLE & PREADYall hold. ConfirmPENABLEis high on all three access edges. - Freeze list. From memory, list every signal that must stay stable across the hold and, for each, name the failure if it drifts. State which one signal is allowed to change and which is only sampled at completion.
- Break it deliberately. Describe two distinct ways to corrupt an extended write — one that drifts
PWDATAmid-hold, one that dropsPENABLEfor a cycle — and the different symptom each produces. - Write the gate. Write the completion expression and the
always_ffguard that advances the manager'sPADDR/PWDATAregisters, such that they load once and never update during a heldACCESScycle. - Place the assertion. Write the SVA property that the access-phase bus is stable while
psel && penable && !pready, and state why it runs in RTL (not gate-level) and which bug class it does not cover.
11. Q&A
12. Key takeaways
- An extended access is one transfer stretched, not many. While
PREADYis low, the manager is stuck in the access phase looping — it does not re-enter setup, re-arbitrate, or re-issue. One setup, one (long) access phase, one completion. - The entire access-phase bus is frozen for the whole hold.
PADDR,PWRITE,PWDATA,PSTRB,PPROT,PSEL, andPENABLEmust be byte-identical on every held edge, because the subordinate may latch on the completing edge and the manager cannot predict which edge that is. PREADYis the only signal allowed to move — low means hold and re-sample, high (withPSEL/PENABLE) completes. The manager's job during the hold is pure stillness.PENABLEmust stay asserted continuously through the hold. Dropping it leaves the access phase and restarts/aborts the transfer — the single most common hold bug.- Completion is the exact condition
PSEL & PENABLE & PREADYon a rising edge —PREADYhigh alone completes nothing ifPSELorPENABLEhas been let go. - Build the freeze into the FSM, not into extra logic: keep
PENABLEhigh by staying inACCESS, and gate the request registers to advance only on completion so the bus physically cannot drift. Verify with a held-window$stableproperty and ACCESS-loop coverage — both catch in RTL.