Skip to content

A failed APB transfer is not debugged by staring at a waveform — it is debugged by running a checklist, in order, from symptom to root cause to the assertion that makes the bug impossible again. This chapter is the module's field manual: it takes the hang-versus-garbage framing from PREADY common design bugs and the confirm-capture-eliminate procedure from error debug methodology and fuses them into one categorised triage checklist you run when any APB access fails. The single idea to carry: bin the symptom first, capture the coherent context, then descend a fixed sequence of categorised checks — and close every branch on a permanent check, because a bug you fixed without adding an assertion is a bug you will debug again.

1. Problem statement

The problem is converting "the transfer failed" into a root cause on a bus that gives you no transaction ID, no error code, and one shared handshake — fast, repeatably, and without a two-day guess-and-check hunt.

When a monitor flags a failure or firmware reports "the write did not land" or "the read hung," you are handed one fact: some access misbehaved. APB tells you nothing about which of a dozen root causes it is, and the same visible symptom can trace back to opposite bugs. The trap is that engineers debug by intuition — probing whatever signal is nearest — instead of by a procedure, and intuition on a shared, phase-sequenced bus is slow and error-prone:

  • The symptom is ambiguous. "It hangs" can be a slave FSM with no exit, a reset-stuck ready, a default-ready violation on a different slave, or an unsynchronised cross-clock done. "It reads garbage" can be a ready that leads the data, a stale PADDR, or a byte-strobe lane. You cannot fix what you have not binned.
  • The context is only coherent at one instant. PADDR, PWRITE, PWDATA, PSTRB, and the active PSELx mean what you think they mean only at the access edge. Probe them a cycle off and you root-cause the wrong slave.
  • A fix without a check is temporary. Reproducing a bug, patching the RTL, and moving on leaves the next engineer to rediscover it. The only durable close is a permanent assertion that fires the instant the bug ever returns.

So the job is not "know the APB rules" — you do, from the rules catalogue. It is to have a triage checklist: a categorised, ordered set of checks that, applied to any failed transfer, lands on exactly one root cause and leaves behind a permanent assertion.

2. Why previous knowledge is insufficient

Every prior chapter in this module taught you a piece — a symptom family, an error source, a monitor, an assertion — and each is a reference this checklist points back to. None of them, alone, is the checklist.

  • PREADY common design bugs built the hang-versus-garbage taxonomy and the two promises of PREADY. That is the spine of binning — but it covered only the PREADY-family bugs, not decode, bridge, or byte-strobe failures, and it did not sequence the checks you run at the bench.
  • Error debug methodology taught confirm-capture-eliminate for a PSLVERR. That is the error branch of triage — but a hang and a garbage read never raise PSLVERR, so that procedure covers one of three symptom bins.
  • APB monitors and APB assertions taught the instruments — how to reconstruct a transaction and how to write the properties. Those are the tools each checklist step reaches for, not the order in which to reach for them.
  • The per-signal debug chaptersPREADY stuck low, incorrect PRDATA, write corruption, address-decode bugs, PSEL debug bugs, PENABLE debug errors, bridge bugs, and peripheral failure modes — each taught one root cause in isolation. Knowing a dozen root causes separately is not the same as having a procedure to discriminate between them from a single failed access.

The missing skill is triage under a shared handshake: a fixed order of categorised checks where each answer rules a family in or out, so the first branch you fail is the root cause and the last thing you do is add the check that catches it forever. This chapter supplies that procedure and binds the module into one instrument. Interview practice on it lives in debug interview questions.

3. Mental model

The model: a failed APB transfer is a triage patient, and you run the same categorised checklist every time — bin the presenting symptom, capture vitals at the one coherent instant, then work down the categories until one check fails, and prescribe a permanent assertion so it never recurs. You do not start with a theory; you start by binning.

The bins are the three ways an access visibly fails, and they partition the whole space:

  • It hangs (a liveness failure). PSEL and PENABLE stay high, the access cycle repeats, no completion. Something good never happens. The cause is always a missing or unreachable path to PREADY=1.
  • It reads or writes garbage (a safety failure). The access completes, cleanly, but the wrong data is committed or captured. Something bad does happen. The cause is always readiness or capture running ahead of, or independent of, validity.
  • It errors (a signalled failure). The access completes with PSLVERR high at the edge — a decode, protection, or peripheral fault the slave told you about. This is the branch error debug methodology owns.

Three refinements make the model precise:

  • The order of checks runs external-to-internal. After binning, you check the cheapest, most bus-visible things first — are PSEL/PENABLE even asserting, is PADDR in a mapped range — before descending into a slave's FSM or a bridge's internals. Most failures resolve from the trace before you touch DUT internals.
  • The symptom can be non-local. A default-ready violation hangs a different slave than the buggy one; a decode alias answers for the wrong slave; a bridge mis-maps an error to the wrong master. Bin the symptom, but suspect the whole bus, per the AMBA APB spec (IHI 0024C) §2.1 model of one manager driving a shared set of subordinates.
  • Every branch closes on a permanent check. The triage is not done when you find the bug — it is done when you have written the assertion that would have caught it. A bounded-completion property closes every hang; a ready-implies-valid property closes every garbage read.
A decision-tree diagram. A failed APB transfer splits into three columns — HANG, GARBAGE, ERROR — each with first checks and a root-cause family listed below, and all three converge on a shared bottom box labelled add the permanent assertion, with the specific property for each family.
Figure 1 — the APB debug triage checklist as a decision tree, the reusable mental tool. It starts at a failed transfer and bins the symptom into three columns: HANG (liveness — PSEL/PENABLE high forever, no completion), GARBAGE (safety — completes but wrong data), and ERROR (signalled — PSLVERR high at the edge). Each column lists its first checks and its root-cause family: the hang column runs FSM-no-exit, reset-stuck, default-ready violation, unbounded wait, and cross-clock done; the garbage column runs asserted-too-early, wrong-or-stale PADDR, and PSTRB lane; the error column defers to the confirm-capture-eliminate flow for decode, protection, and peripheral. Below the families sits the shared closing step every branch flows into: add the permanent assertion — bounded-completion for hangs, ready-implies-valid for garbage, edge-sampled PSLVERR backed by a status bit for errors. The figure is the chapter's spine: bin, run first checks, descend the family, close on a check.

4. Real SoC implementation

The checklist is the deliverable of this chapter. Run it in order. Each stage bins or narrows; each row names what to check, and what a failure of that check isolates. Stage 0 bins the symptom; Stage 1 says what to probe; Stages 2–7 descend the categories.

Stage 0 — bin the symptom, and Stage 1 — capture

Stage / CategoryCheckWhat it isolates
0 · BinDoes the access ever complete (PSEL && PENABLE && PREADY high)?No → hang family. Yes → garbage or error family.
0 · BinAt the completion edge, is PSLVERR high?Yes → error family (go to error methodology). No → garbage family.
0 · BinIs the failure a wrong value on a clean completion?Yes → garbage family; capture back-to-back accesses to different targets.
1 · CaptureProbe PCLK, PSEL[x], PENABLE, PWRITE, PADDR, PWDATA, PRDATA, PREADY, PSLVERRThe nine bus signals; without all nine you cannot bin or eliminate.
1 · CaptureProbe the slave's internal done/data_valid and FSM stateThe one internal pair that tells hang (no done) from garbage (early done).
1 · CaptureLatch {PADDR, PWRITE, PSTRB, PSELx} at the access edge, not a cycle offThe coherent context; a cycle-off capture root-causes the wrong slave.

Stage 2 — first checks (external, cheapest)

Stage / CategoryCheckWhat it isolates
2 · FirstIs PSEL for the intended slave asserting at all during the access?No PSEL → decode/select bug (PSEL debug), not a slave-internal bug.
2 · FirstDoes PENABLE rise exactly one cycle after PSEL, and stay high to completion?Malformed phase → manager/PENABLE bug, not the addressed slave.
2 · FirstIs the addressed slave at fault, or is a shared-line neighbour dragging the bus?Isolates a local bug from a non-local default-ready / shared-PREADY neighbour.

Stages 3–7 — the family checklists

Stage / CategoryCheckWhat it isolates
3 · HangPREADY stuck low — does the FSM have a reachable state that drives it high?FSM-no-exit: a done path unreachable for this access shape.
3 · HangIs PREADY a known 0 out of reset with a real rise condition?Reset-stuck / undriven-X ready (PREADY stuck low).
3 · HangWith this slave idle, does it still drive the shared PREADY high?Default-ready violation — the non-local hang that stalls a different slave.
3 · HangDoes the wait ever end, or is the completion term unbounded?Unbounded-wait hang (multiple wait cycles); needs a manager watchdog.
3 · HangDoes PREADY's done term cross from another clock domain unsynchronised?Cross-clock done: intermittent hang tied to clock phase/ratio.
4 · GarbageDoes PREADY rise a cycle before PRDATA/the read mux settles?Asserted-too-early: readiness leads validity (incorrect PRDATA).
4 · GarbageIs the captured PADDR the right register, or stale/wrong for this beat?Wrong-register / stale-PADDR capture (write corruption).
4 · GarbageDo the PSTRB lanes match the bytes that actually changed?Byte-strobe lane bug: unwritten or over-written byte lanes.
5 · DecodeDo two slaves' ranges overlap, leave a hole, or alias to the same decode?Decode overlap / hole / alias (address-decode bugs).
6 · BridgeDoes the bridge propagate the downstream wait/error to the upstream master?Bridge wait-propagation or error-mapping loss (bridge bugs).
7 · PeripheralBus legal but slave faulted — is a sticky status bit set?Peripheral failure mode (peripheral failure modes).

That is the instrument: bin at Stage 0, probe the nine bus signals plus the internal done/state at Stage 1, run the external first checks at Stage 2, then descend the family that Stage 0 selected. The throughline of the whole table is that every row's failure names a root cause and implies the permanent assertion that would have caught it. The hang family closes on one bounded-completion property; here is that representative catching assertion in real APB signals:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// BOUNDED-COMPLETION — the permanent check that closes the entire hang family.
// Once an access is in its ACCESS phase, PREADY must rise within MAX_WAIT cycles.
// Catches: FSM-no-exit, reset-stuck ready, unbounded wait, and (with a shared
// return) the default-ready violation that stalls a different slave.
property apb_bounded_completion;
  @(posedge pclk) disable iff (!presetn)
    (psel && penable && !pready) |-> ##[1:MAX_WAIT] pready;
endproperty
assert property (apb_bounded_completion);
 
// READY-IMPLIES-VALID — the permanent check that closes the garbage-read family.
// A completing read may not precede a settled data-valid term; readiness FOLLOWS
// validity, never leads it. Catches asserted-too-early on every path.
assert property (@(posedge pclk) disable iff (!presetn)
  (psel && penable && pready && !pwrite) |-> data_valid_q
);

5. Engineering tradeoffs

The checklist is one artifact, but where each check runs and what it costs is the design judgement. The tradeoff is depth of check against speed of triage: a bus-visible check is instant but shallow; an internal check is slower but decisive.

Check locationCost / speedWhat it can decideWhat it cannot
Bus-only (Stages 0, 2, 5)Instant — trace alonebin; PSEL/PENABLE phase; decode rangewhy a slave hangs internally
Internal done/state (Stages 1, 3)Slower — needs DUT visibilityhang vs garbage; FSM-no-exit; reset-stucknon-local neighbour effects
Multi-slave (Stage 3 default-ready, Stage 5 decode)Requires bus-level viewdefault-ready violation; overlap/aliassingle-slave directed tests miss it
Bridge boundary (Stage 6)Two-domain capturewait/error propagation losswhich downstream peripheral faulted
Status read-back (Stage 7)One extra register readnames the peripheral failure modeanything the bus already explained

The throughline: front-load the cheap external checks so most failures resolve from the trace, and descend to internal and multi-slave checks only when the bus has been proven clean. The non-local checks — default-ready and decode-alias — are the ones directed single-slave tests structurally cannot see, which is why they belong in the checklist explicitly and not in an engineer's memory. And the closing column of every row is the same regardless of location: the permanent assertion, run in the right simulation level (§8).

6. Common RTL mistakes

7. Debugging scenario

This is the checklist run end-to-end on the most instructive failure: a bus that hangs only when a specific slave is idle — a non-local default-ready violation that looks, at first, like a bug in the slave being addressed.

  • Observed symptom: firmware reads peripheral A and the read sometimes hangs — but only when peripheral B has not been accessed recently. Access B first and the A reads are fine; leave B idle and A hangs. The team's first instinct is "A's PREADY logic is broken."
  • Stage 0 — bin: the access never reaches PSEL_A && PENABLE && PREADY — it repeats forever with PSEL_A and PENABLE high. This is a hang (liveness), not garbage or error.
  • Stage 1 — capture: probing the nine bus signals plus each slave's internal done, the engineer sees PSEL_A high, PENABLE high, but the shared PREADY sampled by the manager is stuck low — while A's own internal pready_a is high. The bus PREADY disagrees with the addressed slave's ready.
  • Stage 2 — first checks: PSEL_A and PENABLE are well-formed, so the manager and phase are fine. The check "is the addressed slave or a shared-line neighbour at fault?" fails toward the neighbour: A is ready, yet the shared line is low.
  • Stage 3 — hang family, default-ready row: with B idle, B drives the shared/muxed PREADY low instead of high. The return mux ANDs the slaves' readies, so B's idle-low pulls the whole bus low and stalls A. Access B once and its ready goes high, unmasking A — which is exactly the "only when B is idle" signature.
  • Root cause: B violates the default-ready rule. An unselected slave must drive PREADY = 1 (or stay out of the return mux); B drove 0 when idle. The bug is in B; the symptom appeared on A.
  • Correct RTL — B must not stall the shared line when idle:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// B's ready: answer only inside B's own access; default HIGH when not selected.
assign pready_b = (psel_b && penable) ? data_valid_q_b  // our own access
                                      : 1'b1;            // idle -> never pull shared low
  • The permanent assertion (§8): close the branch with a per-slave default-ready check and the bus-level bounded-completion, so an idle slave dragging the shared line can never silently hang a neighbour again.
  • Debug habit: when a hang is conditional on another slave's state ("only when B is idle"), do not debug the addressed slave — bin it as a hang, then run the default-ready row against every slave on the shared return. The addressed slave being innocent is a result, not a dead end.
An annotated APB waveform capture showing nine stacked bus signals plus an internal done/state row, with three marked regions: a HANG tell where PREADY never rises, a GARBAGE tell where PREADY leads PRDATA by a cycle, and an ERROR tell where PSLVERR is high at completion.
Figure 2 — what to probe on a real capture, annotated with the tells for each bin. The nine APB bus signals (PCLK, PSEL, PENABLE, PWRITE, PADDR, PWDATA, PRDATA, PREADY, PSLVERR) plus the slave's internal done/state are stacked on one example waveform of an access with one wait state. The HANG tell (amber) is marked where PSEL and PENABLE stay high but PREADY never rises and internal done never asserts — the access repeats forever. The GARBAGE tell (red) is marked one access later where PREADY rises a cycle before PRDATA settles, so the manager samples the previous beat's value while the read mux is still resolving. The ERROR tell (blue) is marked at a completion edge where PSLVERR is high. The internal done/state row is highlighted as the one signal pair that disambiguates hang (no done) from garbage (early done), which the bus signals alone cannot. The figure is the Stage-1 capture list made concrete: probe these, read these tells, bin the failure.

8. Verification perspective

The checklist is not just a bench skill — every branch of it must terminate in a permanent property, and knowing which simulation level reproduces each bug is what makes the property meaningful. A stability check in RTL will never see a glitch; a value check will never see a hang.

  • Close every hang branch on bounded-completion. The single liveness property from §4 — (psel && penable && !pready) |-> ##[1:MAX_WAIT] pready — catches FSM-no-exit, reset-stuck, and unbounded-wait in one stroke. Pair it with the per-slave default-ready check that closes the non-local branch from §7:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// DEFAULT-READY — an idle (unselected) slave must not pull the shared PREADY low.
// The permanent check that would have prevented the "hangs only when B is idle"
// field bug: with !PSEL, this slave must present ready-high on the shared return.
assert property (@(posedge pclk) disable iff (!presetn)
  (!psel_b) |-> pready_b
);
  • Close every garbage branch on ready-implies-valid and a strobe check. The safety property (psel && penable && pready && !pwrite) |-> data_valid_q closes asserted-too-early; a companion PSTRB check that only-strobed byte lanes change closes the byte-lane branch. Neither fires on a hang, and the bounded-completion property never fires on garbage — which is why you need one property per bin.
  • Match the check to the level — RTL cannot reproduce everything. The RTL level reproduces stuck-low, asserted-too-early causality, phase-gating, and decode overlap — the logic bugs. Gate-level (delay-annotated) is required for the combinational-glitch ready and reset-polarity X, which zero-delay RTL settles away. CDC / multi-clock simulation is required for the cross-clock done hang, which only surfaces at specific clock phases and ratios — the "works in RTL, fails on FPGA" class. So the level dimension is itself a checklist entry: after closing a branch on an assertion, confirm you ran it at a level that can actually manifest the bug.

The point: a triage that finds a bug but leaves no assertion behind has closed nothing. Every branch of the checklist terminates in a named permanent property — bounded-completion, default-ready, ready-implies-valid, strobe-integrity — run at a level that can reproduce the failure, so the same access can never silently fail twice. Building these into a coherent monitor and coverage plan is the job of APB assertions and APB monitors.

9. Interview discussion

"An APB transfer failed — walk me through how you debug it" is a senior filter: a weak answer names one bug and starts probing; a strong answer gives a checklist. The line that lands is that you triage by procedure, not intuition.

Frame it as bin, capture, descend, close. First bin the symptom into hang (liveness — never completes), garbage (safety — completes with wrong data), or error (PSLVERR at the edge) — because the three demand different assertions and different families. Then capture the nine bus signals plus the slave's internal done/state at the access edge, the one coherent instant. Then descend external-to-internal: are PSEL/PENABLE even asserting; is the addressed slave or a shared-line neighbour at fault; then the family checklist — hang runs FSM-no-exit / reset-stuck / default-ready / unbounded-wait / cross-clock; garbage runs asserted-too-early / stale-PADDR / PSTRB-lane; decode runs overlap / hole / alias; bridge runs wait-propagation / error-mapping. Then close on the permanent check — bounded-completion for hangs, ready-implies-valid for garbage — because a fix without an assertion is a bug you will debug again. Land the depth points that signal real silicon: the default-ready violation is non-local ("the bus hangs only when slave B is idle" — suspect every slave, not the addressed one) and the level matters ("works in RTL, fails on FPGA" is the cross-clock done, invisible in zero-delay sim). Closing with "and every triage ends by adding the assertion that would have caught it" is what separates a debugger from someone who has only read the spec.

10. Practice

  1. Bin three symptoms. Given "the access never completes," "every 100th read returns the previous register," and "it only hangs when slave B is idle," assign each to hang / garbage / error and name the Stage-3/4 row you would run.
  2. Order the capture. List the nine bus signals plus the one internal pair you probe at Stage 1, and explain why capturing PADDR a cycle late root-causes the wrong slave.
  3. Run the non-local branch. For "hangs only when B is idle," walk Stages 0→3, name the root cause, and write B's corrected pready_b.
  4. Close on a check. Write the bounded-completion property and the per-slave default-ready property, and state which symptom each would have caught before it shipped.
  5. Pick the level. For stuck-low, asserted-too-early, combinational-glitch ready, and cross-clock done, state whether zero-delay RTL, gate-level, or CDC simulation is required to reproduce each, and why.

11. Q&A

12. Key takeaways

  • Debug by checklist, not intuition: bin, capture, descend, close. Bin the symptom into hang (liveness), garbage (safety), or error (signalled) first; the bin selects the family, and the family selects the checks.
  • Capture the nine bus signals plus the internal done/state at the access edge. PCLK, PSEL, PENABLE, PWRITE, PADDR, PWDATA, PRDATA, PREADY, PSLVERR, and the one internal pair that tells a hang (no done) from garbage (early done) — all coherent only at the access edge.
  • Run external checks before internal ones. Are PSEL/PENABLE asserting; is the addressed slave or a shared-line neighbour at fault; is PADDR mapped — resolve most failures from the trace before descending into a slave's FSM or a bridge, per the AMBA APB (IHI 0024C) §2.1 shared-subordinate model.
  • The symptom can be non-local. A default-ready violation hangs a different slave ("only when B is idle"), a decode alias answers for the wrong slave, and the cross-clock done is the "works in RTL, fails on FPGA" class — so suspect the whole bus and match the simulation level to the bug.
  • Close every branch on a permanent check. Bounded-completion for the hang family, per-slave default-ready for the non-local hang, ready-implies-valid and strobe-integrity for garbage, edge-sampled PSLVERR-with-status for error — run at a level that can reproduce the failure, so the same access can never silently fail twice.