Skip to content

AMBA APB · Module 7

Write Wait-State Behaviour

Why a write specifically stalls — the subordinate delays the commit, not a data return, for a slow clock-domain crossing, a flash program or erase, or a near-full FIFO — holding PREADY low with PWDATA held stable until the commit is durable.

A read stalls because the answer has not arrived; a write stalls for the opposite reason — the value has already arrived, but the subordinate cannot durably store it yet. This chapter is about why writes specifically wait and what the held PWDATA is doing while they do. The single idea to carry: a write inserts wait states because the subordinate cannot commit the value yet — it must first synchronize the write across a clock domain, finish a slow action the write triggered, or wait for a near-full FIFO to make room — so it holds PREADY low to delay the commit, while the manager holds PADDR and PWDATA byte-for-byte stable, and the register commits on exactly one edge: completion, once the action is durable. The frozen-access machinery is identical to any wait; the reason (a delayed commit, not a delayed data return) and the durability rule are what make a write wait write-specific.

1. Problem statement

The problem is letting a subordinate take as long as it needs to durably accept a value, without the manager knowing how slow the commit is or letting go of the data too early.

On a write the data is not the thing that is slow — it is sitting on PWDATA, driven by the manager, ready from setup. What is slow is the acceptance: the place the value must land cannot take it on the first access cycle. It might be:

  • a register in another clock domain, where the write must cross a synchronizer before it can be safely captured — the value is present, but capturing it now would be unsafe;
  • a write that triggers a slow action — a flash or EEPROM program or erase, or a serialized update — that takes many cycles to durably store the value;
  • a near-full FIFO push or other backpressure, where the entry cannot be accepted until space frees up downstream.

In every case the manager has done its part — it presented PADDR, PWRITE high, and PWDATA, and asserted PENABLE. The value simply cannot be committed yet. APB's answer is the wait state: the subordinate holds PREADY low, the access is held in place, and the manager keeps PWDATA stable until the subordinate finally accepts it. The problem being solved is matching a fixed-shape write to an any-speed commit, and the lever is exactly one bit — PREADY — held low until the value can durably land.

2. Why previous knowledge is insufficient

Module 4's multi-cycle transfer taught the mechanism of a wait state in full: PREADY held low stretches one access, PENABLE stays high, SETUP is never re-entered, and every access-defining signal is frozen until the single completion edge. That mechanism is correct for reads and writes alike — and this chapter does not restate it. What Module 4 deliberately left general is the part that is write-specific, and that is what we drill here:

  • Why a write stalls at all. A read stalls because the subordinate is slow to produce a value; a write stalls because the subordinate is slow to commit one. The data is already on PWDATA — what is buying time is the acceptance: a synchronizer that must carry the write into another clock domain, a flash program that must finish, a FIFO that must free a slot. Module 4 said "the subordinate isn't ready"; here we say why the write's commit isn't ready — and it is never the data.
  • Who owns the held signal, and what "stable" means. On a read the interesting held signal is PRDATA, subordinate-driven and don't-care through the waits. On a write the interesting held signals are PADDR and PWDATA, manager-driven and required stable through every wait — because the subordinate will capture them on whatever cycle it finally commits. The single most common write-wait bug is a manager that stops driving PWDATA before the commit lands.
  • Where the value becomes durable, not just "valid." A read's completion edge is "the answer is finally here." A write's completion edge is stronger: it is "the value is now durably stored" — the synchronizer has captured it, the flash word is programmed, the FIFO entry is in. The subordinate must not raise PREADY until that is true, because the manager releases the held PWDATA the moment PREADY rises.

So the layer to add is not the wait-state machine — you have that. It is the write-specific causation (slow commit → hold PREADY) and the write-specific durability rule (PADDR/PWDATA held stable through the waits, committed exactly once, only when durable).

3. Mental model

The model: a write wait state is the courier holding the parcel at the door while the recipient clears a shelf. You have already handed the address and the parcel to the slot (PADDR, PWRITE high, PWDATA), and you keep holding them there. PREADY low is the recipient saying "I have your parcel in sight, but I cannot put it away yet — I am still making room / still walking it to the safe / still finishing the last one." Nothing is wrong, and nothing is lost; you simply keep the parcel at the slot until you hear the click that means it is put away for good.

The model has a write-specific shape Module 4's "pause button" did not stress:

  • The hold is a held-out offer, not a held-back answer. The access is frozen — PADDR and PWDATA stable, PENABLE high — because the subordinate will capture those exact values on the cycle it finally commits. Stop holding PWDATA mid-wait and the subordinate puts away an empty box.
  • The click means durable, not merely accepted. The subordinate must raise PREADY only when the value is genuinely, durably stored — the cross-domain capture has happened, the flash word is written, the FIFO slot is taken. Clicking early (raising PREADY before durability) tells the courier to walk away while the shelf is still empty.
  • The commit is one instant. However long the wait, the register (or FIFO, or flash word) is updated on exactly one cycle — completion, where PSEL, PENABLE, PWRITE, and PREADY are all high. Every wait cycle before it committed nothing; the value was merely offered and held.
A structural diagram with three cause boxes on the left — a cross-domain register that must be synchronized, a slow flash or EEPROM program or erase, and a near-full FIFO push — with arrows converging on a central amber rule block 'hold PREADY low until the commit can durably happen, PADDR and PWDATA held stable', which flows right to a green commit block where PREADY is raised for one cycle once the action is durable.
Figure 1 — why an APB write commit is delayed, and the single rule all three causes feed. On the left, three distinct reasons a write cannot commit on the first access cycle: a register in another clock domain where the write must be synchronized across before it can be safely captured, a write that triggers a slow flash or EEPROM program or erase that takes many cycles to durably store, and a near-full FIFO push that cannot be accepted until space frees up. Three arrows converge on one rule — hold PREADY low until the commit can durably happen, while PADDR and PWDATA are held stable. From that rule the commit lands on the completion edge once the action is durable, where PREADY is raised for one cycle and the manager moves on. The figure stresses that the cause differs but the mechanism is identical: the subordinate delays acceptance, never the data, and the write commits exactly once when it is durable.

4. Real SoC implementation

In silicon, a write wait state is almost always commit latency surfacing on the bus. The data path is trivial — PWDATA is right there. What is slow is the acceptance, and three patterns dominate.

Clock-domain crossing. A control register lives in a peripheral clocked separately from PCLK. Writing it cleanly means carrying the value (and a write pulse) across a synchronizer — a request/acknowledge handshake or a toggle synchronizer — before the destination flop captures it. Those crossing cycles are wait cycles: the subordinate holds PREADY low until the destination domain has acknowledged the capture, then completes. Raising PREADY before the acknowledge is the classic CDC write bug — the manager releases PWDATA while the value is still in flight.

Slow triggered action. Some writes are actions: writing a register kicks off a flash/EEPROM program or erase, or a multi-step serialized update. The value cannot be called durable until the program completes — a flash word can take microseconds, thousands of PCLK cycles. The subordinate holds PREADY low for the whole program (often gated by a prog_busy flag), then raises it for the one completion cycle once the cell is written.

FIFO / backpressure. Pushing into a near-full FIFO, or into a downstream bus that is itself stalled, cannot be accepted until a slot frees. The subordinate holds PREADY low while full (or near-full) is asserted, then commits the push and completes when room appears.

In all three, the manager's contract is unchanged from Module 4 — hold the access frozen, sample PREADY each cycle, keep PADDR/PWDATA stable, complete only at the edge. What changes is what must be true before PREADY rises: not "data ready" (it always is) but "commit durable."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Slow-WRITE subordinate: holds PREADY low while a slow commit action is in
// progress, then commits PWDATA and raises PREADY for ONE cycle when durable.
// Teaching sketch — write path only, not a full slave.
//
// `prog_busy` is high while the slow action (CDC capture / flash program /
// FIFO drain) has not yet durably stored the value. PADDR/PWDATA are held by
// the manager across every wait, so the captured value is the intended one.
logic prog_busy;                                  // 1 while the commit is not yet durable
logic pready;
 
wire access_write = psel && penable && pwrite;    // in ACCESS, this is a write
 
always_ff @(posedge pclk or negedge presetn) begin
  if (!presetn) begin
    pready <= 1'b0;
  end else if (access_write && !pready) begin
    // Selected for a write, not yet completed: gate PREADY on durability.
    pready <= ~prog_busy;        // raise PREADY ONLY when the action is durable
                                 // (commit the held PWDATA on this same edge);
                                 // while prog_busy -> PREADY stays low: a wait
  end else begin
    pready <= 1'b0;              // completion passed or bus idle -> re-arm
  end
end
// The actual register / flash / FIFO update is enabled by the SAME
// (access_write && ~prog_busy) term, so the commit and PREADY rise together.

Two facts make this write-specific and correct. First, pready rises only in the cycle prog_busy is false, and the durable commit is enabled by the same term — so PREADY and the actual storage happen together; the manager never moves on while the commit is still in flight. Second, the wait length is owned entirely by the commit action: a request/acknowledge CDC gives two or three waits, a near-full FIFO gives as many as the drain needs, a flash program gives thousands — and the manager's behaviour is identical for all of them. The subordinate's only job is to not lie: keep PREADY low until the value is genuinely, durably stored.

An APB write timing diagram with rows PCLK, PSEL, PENABLE, PWRITE, PADDR, PWDATA, PREADY: IDLE, SETUP with PSEL high, PWRITE high, PADDR and PWDATA presented, then four ACCESS cycles with PENABLE high — three WAIT cycles with PREADY low and PADDR/PWDATA held stable, then one COMMIT cycle with PREADY high where the register captures PWDATA on the dashed completion edge.
Figure 2 — a multi-wait APB write walked against PCLK, stressing the held data and the single commit. PWRITE is held high across the transfer to mark a write. After one SETUP cycle that presents PADDR and PWDATA with PSEL high, the access phase spans four cycles: three WAIT cycles where PREADY is low because the subordinate cannot yet durably commit, and a final COMMIT cycle where PREADY rises. PADDR and PWDATA are held as one continuous stable band across every wait and through the commit, because the manager owns the write data and must hold it until acceptance. PENABLE stays high across all four access cycles. The single commit edge, marked with a dashed line, is the only cycle PSEL, PENABLE, PWRITE, and PREADY are all high and the register captures PWDATA. The waveform makes the write-specific point: the waits delay only acceptance, the data never moves, and the register commits exactly once.

5. Engineering tradeoffs

Letting a write insert wait states driven by its commit is a deliberate, minimal design. Each property trades a capability APB does not need for the simplicity it does.

Write-wait propertyWhat it gives upWhat it buysWhy it is correct for APB
Commit latency surfaces as PREADY lowA fixed, known write latencyOne bus serves instant and slow commitsCDC, flash, and FIFO writes share identical timing
PADDR/PWDATA held stable through waitsJust-in-time / changing write dataA value good on whatever cycle commitsOne capture rule works at any commit speed
PREADY gated on durability, not arrivalEarly acknowledgementThe manager never moves on before the value landsDurable storage is the only honest "done"
Wait length owned by the commit actionManager control over write durationAny-speed sink on one bus, no side channelOnly the sink knows when the value is durable
Commit on exactly one edgeA spread-out writeOne unambiguous "the value landed here"A single write-enable pulse, easy to verify

The throughline: a write spends fixed latency and just-in-time data delivery to buy a flow where the slow part — the commit — is allowed to be slow without changing anything the manager does except wait, holding the data. The completion edge is the single landmark where the value is both stored and announced — and on a write, "announced" must never precede "stored."

6. Common RTL mistakes

7. Debugging scenario

A write that "completes" on the bus before its commit is durable is the canonical write-wait bug — and it is invisible on PWDATA, because the data was correct; it is the timing of the acknowledgement that was wrong.

  • Observed symptom: firmware writes a configuration register that lives in a peripheral's own clock domain (say, a clock-divider control). The write transfer completes cleanly, firmware proceeds, but the peripheral behaves with the old divider — and an occasional run shows a value that is neither the old nor the new one, but a torn mix of bits. The write "completes but doesn't land," intermittently.
  • Waveform clue: the write looks complete — PSEL, PENABLE, PWRITE, PREADY all high on the completion edge — but PREADY rose one cycle before the cross-domain commit_done (the destination-domain acknowledge). The manager released PWDATA at that early PREADY, so the synchronizer's later capture sampled PWDATA mid-change — stale on a clean run, torn on a metastable one.
  • Root cause: the subordinate gated PREADY on the start of the commit action (the synchronizer request was launched) rather than on its completion (the destination acknowledge). PREADY was decoupled from durability — asserted on a fixed cycle count, or on "request sent," instead of on "value captured."
  • Correct RTL: gate PREADY (and the actual capture-enable) on the durable-done condition, so they rise together: pready <= ~prog_busy; where prog_busy stays high until the destination domain acknowledges the write. PREADY must never lead the durable commit by even one cycle.
  • Verification assertion: bind a check that the commit is durable before completion and that the manager kept the data stable: assert property (@(posedge pclk) (psel && penable && pwrite && pready) |-> commit_done); plus a stability check across the waits, assert property (@(posedge pclk) (psel && penable && pwrite && !pready) |=> $stable(pwdata) && $stable(paddr)); — and a write-then-read-back scoreboard that fails if the read does not return the written value.
  • Debug habit: when a write "completes but doesn't stick" intermittently, do not stare at PWDATA — it was right. Line up the PREADY rising edge against the durable-done signal of the commit action (the CDC acknowledge, the flash done, the FIFO accept). Nine times in ten PREADY is leading durability, and the manager is releasing the data into a commit that has not happened yet.
Two stacked APB write waveforms with identical PENABLE/PADDR/PWDATA: the top correct case raises PREADY on the durable-commit edge and stores the new value matching PWDATA; the bottom buggy case, in red, raises PREADY early before the commit is durable, the manager releases PWDATA, and the stored value is left stale or torn.
Figure 3 — the premature-PREADY bug. Both rows are the same slow write with identical PENABLE, PADDR, and PWDATA. Top (correct, green): PREADY rises only on the cycle the commit is durable — the cross-domain write has been captured — so the manager completes exactly when the value has landed and the stored value matches PWDATA. Bottom (bug, red): PREADY rises a cycle early, before the CDC write is captured, so the manager treats the write as done and releases PWDATA while the commit is still in progress; the later capture finds no held data and the stored value is stale or torn. The figure shows that on a write the danger is acknowledging before durability — the manager drops the held data the instant PREADY rises, stranding the commit.

8. Verification perspective

A write's result is a hidden, durable state change, so verification must check not only that the value reached the register but that the subordinate did not acknowledge before it did — and that it eventually acknowledged at all. The structure is a reference model plus three classes of assertion: stability, durable-commit-once, and liveness.

  • Assert the held data is stable across every wait. The whole contract rests on the manager holding PADDR/PWDATA through the waits, so bind it: on any access-write wait cycle (psel && penable && pwrite && !pready), require $stable(pwdata) and $stable(paddr) into the next cycle. This catches a manager that lets go early and documents the obligation the subordinate depends on.
  • Assert the commit is durable and happens exactly once. Two properties cover the durability bugs: completion implies durability ((psel && penable && pwrite && pready) |-> commit_done), and the register changes only on a commit and once per transfer (no double-pulse across the waits, no early pulse during them). For a triggered action, also assert that PREADY does not rise while the action's busy flag is high (prog_busy |-> !pready).
  • Assert liveness — the write eventually completes. A slow commit must not become a hang. Bind assert property (@(posedge pclk) (psel && penable && pwrite) |-> ##[1:$] pready); so a subordinate whose commit never finishes (a wedged synchronizer, a flash that never reports done, a FIFO that never drains) fails the testbench instead of locking silicon.
  • Cover the corners that hide write-wait bugs. Coverage should hit: a write with one wait, with many waits, with the maximum the design allows; a write whose PWDATA would change if the manager misbehaved (to exercise the stability check); a write that completes with PSLVERR; and a write-then-immediate-read-back after a long wait, to confirm the value is durable, not merely accepted.

The point: because the effect is invisible on the bus and deferred in time, the scoreboard must model durability, not arrival. A write monitor that only checks PREADY rose has verified nothing about whether the value was actually, durably stored when it did.

9. Interview discussion

When an interviewer asks "why would an APB write need wait states?" the weak answer is "because the subordinate isn't ready" — true but generic, and it could describe a read. The strong answer is write-specific: a write waits because the subordinate cannot commit the value yet — the data is already on PWDATA; it is the acceptance that is slow.

Lead with the cause, with examples: the destination register lives across a clock domain and the write must cross a synchronizer before it can be safely captured; or the write triggers a slow action — a flash/EEPROM program or erase — that takes many cycles to durably store; or it is a near-full FIFO push that must wait for a slot. Then state the held-data rule: while the commit is not durable the subordinate holds PREADY low, and the manager holds PADDR and PWDATA byte-for-byte stable across every wait — because the subordinate will capture those exact values on the cycle it commits. Close with the durability rule and the depth point: the subordinate must raise PREADY only when the value is durably stored, because the manager releases the held data the instant PREADY rises — assert ready early and the commit captures stale or torn data (the premature-ready bug). Volunteering "the wait length is owned by the commit action — a CDC handshake gives two or three, a FIFO as many as the drain needs, a flash program thousands — and the manager just holds the data and waits" shows you know where write latency actually lives.

10. Practice

  1. Name the commit. For each of the three causes (cross-domain register, flash program, near-full FIFO), state in one sentence why the value cannot commit on the first access cycle and how many waits you would expect.
  2. Hold the data. On a three-wait write waveform, mark PADDR and PWDATA on every cycle and state the obligation on the manager and what breaks if it lets go on the second wait.
  3. Place the commit. Given PREADY rises on the fourth access cycle, identify the commit edge and state what the subordinate must have made true before it could raise PREADY there.
  4. Catch the premature ready. A subordinate raises PREADY one cycle before its CDC commit_done. Show what the destination register ends up storing and why the failure is intermittent.
  5. Wait versus error versus hang. A write holds PREADY low for fifty cycles, then completes with PSLVERR low. State why this is a normal slow commit and not an error, and write the liveness assertion that would distinguish it from a true hang.

11. Q&A

12. Key takeaways

  • A write waits because the subordinate cannot commit the value yet — a register across a clock domain that must be synchronized, a slow action the write triggers (flash/EEPROM program or erase), or a near-full FIFO push. The data is already on PWDATA; it is acceptance that is slow.
  • The manager holds PADDR and PWDATA byte-for-byte stable across every wait — they are manager-driven, and the subordinate captures those exact values on the commit cycle. Letting go early is the classic write-wait bug (unlike a read's PRDATA, which is don't-care through the waits).
  • PREADY must rise only when the commit is durable — the cross-domain capture acknowledged, the flash word programmed, the FIFO slot taken — because the manager releases the held data the instant PREADY rises.
  • The register changes on exactly one edge — the commit, where PSEL, PENABLE, PWRITE, and PREADY are all high. Every wait cycle commits nothing; the value is merely offered and held.
  • The access machinery is identical to any wait statePADDR/PWRITE/PWDATA frozen, PENABLE high, one completion edge — so this chapter adds the cause (a delayed commit) and the durability rule, not new wait mechanics.
  • A long PREADY-low stretch on a write is commit backpressure, not an error. Slow commits are normal; write errors are signalled by PSLVERR, sampled only at completion — and liveness assertions, not timeouts, are what catch a commit that never finishes.