Skip to content

AMBA APB · Module 7

Write Error Handling

PSLVERR on a write and the no-rollback, no-side-effect contract — the transfer still completes on the error edge, but APB defines no rollback, so a failed write must leave the register unchanged; the corruption a partial side effect causes.

A write pushes a value out and waits only for the acknowledgement to come back — but what if the subordinate cannot honour it? A failed write still finishes on exactly one completion edge, and on that edge PSLVERR says whether the write succeeded. The write-specific question is sharper than the read's: not "is the returned data valid?" but "was the write applied or not?" — and the answer that correct silicon must give is no. The write-specific contract is that a failed write completes normally yet produces no side effect: the addressed register is not updated, and because APB has no rollback, withholding that update is entirely the subordinate's responsibility. The single idea to carry: an APB write error does not abort or retry the transfer — it completes and labels the write failed, and a subordinate that lets a faulted write still change the register has corrupted state, the most damaging write-path bug there is.

1. Problem statement

The problem is letting a subordinate complete a write it cannot honour, while guaranteeing that the register the write targeted did not change.

A write is a one-way push: address and data out, the addressed register captures PWDATA on the commit edge, only acknowledgement returns. But some writes have no honest home. The address may decode to nothing; the target may be a read-only register; the access may violate a protection or security rule; a program/erase the write triggered may fail. In every case the subordinate still has to finish — APB has no abort — so it raises PREADY and lets the transfer complete. The danger is that completion normally means "pulse the write-enable and capture PWDATA," and here that capture must not happen.

PSLVERR closes that gap on the write path specifically:

  • PSLVERR low at completion means the write succeeded — the register captured PWDATA and the new value took effect.
  • PSLVERR high at completion means the write failed — the transfer still completes, but the addressed register must be left exactly as it was. No side effect.

So the problem solved is qualifying the side effect, not stopping the transfer. The write finishes the same way it always does; the subordinate's job is to ensure the one observable consequence of a write — the register changing — does not occur when the write is illegal. There is no "undo": the only safe design is to never have applied the change in the first place.

2. Why previous knowledge is insufficient

Module 3 — PSLVERR, the subordinate error gave you the generic contract: a one-bit status sampled at the completion edge, high means failed, the transfer completes anyway, recovery is software's job. That is correct, and it is the foundation — but it was written for any transfer, and it deliberately stopped at "the transfer completes; the write may not have taken effect." That sentence is a placeholder. On a write, the entire point of the transfer is the side effect — the register changing — so the question Module 3 set aside, was the write applied or not?, is the whole subject here.

What this chapter drills deeper, write-specific:

  • PSLVERR on a write is a label on a state change that must not happen. On a read, PSLVERR invalidates returned data the manager can simply discard. On a write there is nothing to discard — the consequence is internal, and once a register changes, no later bit can take it back. So PSLVERR high is not "ignore what came back"; it is a contract that the subordinate never let the register change. The two error paths are mirror images: a failed read must not be consumed, a failed write must not be applied.
  • APB has no rollback, so "no side effect" is a design obligation, not a protocol feature. Module 3 said the bus does nothing further. On a write that has teeth: the protocol gives you no mechanism to undo a commit, so correctness depends entirely on the subordinate qualifying its write-enable so a faulted write never pulses it. The guarantee lives in RTL, not in the wire.
  • The failure is invisible and irreversible, which raises the stakes. A bad read returns a benign value you throw away. A bad write that still commits silently corrupts a live register, and the corruption persists until something overwrites it — there is no edge on which it "comes back." That is why the write-error rule is prevent the commit, not recover after it.

So the model to add is not "errors exist" — you have that. It is the write no-side-effect contract: PSLVERR high means the register must be untouched, enforced by qualifying the commit, because APB cannot roll back.

3. Mental model

The model: a failed write is a returned letter the post office never delivered — the slot accepted your envelope and stamped the receipt "UNDELIVERABLE," but crucially the recipient's mailbox was never opened and nothing was put inside it. The transaction at the counter completed; what did not happen is the delivery. There is no "un-deliver" step because the delivery never occurred — that is exactly what makes it safe.

You hold the letter at the slot (PADDR, PWRITE high, PWDATA, held stable). The clerk takes it and completes the handoff — PREADY high, the transfer finishes exactly as a good write would. On that same handoff the clerk hands back a stamp (PSLVERR). If the stamp reads OK, the letter was placed in the mailbox — the register captured PWDATA, the commit. If the stamp reads UNDELIVERABLE, the mailbox was never opened: the write-enable never pulsed, the register holds exactly what it held before, and your job is to treat the write as failed. The post office does not deliver-then-retrieve; it simply declines to deliver, and that absence of a side effect is the whole guarantee.

Three refinements make the write contract precise:

  • The stamp is read at the counter, with the receipt. PSLVERR is valid only on the completion edge (PSEL, PENABLE, PWRITE, PREADY all high). The subordinate decides legality before it lets the commit fire, so the stamp and the (non-)delivery are resolved on the same edge.
  • An undeliverable letter is still a completed transaction. The write completes normally whether the stamp is OK or UNDELIVERABLE. "Failed" labels the write, it does not cancel the transfer — the bus has already moved on, and there is no automatic re-send.
  • The non-delivery is by design, not by accident. The clerk does not deliver a corrupted letter or half-fill the mailbox (a partial/garbled register update). A faulted write produces exactly zero change — the only correct outcome, because there is no way to clean up a wrong one.
Two stacked APB write timing diagrams. The top clean write completes with PSLVERR low, the write-enable pulses, and the register captures the new value. The bottom failed write completes identically — PREADY high on the same kind of edge — but PSLVERR is high, the write-enable stays low, and the register holds its old value, marked no side effect.
Figure 1 — a clean write and a failed write walked one above the other against PCLK, showing the no-side-effect rule. Both transfers take the same SETUP then ACCESS path and both complete on a single cycle where PSEL, PENABLE, PWRITE, and PREADY are all high. In the top, clean write, PSLVERR is low at completion, the write-enable pulses, and the register captures PWDATA — the write commits. In the bottom, failed write, the completion edge is structurally identical (PREADY high, transfer completes normally), but PSLVERR is high, the write-enable does NOT pulse, and the register keeps its old value — no side effect. The figure stresses that the failed write completes exactly like the clean one — only PSLVERR differs, and because PSLVERR is high the subordinate withholds the commit so the register never changes.

4. Real SoC implementation

In silicon a write error is the address decode, the legality check, and the write-enable resolving onto the same completion edge. The subordinate decodes PADDR; in parallel, validity logic decides whether this write is legal (mapped address, writable register, permitted protection) and drives PSLVERR. The decisive detail is that the write-enable is qualified by that legality — a faulted write completes the transfer but never pulses the enable, so the register cannot change.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Subordinate write-error path (teaching sketch — not a full slave).
// On a bad write, drive PSLVERR high at completion AND withhold the write-enable,
// so the register has NO side effect. Completion is the cycle where PSEL, PENABLE,
// PWRITE, and PREADY are all high.
wire access     = psel && penable;                 // ACCESS phase
wire write      = access && pwrite;                // this is a write
wire unmapped   = paddr[11:0] > LAST_VALID_OFFSET; // reserved / unmapped address
wire write_ro   = write && (paddr[11:0] == STATUS_ADDR);   // write to read-only reg
wire prot_fail  = write && reg_is_protected[paddr]         // protected register...
                       && !pprot_permits_access;           // ...with disallowed PPROT
wire write_bad  = write && (unmapped || write_ro || prot_fail);
 
assign pready  = 1'b1;                              // always ready in this sketch
// PSLVERR qualifies the completing write; valid only when PREADY is high.
assign pslverr = access && pready && write_bad;
 
// THE no-side-effect rule: the commit is qualified by !write_bad. A faulted write
// completes the transfer (PREADY high) but the write-enable NEVER pulses, so the
// register cannot change. There is no rollback path — there is simply no commit.
wire write_commit = psel && penable && pwrite && pready && !write_bad;
always_ff @(posedge pclk or negedge presetn) begin
  if (!presetn)            ctrl_q <= '0;
  else if (write_commit && (paddr[11:0] == CTRL_ADDR))
                           ctrl_q <= pwdata;        // commits ONLY on a legal write
  // No other path writes ctrl_q — a faulted write leaves it exactly as it was.
end

Two facts make the write-error path robust. First, pslverr is gated by access && pready so it asserts only on the completion edge — never during setup or a wait state — which is exactly when the manager samples it. Second, and the heart of the chapter, write_commit carries !write_bad: the legality check and the write-enable are decided on the same edge, so a faulted write completes but the enable stays low and the register is untouched. The transfer still completes either way — there is no abort, no retry, no extra wait (the error rides the same edge the manager already samples for PREADY); the qualification decides only whether the commit happens or is withheld.

A decision figure: at the write completion edge the subordinate samples PSLVERR — PSLVERR low routes to 'commit: write-enable pulses, register captures PWDATA', PSLVERR high routes to 'reject: write-enable withheld, register unchanged, no side effect', with the rule 'commit only if PSLVERR == 0' and a note that APB has no rollback.
Figure 2 — the subordinate's write-commit decision at the completion edge. With PSEL, PENABLE, PWRITE, and PREADY all high, the subordinate samples PSLVERR. If PSLVERR is low the write commits — the write-enable pulses and the register captures PWDATA — shown green as the success path. If PSLVERR is high the write is rejected — the write-enable is withheld and the register is left unchanged, no side effect — shown green because leaving state untouched is the correct behaviour, while the manager and software must treat the write as failed. The rule is one line: commit only if PSLVERR equals zero. APB defines no rollback, so the figure stresses that the no-side-effect guarantee is the subordinate withholding the commit, not the protocol undoing it; both branches complete the transfer normally on the bus.

5. Engineering tradeoffs

A write error is a one-bit label on a normally-completing transfer with a strict no-side-effect rule. Each property trades a capability APB does not carry for a clean, predictable write path.

Write-error propertyWhat it gives upWhat it buysWhy it is correct for APB
Failed write completes normallyA hardware abort/cancelOne uniform completion modelNo special "cancelled write" state to handle
PSLVERR qualifies the commitA protocol rollback stepOne rule: commit only if PSLVERR is lowThe check and the enable share one edge — zero extra cost
No side effect on errorA "fix it afterwards" pathA register guaranteed untouched on a bad writeAPB cannot undo a commit, so it must never happen
No retry on a failed writeA protocol-level re-issueA clean status-only write contractRetry policy belongs to software, not the bus
One bit, no reason codeAn in-band error causeThe cheapest write-error channelSoftware reads the cause from its own register map

The throughline: an APB write error costs one bit on an edge the manager already samples, completes the transfer unchanged, and demands exactly one discipline in return — a faulted write must leave the register exactly as it was. Because there is no rollback, that discipline is enforced before the commit, by qualifying the write-enable — never after, because after is too late.

6. Common RTL mistakes

7. Debugging scenario

A faulted write that still changed the register is the canonical write-error bug — and it is invisible on the bus, because the transfer completed normally and PSLVERR rose exactly as it should. The damage is internal and irreversible.

Two stacked APB write waveforms, both faulted with PSLVERR high at completion. The top correct case, in green, never pulses the write-enable and leaves the register at its old value. The bottom buggy case, in red, pulses the write-enable on the completion edge despite PSLVERR high and writes the new value into the register, marked as corrupted state.
Figure 3 — the corrupted-state bug. Both rows are the same faulted write completing with PSLVERR high; PENABLE, PREADY, and PSLVERR are identical. Top (correct, green): the write-enable is qualified by !PSLVERR, so the faulted write does not pulse it and the register keeps its old value — no side effect. Bottom (bug, red): the write-enable ignored PSLVERR and pulsed on the completion term alone, so the faulted write still committed and the register was wrongly updated with the new value despite PSLVERR being high — a write reported as failed that nevertheless changed state. The figure shows that the no-side-effect rule lives in the write-enable qualification, and a faulted write that still commits leaves silicon in an inconsistent, irreversible state.

8. Verification perspective

A write's result is a hidden state change, and on an error the required result is the absence of a state change — which verification must assert directly, because nothing on the bus shows it. The standard structure adds a no-side-effect rule on top of the normal write scoreboard.

  • Assert $stable on a faulted write. The single most important property: on any write whose completion edge carries PSLVERR high, the targeted register must not change. assert property (@(posedge pclk) (write_complete && pslverr) |-> $stable(target_reg)); — bind it per register or generate it from the register map. This is the exact inverse of the read rule (read_commit && pslverr |-> !rdata_valid): a failed read must not be used, a failed write must not be applied.
  • Scoreboard the no-side-effect rule. The reference model updates model_reg[paddr] = pwdata only on a write whose completion edge has PSLVERR low; on a PSLVERR-high write it leaves the model untouched. A later read-back that returns the rejected value while the model holds the original fails the scoreboard — catching exactly the debug-scenario corruption automatically. The model encodes the rule "a failed write applied nothing."
  • Cover the corners that hide write-error bugs. Coverage should hit: a write to every read-only register, a write to a reserved/unmapped offset, a protection-violating write, a failed program/erase, and crucially a faulted write immediately followed by a read-back of the same register (to prove it kept its old value). Functional coverage on write_complete && pslverr && paddr per register closes the "did we ever fault-test this one" gap.

The point: a clean write scoreboard verifies that the right value landed; a write-error scoreboard verifies that the wrong value did not. Because the effect of a faulted write must be nothing, the $stable assertion is the test — a monitor that only checks PSLVERR rose has verified the label, not the no-side-effect guarantee.

9. Interview discussion

"What happens to the register when an APB write errors?" separates engineers who memorised PSLVERR from those who understand the write no-side-effect contract. The trap answer — "the transfer aborts" or "the register gets the value and PSLVERR just flags it" — fails the whole point.

Lead with the outcome: on a write, PSLVERR high at completion means the addressed register must be left unchanged — the write produces no side effect. Then state the non-behaviour: the failed write completes normally — same edge, no abort, no retry — PSLVERR only labels the write failed. Then deliver the two depth points that mark a strong answer: APB has no rollback, so "no side effect" is the subordinate's job, enforced by qualifying the write-enable (write_commit = ... & !write_bad) so a faulted write never commits — there is no detect-then-clear, because there is no safe "afterwards"; and a faulted write that still commits silently corrupts a live register irreversibly, which is why verification asserts $stable on a PSLVERR-high write. Frame it as the mirror of the read rule — a failed read must not be consumed, a failed write must not be applied — and name real causes (write to a read-only register, reserved/unmapped address, protection/security violation, a failed program/erase). That framing — error withholds the commit, software handles it, the register never changes — is exactly what an interviewer is listening for.

10. Practice

  1. Draw the failed write. Draw a clean write and a failed write back to back; mark that both complete on the same kind of edge (PSEL, PENABLE, PWRITE, PREADY high) and that the only differences are PSLVERR and whether the write-enable pulses.
  2. Write the qualification. From memory, write the write_commit term that gates a register update so a faulted write produces no side effect, and explain why folding !write_bad into the enable — not just the status output — is the whole point.
  3. State the non-behaviour. In one sentence each, explain why PSLVERR does not abort the write, does not trigger a retry, and why APB cannot roll back a commit.
  4. Name the causes. List four distinct conditions that fail a write — and for each say whether it is an address, direction, protection, or operation issue (write to a read-only register, reserved/unmapped offset, protection/security violation, a failed program/erase).
  5. Catch the corruption. A subordinate raises PSLVERR correctly but still pulses the write-enable on a faulted write. Describe the failure mode, the one-line RTL fix, and the $stable assertion that would have caught it before silicon.

11. Q&A

12. Key takeaways

  • On a write, PSLVERR high means no side effect — the addressed register must be left exactly as it was. The question a write error answers is "was the write applied?", and the correct answer is no.
  • A failed write completes normally — same completion edge as a clean write (PSEL, PENABLE, PWRITE, PREADY all high), no abort, no retry. PSLVERR labels the write failed; it does not change the transfer's shape.
  • APB has no rollback, so "no side effect" is the subordinate's design obligation, enforced by qualifying the write-enable: write_commit = psel & penable & pwrite & pready & !write_bad. A faulted write never pulses the enable, so the register is never written.
  • A faulted write that still commits corrupts a live register irreversibly — there is no detect-then-clear that is safe, because no later edge undoes a commit. Prevent the commit; never patch after it.
  • Common write-error causes are a write to a read-only register, a reserved/unmapped address, a protection or security violation, and a failed program/erase operation.
  • Verify the absence of a change. Assert $stable on the target register for any PSLVERR-high write and scoreboard the no-side-effect rule — the mirror of the read's discard-on-error gate (read_commit & ~pslverr).