Skip to content

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 PSLVERR on the same edge it samples PREADY high (Module 9.2). So PSLVERR must be valid and aligned to that edge — registered alongside PREADY, driven 0 on 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 (PREADY rises) but must not commit (the register keeps its old value). If the slave flags PSLVERR and 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-behavior established that PSLVERR is a verdict, not an abort — the transfer completes regardless — and that it is sampled in a specific window. error-response-timing pinned the exact-cycle alignment: PSLVERR is valid on the completing edge, with PREADY high. 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-decoder produces the decode-error flag (an in-window, unmapped offset). Chapter write-logic owns the commit you must now suppress. Chapter pready-generation owns the PREADY edge 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 the PREADY edge, 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. PSLVERR is driven by the same flop discipline as PREADY: on the cycle the slave completes, PSLVERR <= err; on every other cycle, PSLVERR <= 0. This guarantees the verdict is exactly aligned to the PREADY the 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 write err = 0, the commit fires, the register updates. On an errored write err = 1, PREADY still 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 both PSLVERR and the commit gate.
A datapath diagram of an APB slave's PSLVERR generator: four error-source flags (decode, protection, access-violation, internal fault) OR-ed into one err term, that term registered onto PSLVERR on the same completing edge as PREADY and forced low otherwise, and a separate path showing the write commit gated by NOT err so an errored access completes but does not store its data.
Figure 1 — the slave's PSLVERR source combiner. On the left, four independent error sources each raise a flag: decode_err (an in-window but unmapped offset, from the 11.2 decoder), prot_err (PPROT versus the register's policy), access_err (a read-only-write or write-only-read violation), and internal_err (a peripheral/internal fault). These are OR-ed into a single combinational err term. In the centre, err is registered onto PSLVERR on the same completing edge that drives PREADY high — and forced to 0 on every non-completing cycle — so the verdict is presented exactly where the master samples it, never asserted early. On the right, the write-side discipline: the register's commit write-enable is gated by NOT err, so on an error the transfer still completes (PREADY = 1, PSLVERR = 1) but the side effect is suppressed and the register keeps its old value. The figure shows PSLVERR generation as a four-stage pipeline: gather, OR, align-to-completion, suppress-commit.

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.

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

Three facts make this the right structure. First, the OR is the whole combiner, but the qualification carries the correctnesserr 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 sourceCondition that raises itWhere the detail is ownedCommits or suppresses (on write)
Decode errorOffset is inside the slave's window but maps to no register (a hole)address-decoder (11.2); semantics in Mod 9.3 decode sourceSuppress — nothing to write to; no register exists
Protection mismatchPPROT (privilege/secure/data) fails the addressed register's policyMod 9.4 protection source; policy lives per-register in the bankSuppress — the access is not permitted; must not change state
Access-direction violationWrite to a read-only register, or read of a write-only registerThis generator + the bank's register kinds (11.1)Suppress — an RO register must not be written even though flagged
Internal / peripheral faultPeripheral reports a fault (FIFO over/underflow, parity, busy resource)Mod 9.5 peripheral source; the peripheral's own logicDepends — usually suppress; some faults are post-commit and only reported
(Policy choice) clean accessNone of the above for this accessn/aCommiterr = 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 PSLVERR and 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, PREADY is high and the register's write-enable is also high on an access where decode_err/ro_write is asserted — the commit fired on an errored access. And PSLVERR is seen pulsing high one cycle early (during the setup phase, when decode_err first resolves) and falling back to 0 before the PREADY edge — so on the completing edge the master samples PSLVERR = 0 while 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_raw directly — never gated by ~err — so an errored access committed like any other. (2) PSLVERR was 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:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 PSLVERR only ever appears at completion, and that an error implies no commit:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 PSLVERR generator and check two things together: is the verdict registered onto the same completing term as PREADY (alignment), and is the commit gated by ~err (suppression). The classic bug is treating PSLVERR and the write commit as unrelated logic; in a correct generator they are driven by the same err, so they can never disagree. If PSLVERR is combinational or the commit is ungated, you have found it.
Two stacked timing diagrams of an errored APB write. The correct top case shows PSLVERR rising with PREADY on the completing edge and the register value unchanged because the commit is suppressed. The buggy bottom case shows the register value changing on the errored access and PSLVERR pulsing early then falling low before the completing edge, so the master samples no error while the state changed.
Figure 2 — an errored write, correct (top, green) versus buggy (bottom, red). In both, PSEL/PENABLE are high through the access phase and PREADY rises on the completing edge, so the transfer completes. Top (correct): PSLVERR is registered high on the same edge as PREADY, and the register write-enable is held low because the commit is gated by NOT err — the register value stays unchanged, so the failed verdict matches an untouched register. Bottom (bug): two faults at once — the commit fires anyway, so the register value changes even though the access is errored (the bus says failed while the side effect applied, the worst of both); and PSLVERR is OR-ed combinationally off a decode that pulses one cycle early and falls before PREADY, so at the completing edge the master may sample PSLVERR low. The figure shows the verdict must align to the completing edge and the commit must be suppressed when err is set.

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 assert PSLVERR is high on the completing edge (pslverr |-> completing) and that the transfer still completes (PREADY rises). A source that sets err but whose verdict is misaligned or that hangs PREADY is 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 err is high, the register commit never fires: err |-> !wr_commit, and as a stronger end-to-end check, that the addressed register's value is $stable across an errored write. For reads, assert an errored read returns defined PRDATA (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 a PSLVERR" 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

  1. Build the OR term. Given decode_err, prot_err, ro_write, wo_read, and internal_err, write the combinational err expression — qualified to the access phase and direction-split for RO/WO. Explain why each qualification matters.
  2. Align the verdict. From memory, write the always_ff that registers PSLVERR onto the completing edge and drives it 0 otherwise. State precisely which edge the master samples and why a combinational assignment fails.
  3. Gate the commit. Write the wr_commit assignment that suppresses the write on error, and explain why PREADY must not be gated by err while the commit must be.
  4. 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.
  5. Find the bug. Given a slave where a write to a read-only register reports PSLVERR yet the register changed, locate the two possible faults (ungated commit; combinational/misaligned PSLVERR) and give the one-line fix for each.

11. Q&A

12. Key takeaways

  • PSLVERR generation 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. PSLVERR is sampled where PREADY goes high, so drive it from the same completing term, 0 otherwise — 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; PREADY is owned by pready-generation and is independent of err. Holding PREADY low on an error hangs the bus.
  • Suppress the side effect on error. The same err gates the write commit (wr_commit = wr_en_raw & ~err), so an errored write completes but never stores; on a read, return defined PRDATA. 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 |-> completing and err |-> !wr_commit, cover the read/write split, and prove an errored write leaves the register $stable — not just that PSLVERR toggled.