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:
PSLVERRlow at completion means the write succeeded — the register capturedPWDATAand the new value took effect.PSLVERRhigh 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:
PSLVERRon a write is a label on a state change that must not happen. On a read,PSLVERRinvalidates 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. SoPSLVERRhigh 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.
PSLVERRis valid only on the completion edge (PSEL,PENABLE,PWRITE,PREADYall 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.
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.
// 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.
endTwo 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.
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 property | What it gives up | What it buys | Why it is correct for APB |
|---|---|---|---|
| Failed write completes normally | A hardware abort/cancel | One uniform completion model | No special "cancelled write" state to handle |
PSLVERR qualifies the commit | A protocol rollback step | One rule: commit only if PSLVERR is low | The check and the enable share one edge — zero extra cost |
| No side effect on error | A "fix it afterwards" path | A register guaranteed untouched on a bad write | APB cannot undo a commit, so it must never happen |
| No retry on a failed write | A protocol-level re-issue | A clean status-only write contract | Retry policy belongs to software, not the bus |
| One bit, no reason code | An in-band error cause | The cheapest write-error channel | Software 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.
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
$stableon a faulted write. The single most important property: on any write whose completion edge carriesPSLVERRhigh, 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] = pwdataonly on a write whose completion edge hasPSLVERRlow; on aPSLVERR-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 && paddrper 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
- 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,PREADYhigh) and that the only differences arePSLVERRand whether the write-enable pulses. - Write the qualification. From memory, write the
write_committerm that gates a register update so a faulted write produces no side effect, and explain why folding!write_badinto the enable — not just the status output — is the whole point. - State the non-behaviour. In one sentence each, explain why
PSLVERRdoes not abort the write, does not trigger a retry, and why APB cannot roll back a commit. - 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).
- Catch the corruption. A subordinate raises
PSLVERRcorrectly but still pulses the write-enable on a faulted write. Describe the failure mode, the one-line RTL fix, and the$stableassertion that would have caught it before silicon.
11. Q&A
12. Key takeaways
- On a write,
PSLVERRhigh 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,PREADYall high), no abort, no retry.PSLVERRlabels 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
$stableon the target register for anyPSLVERR-high write and scoreboard the no-side-effect rule — the mirror of the read's discard-on-error gate (read_commit & ~pslverr).