Skip to content

AMBA APB · Module 9

Peripheral Failure Modes

Mapping a peripheral's internal failure conditions — FIFO full or empty, busy, hardware fault, read-only/write-only violation, internal timeout — onto APB's three primitives: a wait, an error, or a sticky status bit. The deliberate classification discipline, and the bugs from conflating a transient busy with a hard fault.

A peripheral fails in many internal ways — and APB gives it exactly three ways to say so. A FIFO fills, an engine is busy, an ECC check trips, a register is read-only, an operation times out. None of these is "an APB error" by nature; each becomes a wait, an error, or a status bit only because the designer chose to map it there. The single idea to carry: every internal failure mode must be deliberately mapped to wait (PREADY low), error (PSLVERR), or a sticky status bit — and choosing wrong turns a recoverable condition into a hard fault, or a real fault into a hung bus.

1. Problem statement

The problem is a peripheral has a rich set of internal failure conditions, but APB exposes only three mechanisms to report them — and there is no automatic mapping. The designer must classify each condition and route it to the right primitive, by hand, with full knowledge of what software downstream will do with the result.

A real peripheral can fail because:

  • a write FIFO is full and cannot accept another datum this cycle;
  • a read FIFO is empty and has no datum to return;
  • the engine is busy finishing a prior command and cannot take a new one;
  • a hardware fault is detected — a parity/ECC error in its RAM, a sensor reading out of range, a PLL that lost lock;
  • the access targets a read-only register being written, or a write-only register being read;
  • an internal operation timed out — a handshake to an off-chip device, a long-latency compute, never returned.

APB offers three responses, and only three: hold PREADY low (make the access wait), assert PSLVERR at completion (declare the access an error), or latch a sticky bit in a status register (record the fault durably for software to poll). The job is to map each of the conditions above onto one — or a combination — of these, and the mapping is a genuine engineering decision, not a lookup. The wrong choice is silent and dangerous: map transient FIFO-full to PSLVERR and ordinary burst writes fault under load; map a real ECC fault to "wait forever" and the bus hangs instead of erroring. The protocol does not protect you here — it hands you three primitives and trusts you to assign them correctly.

2. Why previous knowledge is insufficient

Chapter 9.1 established what PSLVERR is — a one-cycle pass/fail verdict sampled only on the completion edge. Chapter 9.3 covered one specific source of error: an address that decodes to nothing. Module 8 on multiple wait cycles taught how PREADY low stalls an access for a bounded number of cycles. Module 7 on write-error handling taught that an errored write does not roll back. Each gave you one primitive in isolation. This chapter is the integration problem none of them addressed: given a peripheral's actual internal failures, which primitive does each one get?

  • Knowing how PSLVERR behaves does not tell you when to assert it. 9.1 is the mechanism; this chapter is the policy. The hard question is not "how do I drive PSLVERR" but "is this condition an error at all, or is it a wait?" — and that is a property of the condition, not of the bit.
  • Wait and error look interchangeable until you ask whether the access can ever succeed. The wait-state chapters and the error chapter each lived on one side. The decision that joins them — transient backpressure the access will satisfy (wait) versus a condition the access can never satisfy as issued (error) — is the actual skill, and neither prior chapter forced you to make it.
  • There is a third primitive the earlier chapters barely used: the sticky status bit. A reported PSLVERR is gone the cycle after; it tells software "something failed here" but not what. A latched status bit is the durable record that lets firmware attribute the fault. Mapping a hardware fault means deciding it is both an error on the access and a sticky bit, and that combination is new.

Forward: once each condition is mapped, tracing a reported error back to which condition produced it is Chapter 9.6 on error-debug methodology — this chapter ends where the diagnosis begins. Here we build the map; there you read it backwards under a logic-analyzer trace.

3. Mental model

The model: a peripheral is a switchboard with three output jacks — WAIT, ERROR, STATUS — and every internal failure must be patched into exactly the right jack. The patching rule is a single question asked of each condition: can this access still succeed if I just hold on a moment?

  • If yes — the condition is transient backpressure — patch it to WAIT. A full write FIFO drains as the consumer reads; an empty read FIFO fills as the producer writes; a busy engine frees when it finishes. The access is fine; it just arrived a few cycles early. Hold PREADY low (within a bounded window — an unbounded wait is itself a bug) and complete normally once the condition clears.
  • If no — the access can never be honoured as issued — patch it to ERROR. Writing a read-only register, reading a write-only register, an internal timeout that has already given up: waiting cannot fix these. Complete the transfer and assert PSLVERR. Stalling instead would hang the bus for a condition that will never resolve.
  • If the condition is a fault the system must diagnose — also patch it to STATUS. An ECC error, a sensor fault, a PLL unlock are errors on the access and events firmware needs to attribute later. PSLVERR says "this access failed"; the sticky status bit says "it failed because the ECC checker tripped," surviving until software reads and clears it.

The trap the model exposes: WAIT and ERROR are not interchangeable, and the failure mode looks identical from the outside. A busy peripheral and a permanently-faulted one both "can't service the access right now" — but one is a wait and the other is an error, and patching them to the wrong jack is the central bug of the whole chapter.

A mapping diagram with six peripheral-internal failure conditions on the left, each connected by an arrow to one of three APB primitive boxes on the right: WAIT for the transient FIFO-full, FIFO-empty, and busy conditions; ERROR for hardware fault, read-only/write-only violation, and internal timeout; and a dashed arrow from the hardware fault additionally to a STATUS BIT box.
Figure 1 — a taxonomy of peripheral-internal failure conditions, each mapped to one of APB's three primitives. The transient conditions (FIFO full on write, FIFO empty on read, engine busy) map to WAIT — PREADY held low for a bounded window — because the access will succeed once the condition clears. The conditions the access can never satisfy as issued (a detected hardware fault, a read-only/write-only violation, an internal timeout that has expired) map to ERROR — PSLVERR at completion. A hardware fault additionally maps to a sticky STATUS BIT so software can attribute which fault occurred after the access is gone. The figure frames the single decision rule: can holding make this access succeed? Yes is a wait; no is an error; a diagnosable fault is an error plus a status bit.

4. Real SoC implementation

A real peripheral evaluates its internal conditions every cycle and routes each to the correct primitive in the same completion logic that drives PREADY and PSLVERR. The discipline below makes the mapping explicit in the RTL: transient conditions hold PREADY low; non-satisfiable conditions complete with PSLVERR; a fault both errors the access and latches a sticky bit. The structure is deliberately readable as the taxonomy from Figure 1.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A DMA-style command peripheral on APB. It maps each internal failure mode
// to exactly one APB primitive, in the same logic that completes the access.
//
//   transient backpressure  -> WAIT  (hold PREADY low, bounded)
//   can-never-satisfy        -> ERROR (PSLVERR at completion)
//   diagnosable hardware fault-> ERROR + STICKY STATUS BIT
 
module apb_cmd_periph (
  input  logic        pclk, presetn,
  input  logic        psel, penable, pwrite,
  input  logic [11:0] paddr,
  input  logic [31:0] pwdata,
  output logic [31:0] prdata,
  output logic        pready,
  output logic        pslverr,
  // internal status from the rest of the peripheral
  input  logic        cmd_fifo_full,   // write FIFO cannot accept now (TRANSIENT)
  input  logic        rsp_fifo_empty,  // read FIFO has nothing now    (TRANSIENT)
  input  logic        engine_busy,     // engine finishing prior cmd   (TRANSIENT)
  input  logic        ecc_fault,       // RAM ECC check tripped        (FAULT)
  output logic [31:0] rsp_data
);
  // ---- address map ----
  localparam ADDR_CMD = 12'h000;  // write-only command FIFO push
  localparam ADDR_RSP = 12'h004;  // read-only  response FIFO pop
  localparam ADDR_STS = 12'h008;  // read/write-1-to-clear status register
 
  wire access     = psel && penable;        // we are in the ACCESS phase
  wire is_cmd     = (paddr == ADDR_CMD);
  wire is_rsp     = (paddr == ADDR_RSP);
  wire is_sts     = (paddr == ADDR_STS);
 
  // ---- 1. WAIT: transient backpressure -> hold PREADY low (bounded) ----
  // The access WILL succeed once the condition clears, so we stall, not error.
  wire wait_write_full  = is_cmd &&  pwrite && cmd_fifo_full;   // FIFO full on write
  wire wait_read_empty  = is_rsp && !pwrite && rsp_fifo_empty;  // FIFO empty on read
  wire wait_busy        = is_cmd &&  pwrite && engine_busy;     // engine busy
  wire stall            = wait_write_full | wait_read_empty | wait_busy;
 
  // Bounded-wait watchdog: a transient condition must NOT stall forever.
  // If it does, it has effectively become a fault -> escalate to an error.
  logic [7:0] wait_cnt;
  always_ff @(posedge pclk or negedge presetn)
    if (!presetn)                 wait_cnt <= '0;
    else if (access && stall)     wait_cnt <= wait_cnt + 1'b1;
    else                          wait_cnt <= '0;
  wire wait_timeout = (wait_cnt == 8'hFF);  // stall exceeded its budget -> ERROR
 
  // ---- 2. ERROR: the access can never be honoured as issued -> PSLVERR ----
  wire err_ro_violation = is_rsp &&  pwrite;   // write to a READ-ONLY register
  wire err_wo_violation = is_cmd && !pwrite;   // read  of a WRITE-ONLY register
  wire err_fault        = ecc_fault;           // diagnosable hardware fault
  wire err_timeout      = wait_timeout;        // transient stall outlived its budget
  wire err_condition    = err_ro_violation | err_wo_violation | err_fault | err_timeout;
 
  // ---- complete the access: stall holds PREADY low; otherwise complete this cycle ----
  // PREADY low while (and only while) a transient, recoverable condition holds.
  assign pready  = access && (!stall || wait_timeout);
  // PSLVERR is the verdict, valid only on the completion edge (psel & penable & pready).
  assign pslverr = pready && err_condition;
 
  // On a write error we still complete, but the peripheral must SUPPRESS the side
  // effect (no FIFO push) so a "failed" write never mutates state. (See 9.x write-error.)
  wire do_cmd_push = is_cmd && pwrite && pready && !err_condition && !cmd_fifo_full;
 
  // ---- 3. STATUS BIT: a diagnosable fault is sticky so software can attribute it ----
  // PSLVERR is gone next cycle; the sticky bit survives until software W1C-clears it.
  logic ecc_sts, timeout_sts;
  always_ff @(posedge pclk or negedge presetn) begin
    if (!presetn) begin
      ecc_sts <= 1'b0; timeout_sts <= 1'b0;
    end else begin
      if (ecc_fault)                       ecc_sts     <= 1'b1;  // latch on fault
      if (wait_timeout && pready)          timeout_sts <= 1'b1;  // latch on timeout-error
      // write-1-to-clear when software writes the status register
      if (is_sts && pwrite && pready) begin
        if (pwdata[0]) ecc_sts     <= 1'b0;
        if (pwdata[1]) timeout_sts <= 1'b0;
      end
    end
  end
 
  assign prdata = is_sts ? {30'b0, timeout_sts, ecc_sts}
                : is_rsp ? rsp_data
                : 32'b0;
endmodule

Two facts drive this design. First, the bounded-wait watchdog (wait_timeout) is the seam between WAIT and ERROR. A transient condition is a wait only as long as it stays transient — if a "busy" engine never frees, the slave must not stall the bus forever; the watchdog escalates the stall into a PSLVERR so the bus recovers. That single counter encodes the chapter's deepest rule: an unbounded wait is a latent hang, so even a legitimately-transient condition needs an error escape hatch. Second, the fault is mapped to two primitives at oncePSLVERR (the per-access verdict) and ecc_sts (the durable record) — because the access-time bit tells software that an access failed while the sticky bit tells it which fault caused it, and software needs both to recover.

5. Engineering tradeoffs

The mapping is the design. This table is the canonical assignment of each common failure mode to a primitive — wait, error, or status — with the rationale that forces the choice.

Internal conditionWait or error?Sticky status bit?Rationale
FIFO full on writeWait (PREADY low)NoTransient backpressure — the FIFO drains as the consumer reads, so the write will succeed. Erroring it faults legitimate burst writes under load.
FIFO empty on readWait (PREADY low)NoTransient — the FIFO fills as the producer writes, so the read will return data. Erroring it makes ordinary polling fault.
Engine busy with prior commandWait (PREADY low)NoTransient — the engine frees when it finishes. A wait serializes commands cleanly; an error would reject valid commands the engine simply hasn't reached yet.
Hardware fault (ECC / sensor / PLL)Error (PSLVERR)YesWaiting cannot heal a fault. Error the access and latch a sticky bit so firmware can attribute and recover. The status bit is the durable "why."
Write RO / read WO registerError (PSLVERR)OptionalThe access can never be honoured as issued — no amount of waiting changes that. Often paired with a sticky "illegal access" bit for software triage.
Internal operation timed outError (PSLVERR)YesA bounded wait expired — the operation gave up. Must error (never stall forever) and latch a sticky timeout bit so software sees a stall happened, not just a generic failure.

The throughline: the wait-versus-error axis is decided by one question — can holding make this access succeed? — and the status-bit axis is decided by another — does software need to attribute which fault occurred? They are independent. A transient condition is a wait and needs no status bit; a fault is an error and a status bit; a register-protection violation is an error that may or may not want one. Map along both axes deliberately and the peripheral is debuggable; conflate them and you get either phantom faults or a hung bus.

6. Common RTL mistakes

7. Debugging scenario

A classic silicon bring-up bug: a high-throughput peripheral that randomly faults legitimate writes under load — because its designer mapped a transient FIFO-full condition straight to PSLVERR instead of to a wait.

  • Observed symptom: during a DMA-style burst of writes into a peripheral's command FIFO, occasional writes return a bus fault — but only under load, and never reproducibly on a single isolated write. Lighter traffic passes 100%; heavy back-to-back bursts throw scattered PSLVERR faults that software (correctly) reports as hard errors, aborting the transfer. The data for those writes is silently dropped.

  • Waveform clue: on a faulting write, PREADY goes high immediately (no wait cycle) and PSLVERR is high on that same completion edge. Correlating against the peripheral's internal FIFO occupancy shows the FIFO was momentarily full on exactly those cycles — the consumer side had briefly fallen behind. There is no wait state anywhere in the burst; every access completes in one cycle, some with PSLVERR.

  • Root cause: the slave's completion logic OR'd cmd_fifo_full into err_condition instead of into the stall term. A full FIFO is transient backpressure — it drains the moment the consumer reads — so it belongs on the PREADY-low (wait) path. Mapping it to PSLVERR declares an unrecoverable error for a condition that resolves on its own, so any write that happens to arrive while the FIFO is briefly full is faulted even though it was perfectly valid.

  • Correct RTL: move FIFO-full from the error term to the stall term, so the access waits instead of erroring — assign stall = is_cmd && pwrite && cmd_fifo_full; assign pready = access && !stall; assign pslverr = pready && err_condition; where err_condition excludes cmd_fifo_full. Add a bounded-wait watchdog so a never-draining FIFO escalates to an error rather than hanging the bus forever.

  • Verification assertion: prove that a transient FIFO-full is never reported as an error — it must produce a wait, not a PSLVERR:

    Azvya Education Pvt. Ltd.VLSI Mentor
    Snippet
    // A write to the command FIFO while it is full must WAIT (PREADY low),
    // never complete-and-error. FIFO-full is backpressure, not a fault.
    property p_fifo_full_waits;
      @(posedge pclk) disable iff (!presetn)
        (psel && penable && is_cmd && pwrite && cmd_fifo_full && !wait_timeout)
          |-> (!pready);            // must stall, must not complete-with-error
    endproperty
    a_fifo_full_waits: assert property (p_fifo_full_waits);
  • Debug habit: when an access faults only under load, suspect a transient condition mis-mapped to an error. Line up PSLVERR against the peripheral's internal occupancy/busy signals on the faulting cycles: if the "fault" coincides with a recoverable state (FIFO full, engine busy) that clears on its own, the bug is the mapping, not the access. Errors that fire only under contention are almost never real faults — they are waits wearing an error's clothes.

Two stacked APB timing diagrams of the same write burst with a momentary FIFO-full. The top correct case holds PREADY low during the full cycles with PSLVERR staying low and the write completing in green; the bottom buggy case completes immediately with PREADY high and PSLVERR high, faulting and dropping the legitimate write in red.
Figure 2 — the same write burst into a peripheral whose FIFO momentarily fills, handled two ways. Top (correct, green): on the full cycles the slave holds PREADY low so the write waits; PSLVERR stays low; the FIFO drains, PREADY rises, and the write completes successfully. Bottom (bug, red): the designer mapped FIFO-full to PSLVERR, so the write completes immediately with PREADY high but raises PSLVERR — a legitimate write is faulted and its data dropped purely because it arrived while the FIFO was briefly full. The figure shows that the identical physical condition, transient backpressure, must be a wait; mapping it to an error turns ordinary burst writes into random faults under load.

8. Verification perspective

Failure-mode mapping is a per-condition correctness property, so the verification plan must exercise each condition and prove it lands on the right primitive — a functional test that never stresses backpressure or injects a fault covers none of the mapping.

  • Cover every failure mode, and its primitive. Functional coverage must hit each condition — FIFO full on write, FIFO empty on read, engine busy, hardware fault, RO/WO violation, internal timeout — and cross it with the primitive it produced (wait, error, or status). A coverage hole on "FIFO-full → wait observed" is exactly where a mis-mapped-to-error bug hides; you only catch it by requiring the condition and its correct response in the same bin.
  • Assert the wait-versus-error classification directly. For each transient condition, bind a property that it produces a wait and never a completion-with-PSLVERR (as in Beat 7). For each non-satisfiable condition (RO/WO write/read, expired timeout), bind the converse — it must complete with PSLVERR and never stall indefinitely. These classification assertions are the heart of the testbench; they pin the mapping that the RTL is so easy to get backwards.
  • Inject faults and stress backpressure as first-class stimulus. Real ECC errors, sensor faults, and PLL unlocks rarely occur in a functional run, so the testbench must force them and confirm both the PSLVERR and the sticky status bit assert. Likewise, drive sustained back-to-back bursts that deliberately fill the FIFO and starve it, and confirm the access waits (and the watchdog escalates only when the stall genuinely exceeds its bound). Fault injection and backpressure stress are the only way to reach the corners where the mapping bugs live.
  • Check the sticky status bit's full lifecycle. Assert that a fault sets the status bit, that the bit survives subsequent clean accesses (it is sticky, not transient), and that a write-1-to-clear actually clears it — and only the targeted bit. A status bit that fails to latch, leaks away, or clears the wrong bit silently breaks software's ability to attribute faults.

The point: verify the mapping, not just the bits. Every condition must be reached, classified to the correct primitive by assertion, fault-injected and backpressure-stressed, and — for faults — checked across the full sticky-bit lifecycle.

9. Interview discussion

"A peripheral's write FIFO is full — do you assert PSLVERR or hold PREADY low?" is a fast, ruthless filter. The wrong answer ("PSLVERR, it can't take the write") reveals someone who memorized the error mechanism without understanding the mapping.

Frame the whole answer around one decision rule: can the access still succeed if I hold on? Yes → wait; no → error. A full write FIFO drains as the consumer reads, so the write will succeed — it is transient backpressure, a wait (PREADY low), and erroring it would fault legitimate burst writes under load. Then show range across the failure modes: FIFO-empty-on-read and engine-busy are the same transient case (waits); a read-only register being written or a write-only register being read can never be honoured (errors); a hardware fault — ECC, sensor, PLL unlock — is an error and a sticky status bit, because PSLVERR says "this access failed" but the status bit says which fault, and software needs both. Land the two depth signals that separate senior from junior: an unbounded wait is a latent bus hang, so even a legitimately-transient condition needs a bounded-wait watchdog that escalates to PSLVERR (a stuck engine must not stall the bus forever); and the two failure modes — mapping transient backpressure to an error, or a real fault to an infinite wait — are the worst-case opposites, one faulting good traffic, the other hanging on a genuine fault. Closing with "and on a write error the slave must suppress the side effect, because APB has no rollback" proves you have actually built one of these, not just read the table.

10. Practice

  1. Classify the six. For FIFO-full-on-write, FIFO-empty-on-read, engine-busy, ECC fault, write-to-RO-register, and internal-timeout, state wait or error, and whether it gets a sticky status bit — and give the one-line rationale for each.
  2. Apply the decision rule. Write the single question that decides wait versus error, and use it to explain why "busy" is a wait but "ECC fault" is an error even though both mean "can't service this access right now."
  3. Bound the wait. Explain why a transient condition mapped to a wait still needs a watchdog, and sketch the RTL that escalates an over-long stall into a PSLVERR.
  4. Two primitives at once. Explain why a hardware fault maps to both PSLVERR and a sticky status bit, and what software loses if you provide only one of the two.
  5. Find the under-load fault. Given a waveform where writes fault only during bursts and PSLVERR coincides with FIFO-full cycles, state the bug and the one-line fix.

11. Q&A

12. Key takeaways

  • A peripheral has many internal failure modes; APB exposes exactly three primitives — wait (PREADY low), error (PSLVERR), and a sticky status bit — and the designer must deliberately map each condition onto the right one. There is no automatic mapping.
  • The wait-versus-error decision is one question: can the access still succeed if I hold on? Yes (FIFO full/empty, engine busy — transient backpressure) → wait. No (RO/WO violation, expired timeout, a detected fault) → error.
  • A status bit answers a different question — does software need to attribute which fault occurred? A diagnosable fault is an error and a sticky bit; PSLVERR says it failed, the status bit says why. Transient conditions need no status bit.
  • An unbounded wait is a latent bus hang. Even a legitimately-transient condition mapped to a wait needs a bounded-wait watchdog that escalates an over-long stall into a PSLVERR, so a stuck engine never stalls the bus forever.
  • The two worst-case mis-mappings are opposites: transient backpressure mapped to an error faults good traffic under load; a real fault mapped to an infinite wait hangs the bus. APB recovers from neither on its own, so the mapping is the correctness.
  • Verify the mapping, not just the bits: cover every condition crossed with its primitive, assert wait-versus-error classification, inject faults, stress backpressure, and check the sticky-bit lifecycle (set, survive, W1C). Tracing a reported error back to its condition is the next chapter.