Skip to content

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.
  • PENABLE must stay asserted. The access phase is defined by PSEL && PENABLE. If the manager drops PENABLE mid-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 PREADY low is the question, not the answer. Chapter 8.3 guaranteed you sample the right PREADY bit. 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 & PREADY are all true. Treating it as "PREADY high" alone loses the fact that dropping PENABLE or PSEL during 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, and PENABLE must 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 raises PREADY itself.
  • PENABLE is 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 & PREADY all true. Before that edge: hold and re-sample. On that edge: the transfer is done and the manager may advance.
An APB timing diagram of a single write access extended by one wait state: PSEL and PENABLE held high across the access, PADDR/PWDATA/PSTRB/PPROT frozen as one green stable value, PREADY low at the first access edge (extend) then high at the second (complete), with the completion edge marked PSEL & PENABLE & PREADY.
Figure 1 — one write access extended by a single wait state, cycle by cycle. The transfer enters SETUP, then ACCESS with PENABLE asserted. PSEL and PENABLE are held high continuously from the start of access through completion — PENABLE is never deasserted during the hold. PADDR, PWRITE, PWDATA, PSTRB and PPROT are frozen (green) as one stable value across the whole access, because the subordinate may latch them on the completing edge. At the first access rising edge PREADY is low, so the manager treats it as a wait, re-presents the identical bus, and re-samples; at the second access rising edge PREADY is high and PSEL & PENABLE & PREADY all hold, so the single access completes. The figure stresses that this is one transfer stretched, not two — the bus is frozen and re-offered until PREADY is high.

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.

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

Two 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 signalStable for the whole hold?Why / what breaks if it changes mid-hold
PADDRYes — frozenThe subordinate may decode/sample the address on the completing edge. Drift it and the access lands at the wrong register.
PWRITEYes — frozenDirection must match the captured data and the subordinate's read/write logic. Flipping it mid-hold corrupts the operation type.
PWDATAYes — frozen (writes)The subordinate latches write data on the completing edge; drift and it commits the wrong value (the classic silent write-corruption bug).
PSTRBYes — frozen (writes)Byte-lane enables committed must match the completing edge. Change them and the wrong bytes are written.
PPROTYes — frozenProtection attributes are part of the same access; changing them can flip a permission check on the completing edge.
PSELYes — held highPSEL deasserting drops the access entirely (and the muxed PREADY). The transfer can never complete.
PENABLEYes — held highPENABLE is the access-phase marker. Dropping it leaves access and restarts/aborts the transfer — the single most common hold bug.
PREADYNo — re-sampledThis 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.
PRDATAOnly valid at completionThe 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=0 for 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), PENABLE is high and PREADY is still low — clearly mid-hold — yet PWDATA changes inside that window, drifting from D0 to D1. On the completing edge (PSEL & PENABLE & PREADY) the subordinate latches whatever is on the bus now, which is the drifted D1, not the intended D0.

  • Root cause: the manager's PWDATA (and often PADDR) register has an update path that fires during the held ACCESS cycle — typically the datapath/pipeline advanced on every clock instead of only on completion, so a low PREADY cycle 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→SETUP and advances only on completing = (state==ACCESS) && pready; there must be no branch that writes pwdata/paddr/pstrb during a held ACCESS cycle — 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 Mentor
    Snippet
    // 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 PENABLE dropping.

  • 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 PENABLE high and PREADY low (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.

Two stacked APB write waveforms of the same access with one wait state: the top correct case holds PWDATA at D0 through completion and captures D0; the bottom buggy case lets PWDATA drift to D1 during the wait while PENABLE is high and PREADY low, drawn in red, so the completing edge captures the wrong D1.
Figure 2 — held-bus stability versus PWDATA drift, on the same single access with one wait state. Top (correct, green): PWDATA is frozen at D0 from the first access edge through completion, so when PREADY goes high the subordinate latches the intended D0 on the completing edge. Bottom (bug, red): the manager lets PWDATA drift from D0 to D1 during the wait cycle — while PENABLE is high and PREADY is still low — because its datapath advanced instead of waiting for completion; on the completing edge the subordinate now latches D1, the wrong value, and the write silently corrupts. The waveform tell is a request signal changing inside the held window; the fix is to advance the manager's request registers only on PSEL & PENABLE & PREADY.

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 $stable while the access is held (psel && penable && !pready |=> $stable(...)), covering PADDR, PWRITE, PWDATA, PSTRB, PPROT, and PSEL, plus PENABLE staying high. This is the single most valuable APB wait-state assertion: it fires on any drift and on any mid-hold PENABLE drop. 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 & PREADY held 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 leaves ACCESS while !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 ACCESS self-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 the PWDATA/PSTRB stability 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

  1. Trace the loop. On a write extended by two wait states, mark every PCLK rising edge, state the FSM state at each, and identify the single edge where PSEL & PENABLE & PREADY all hold. Confirm PENABLE is high on all three access edges.
  2. 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.
  3. Break it deliberately. Describe two distinct ways to corrupt an extended write — one that drifts PWDATA mid-hold, one that drops PENABLE for a cycle — and the different symptom each produces.
  4. Write the gate. Write the completion expression and the always_ff guard that advances the manager's PADDR/PWDATA registers, such that they load once and never update during a held ACCESS cycle.
  5. 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 PREADY is 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, and PENABLE must 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.
  • PREADY is the only signal allowed to move — low means hold and re-sample, high (with PSEL/PENABLE) completes. The manager's job during the hold is pure stillness.
  • PENABLE must 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 & PREADY on a rising edgePREADY high alone completes nothing if PSEL or PENABLE has been let go.
  • Build the freeze into the FSM, not into extra logic: keep PENABLE high by staying in ACCESS, and gate the request registers to advance only on completion so the bus physically cannot drift. Verify with a held-window $stable property and ACCESS-loop coverage — both catch in RTL.