AMBA APB · Module 11
PSLVERR Generation
Building the slave's PSLVERR generator — OR-ing the decode error, protection mismatch, read-only-write, and internal-fault sources into one verdict, registering it aligned to PREADY on the completing edge, and suppressing the write side effect on error while the transfer still completes.
PSLVERR is the slave's one-bit verdict on every access — "did this fail, and (internally) why" — and this chapter builds the RTL that produces it. Module 9 told you what PSLVERR means on the bus: it is a flag, not an abort; the transfer still completes; the master samples it on the completing edge. That is the protocol contract. This chapter is the other side of that contract — the generator inside the slave that actually drives the wire. The single idea to carry: a PSLVERR generator is a four-stage combiner — gather the slave's error sources, OR them into one err term, align that verdict to the completing edge so it appears exactly when PREADY rises, and suppress the write commit when err is set so a failed access never changes state. Get those four stages right and PSLVERR is a small, mechanical module that plugs onto the PREADY you already built. Get the alignment or the suppression wrong and you ship a slave that either lies about failure or corrupts state on an access it called "failed."
1. Problem statement
The problem is combining the slave's several independent error conditions into a single PSLVERR that is presented at exactly the right cycle, while guaranteeing that an errored access leaves no trace in the register state.
A real slave can decide an access is bad for several unrelated reasons, each detected by a different piece of logic: the address decoder finds the offset is inside the slave's window but maps to nothing (a decode error); the protection check finds PPROT does not satisfy the addressed register's policy (a privilege/security mismatch); an access-direction check finds software is writing a read-only register or reading a write-only one; or the peripheral itself reports an internal fault (FIFO error, parity, a busy resource). Any one of these means "this access failed." The slave exposes that verdict on a single wire, PSLVERR, so the generator must:
- Collect the sources and reduce them to one term. Four (or more) detectors, one output bit. They are independent, so the natural combiner is an OR — but each must be qualified to the current access so a stale or unrelated condition does not bleed into an unrelated transfer.
- Present the verdict at the completing edge, and only then. The master samples
PSLVERRon the same edge it samplesPREADYhigh (Module 9.2). SoPSLVERRmust be valid and aligned to that edge — registered alongsidePREADY, driven0on every cycle that is not completing. A verdict that asserts early, late, or floats between transfers is a protocol violation even if the OR term is logically correct. - Suppress the side effect when the verdict is "failed." This is the part the protocol layer does not state and slaves most often get wrong: an errored write must still complete (
PREADYrises) but must not commit (the register keeps its old value). If the slave flagsPSLVERRand writes the register, the bus reports "failed" while the state changed — the worst of both worlds.
So the job is not "OR some error flags onto a wire." It is to build a generator that gathers typed sources, qualifies and OR-s them, aligns the result to the completing edge, and gates the commit so the bus verdict and the slave's state stay consistent.
2. Why previous knowledge is insufficient
You already know a great deal about PSLVERR — but all of it is protocol semantics, not slave RTL, and the gap between the two is exactly this chapter.
- Module 9 gave you the meaning and timing, not the generator.
pslverr-behaviorestablished thatPSLVERRis a verdict, not an abort — the transfer completes regardless — and that it is sampled in a specific window.error-response-timingpinned the exact-cycle alignment:PSLVERRis valid on the completing edge, withPREADYhigh. Those chapters tell you what the wire must do; they do not build the logic that drives it. This chapter is that logic. - You have seen each source in isolation, never combined. Module 9 walked the individual origins — the decode source (9.3), the protection source (9.4), the peripheral source (9.5). But a slave has all of them at once and exposes one bit. The combiner — gather, qualify, OR, and the rule that any source forces the verdict — has no representation in those single-source chapters.
- The pieces you need to plug into already exist, but unconnected. Chapter
address-decoderproduces the decode-error flag (an in-window, unmapped offset). Chapterwrite-logicowns the commit you must now suppress. Chapterpready-generationowns thePREADYedge you must align to. You have each part; what is missing is the generator that consumes the decode flag, OR-s in the other sources, registers onto thePREADYedge, and gates the write commit. That wiring is the new model.
So the knowledge to add is not new protocol — it is the slave-side combiner that turns several independent fault detectors into one correctly-timed, side-effect-safe PSLVERR. The next chapter, slave-reset-behavior, then defines what this verdict (and the whole slave) looks like coming out of reset.
3. Mental model
The model: the PSLVERR generator is a courtroom verdict desk. Several independent inspectors each examine the access — the address inspector (is this offset actually mapped?), the privilege inspector (does PPROT clear this register's policy?), the direction inspector (is software writing a read-only register?), the internal inspector (did the peripheral itself fault?). Each can raise a hand. The desk's rule is simple and strict: if any inspector raises a hand, the verdict is "failed." But the verdict is not shouted the moment a hand goes up — it is recorded and announced at exactly one moment, the gavel strike, which is the completing edge where PREADY goes high. And the moment the verdict is "failed," a second rule fires: the clerk does not file the paperwork — the write is not committed, so the register keeps its old value.
Three refinements make it precise:
- The combiner is an OR, but every source is qualified to this access.
err = decode_err | prot_err | access_err | internal_err. The OR is right because the sources are independent and any one is sufficient. But each flag must be true only for the access in progress — gated by the slave being selected and in its access phase — so a stale fault from a previous cycle or an unrelated internal condition does not contaminate a clean transfer. Garbage qualification, not the OR, is where source combiners go wrong. - The verdict is registered onto the completing edge, never asserted combinationally.
PSLVERRis driven by the same flop discipline asPREADY: on the cycle the slave completes,PSLVERR <= err; on every other cycle,PSLVERR <= 0. This guarantees the verdict is exactly aligned to thePREADYthe master samples — not one cycle early off a decode that pulses during the setup phase, not floating between transfers. Alignment is a timing property, and the flop is what enforces it. - The verdict gates the commit — the side effect is suppressed on error. The write commit (from
write-logic) is the register's write-enable. The generator gates it:commit = wr_en & ~err. On a clean writeerr = 0, the commit fires, the register updates. On an errored writeerr = 1,PREADYstill rises (the transfer completes) but the commit is held low, so the register does not change. This is the discipline that keeps "the bus says failed" and "the state is unchanged" consistent — the heart of why the generator owns bothPSLVERRand the commit gate.
4. Real SoC implementation
In RTL the generator is small but disciplined: a combinational OR of qualified sources, a registered PSLVERR that mirrors the PREADY completion edge, and a commit gate that suppresses the write on error. The structure deliberately mirrors the PREADY generator (its chapter) so the two stay in lockstep on the completing edge.
// PSLVERR generator: gather the slave's error sources, OR them into one term,
// register the verdict onto the SAME completing edge as PREADY (0 otherwise),
// and suppress the write commit when err is set. Plugs onto the existing
// decoder (11.2), write logic (11.4), and PREADY generator (11.5).
module apb_pslverr_gen (
input pclk, presetn,
input access_phase, // PSEL & PENABLE: the slave's access cycle
input completing, // the cycle PREADY is driven high (from 11.5)
input pwrite,
// --- error sources (each already qualified to THIS access) ---
input decode_err, // in-window but unmapped offset (from 11.2)
input prot_err, // PPROT fails the addressed reg's policy (Mod 9.4)
input ro_write, // write hit a read-only register
input wo_read, // read hit a write-only register
input internal_err, // peripheral/internal fault (Mod 9.5)
// --- the raw write commit the generator must gate ---
input wr_en_raw, // "this register selected + write committing" (11.4)
// --- outputs ---
output reg pslverr, // the verdict, aligned to PREADY
output wr_commit // gated write-enable: fires only on a clean write
);
// 1) GATHER + OR: one combinational error term, every source qualified to
// the current access so a stale/unrelated condition can't leak in.
wire access_err = (ro_write & pwrite) | (wo_read & ~pwrite);
wire err = access_phase &
(decode_err | prot_err | access_err | internal_err);
// 2) ALIGN: register the verdict onto the SAME edge PREADY completes on.
// PSLVERR is valid exactly where the master samples it; 0 otherwise so it
// never floats or pulses early off a combinational decode.
always_ff @(posedge pclk or negedge presetn)
if (!presetn) pslverr <= 1'b0;
else if (completing) pslverr <= err; // verdict presented WITH PREADY
else pslverr <= 1'b0; // driven 0 on non-completing cycles
// 3) SUPPRESS: gate the write commit with ~err. On an error the transfer
// still COMPLETES (PREADY rises in 11.5) but the side effect is suppressed
// — the register keeps its old value, so "bus says failed" == "no commit".
assign wr_commit = wr_en_raw & ~err;
endmoduleThree facts make this the right structure. First, the OR is the whole combiner, but the qualification carries the correctness — err is gated by access_phase, and access_err is split by direction (ro_write only on a write, wo_read only on a read), so an unrelated or stale source can never tip a clean transfer. Second, PSLVERR is registered on the exact completing term that the PREADY generator uses — not a separate condition — which is what guarantees the verdict lands on the cycle the master samples, with 0 on every other cycle so it never pulses early or floats. Third, the generator owns the commit gate (wr_commit = wr_en_raw & ~err): the same err that drives PSLVERR suppresses the write, so the bus verdict and the register state can never disagree. The transfer still completes because PREADY is owned by 11.5 and is independent of err — completion and verdict are decoupled, exactly as the protocol requires.
5. Engineering tradeoffs
The slave's error sources are a small, fixed catalogue — the design judgment is in which conditions you treat as errors at all, who owns the detail, and whether each commits or suppresses. The table is the source map the generator OR-s together.
| Error source | Condition that raises it | Where the detail is owned | Commits or suppresses (on write) |
|---|---|---|---|
| Decode error | Offset is inside the slave's window but maps to no register (a hole) | address-decoder (11.2); semantics in Mod 9.3 decode source | Suppress — nothing to write to; no register exists |
| Protection mismatch | PPROT (privilege/secure/data) fails the addressed register's policy | Mod 9.4 protection source; policy lives per-register in the bank | Suppress — the access is not permitted; must not change state |
| Access-direction violation | Write to a read-only register, or read of a write-only register | This generator + the bank's register kinds (11.1) | Suppress — an RO register must not be written even though flagged |
| Internal / peripheral fault | Peripheral reports a fault (FIFO over/underflow, parity, busy resource) | Mod 9.5 peripheral source; the peripheral's own logic | Depends — usually suppress; some faults are post-commit and only reported |
| (Policy choice) clean access | None of the above for this access | n/a | Commit — err = 0, wr_commit fires, register updates |
The deeper trades sit behind this table. Whether a condition is an error at all is a policy decision, not a protocol one — many slaves treat a write to a hole as a silent drop with PSLVERR = 0 (legacy-friendly) rather than a decode error; whether an RO-write raises PSLVERR or is silently ignored is likewise a per-register choice (see register-bank-design). Suppress-on-error is almost always the right default, because a state change on a "failed" access is a debugging nightmare; the one nuance is genuinely post-commit faults (e.g. a write accepted into a FIFO that then overflows), which are reported after the fact and cannot be un-committed — those are an architectural exception, not a generator feature. And the verdict must be registered, not combinational, trading one flop of latency you already pay (the access phase is multi-cycle) for guaranteed alignment to PREADY. The throughline: the generator's logic is trivial (an OR and a gate); its correctness is in qualifying sources to the access, aligning the verdict to completion, and defaulting to suppress.
6. Common RTL mistakes
7. Debugging scenario
The signature PSLVERR-generator bug is a slave that flags an error but still commits the write — the bus reports "failed" while the register actually changed — combined, in its nastier variant, with a PSLVERR that is OR-ed combinationally off an early-pulsing decode and so is misaligned from PREADY.
- Observed symptom: firmware does a write that the slave is expected to reject (a write to a read-only register, or to an unmapped offset in-window). The driver sees
PSLVERRand logs the access as failed — yet a subsequent read shows the register did change, or a downstream side effect fired. Intermittently the driver sees no error at all on the same stimulus. So: "the access failed but the value changed," plus a flaky error report. - Waveform clue: on the completing edge,
PREADYis high and the register's write-enable is also high on an access wheredecode_err/ro_writeis asserted — the commit fired on an errored access. AndPSLVERRis seen pulsing high one cycle early (during the setup phase, whendecode_errfirst resolves) and falling back to0before thePREADYedge — so on the completing edge the master samplesPSLVERR = 0while the register still changed. The verdict and the commit are both wrong, in opposite directions. - Root cause: two coupled mistakes. (1) The write commit was
wr_en_rawdirectly — never gated by~err— so an errored access committed like any other. (2)PSLVERRwas assigned combinationally (assign pslverr = decode_err | ...) instead of being registered onto the completing edge, so it tracked the source's detection timing, not the completion timing, and fell before the master sampled it. The generator violated both "suppress the side effect" and "align to completion." - Correct RTL: register the verdict onto the completing edge and gate the commit with the same
err:
// align the verdict to PREADY's completing edge (0 otherwise)
always_ff @(posedge pclk or negedge presetn)
if (!presetn) pslverr <= 1'b0;
else if (completing) pslverr <= err; // sampled WITH PREADY high
else pslverr <= 1'b0;
// suppress the side effect on error: an errored write never stores
assign wr_commit = wr_en_raw & ~err; // err drives BOTH verdict and gate- Verification assertion: prove
PSLVERRonly ever appears at completion, and that an error implies no commit:
// PSLVERR is only high on a completing cycle (never floats / pulses early)
ap_verr_at_complete: assert property (@(posedge pclk) disable iff (!presetn)
pslverr |-> completing);
// an errored access NEVER commits the write side effect
ap_err_no_commit: assert property (@(posedge pclk) disable iff (!presetn)
err |-> !wr_commit);- Debug habit: when a "failed" access changed state, do not start in the firmware or the bus monitor — go to the slave's
PSLVERRgenerator and check two things together: is the verdict registered onto the samecompletingterm asPREADY(alignment), and is the commit gated by~err(suppression). The classic bug is treatingPSLVERRand the write commit as unrelated logic; in a correct generator they are driven by the sameerr, so they can never disagree. IfPSLVERRis combinational or the commit is ungated, you have found it.
8. Verification perspective
A PSLVERR generator is verified on three orthogonal properties — every source produces an aligned verdict, an error implies no side effect, and the source space is covered — and the highest-value tests deliberately exercise an error on both a read and a write.
- Each source produces
PSLVERR, aligned to completion. For every source — decode hole, protection mismatch, RO-write, WO-read, internal fault — drive a transfer that triggers exactly that condition and assertPSLVERRis high on the completing edge (pslverr |-> completing) and that the transfer still completes (PREADYrises). A source that setserrbut whose verdict is misaligned or that hangsPREADYis a generator bug, and only a per-source directed test catches it. - Error implies no side effect — the central safety property. Assert that on any access where
erris high, the register commit never fires:err |-> !wr_commit, and as a stronger end-to-end check, that the addressed register's value is$stableacross an errored write. For reads, assert an errored read returns definedPRDATA(e.g.err & ~pwrite |-> prdata == '0), never stale or X data. This pair — no commit on errored write, defined data on errored read — is what guarantees the bus verdict and slave state stay consistent. - Cover the source space and the read/write split. Functional coverage must show each error source was individually exercised (decode, protection, RO/WO, internal), that an error occurred on a write and on a read (the suppression rules differ — no commit versus defined
PRDATA), and ideally that two sources were asserted on the same access (the OR was actually exercised, not just one leg). Address-only or "we saw aPSLVERR" coverage misses which source produced it and whether the read path was ever errored.
The point: the generator's correctness is a per-source, side-effect-safe, completion-aligned property. Trigger each source, prove error-implies-no-state-change on both directions, and cover the source space — not just that PSLVERR toggled.
9. Interview discussion
"How does an APB slave generate PSLVERR?" separates candidates who memorized the protocol from those who have built the RTL, because a weak answer says "it asserts PSLVERR when there's an error" while a strong one describes a four-stage combiner and the two non-obvious disciplines.
Lead with the structure: the generator gathers the slave's independent error sources — decode (in-window unmapped offset), protection (PPROT versus policy), access-direction (RO-write / WO-read), internal fault — and OR-s them, each qualified to the current access, into one err term. Then deliver the two points that signal real experience. First, alignment: PSLVERR is a verdict sampled at completion, so it must be registered onto the same edge that drives PREADY high, 0 on every other cycle — never combinational off an early-resolving decode, or it can pulse and fall before the master samples it. Second, suppression: an errored write still completes (PREADY rises, independent of err) but must not commit — the same err gates the write-enable, wr_commit = wr_en_raw & ~err, so "the bus says failed" and "the register is unchanged" can never disagree. Close with the consistency insight: PSLVERR and the commit gate are driven by the same err — that single shared term is what makes the verdict and the state always agree, and mentioning that the worst real bug is "flagged an error but committed the write anyway" (or a combinational PSLVERR that misaligns from PREADY) shows you have debugged one. If you want to go further: note that PSLVERR reduces all reasons to one bit on the bus, while the reason is kept internally for a debug/status register — the master learns "failed," not "why."
10. Practice
- Build the OR term. Given
decode_err,prot_err,ro_write,wo_read, andinternal_err, write the combinationalerrexpression — qualified to the access phase and direction-split for RO/WO. Explain why each qualification matters. - Align the verdict. From memory, write the
always_ffthat registersPSLVERRonto the completing edge and drives it0otherwise. State precisely which edge the master samples and why a combinational assignment fails. - Gate the commit. Write the
wr_commitassignment that suppresses the write on error, and explain whyPREADYmust not be gated byerrwhile the commit must be. - Map the sources. Reproduce the source table (source / condition / owner / commit-or-suppress) from memory and add one source your design has that the table doesn't.
- Find the bug. Given a slave where a write to a read-only register reports
PSLVERRyet the register changed, locate the two possible faults (ungated commit; combinational/misalignedPSLVERR) and give the one-line fix for each.
11. Q&A
12. Key takeaways
PSLVERRgeneration is a four-stage combiner — gather, OR, align to completion, suppress the commit. The logic is trivial; the correctness is in the qualification, alignment, and suppression around it.- Reduce all sources to one bit. Decode error, protection mismatch, access-direction violation, and internal fault are OR-ed — each qualified to the current access — into a single
err; the master learns "failed," not why (the reason stays internal for a debug/status register). - Register the verdict onto the completing edge.
PSLVERRis sampled wherePREADYgoes high, so drive it from the samecompletingterm,0otherwise — never combinationally off an early-resolving source, or it misaligns and the master samples no error. - Complete AND flag — never abort. An errored transfer still completes;
PREADYis owned bypready-generationand is independent oferr. HoldingPREADYlow on an error hangs the bus. - Suppress the side effect on error. The same
errgates the write commit (wr_commit = wr_en_raw & ~err), so an errored write completes but never stores; on a read, return definedPRDATA. The verdict and the state are driven by one shared term and can never disagree. - Verify per-source, side-effect-safe, completion-aligned. Trigger each source individually, assert
pslverr |-> completinganderr |-> !wr_commit, cover the read/write split, and prove an errored write leaves the register$stable— not just thatPSLVERRtoggled.