A verified APB peripheral is not an integrated one — most bring-up failures are not RTL bugs inside the slave but connectivity mistakes made where it was wired into the SoC, and a categorised review checklist is the instrument that catches them before tape-out. You have spent this module building and verifying APB slaves in isolation: their PREADY handshake is clean, their register bank is correct, their assertions pass. This chapter is about the seam that no unit test sees — the moment the slave is dropped into a real subsystem behind a decoder, sharing a return path with other peripherals, hanging off a bridge, and routing an interrupt to a controller. The single idea to carry: integration failures are connectivity failures, and connectivity is checked by a categorised checklist, not by re-running the slave's own testbench — because the slave can be flawless and the integration still hang the whole bus.
1. Problem statement
The problem is enumerating, category by category, every connectivity fact that must be true for an APB peripheral to work once wired into a real SoC — so that an integration review has a definitive checklist against which the actual netlist can be judged, rather than a hand-wave of "it looks connected."
A peripheral's own verification proves it behaves as a standalone slave: given clean PSEL, PENABLE, PADDR, it drives the right PRDATA and PREADY. Integration is a different contract entirely — it is about the wiring around the slave, and that wiring is where the failures cluster:
- The failure is non-local. A peripheral whose own logic is perfect can still hang the bus if its idle
PREADYpulls a shared return line low, or corrupt reads if itsPRDATAfights another slave's in the mux. The symptom shows up on a different transaction than the one with the wiring mistake — exactly the class of bug a standalone testbench structurally cannot see, because it only ever exercises one slave. - Each check must be atomic and connectivity-grounded. "The peripheral is integrated correctly" is not a check; "the peripheral's base address does not overlap any other peripheral's window, and both windows fit inside the decoder's compare width" is. Each entry is a single, inspectable proposition about the netlist — one that maps to a wire, a constant, or a tie-off you can point at.
- It must be categorised by what it governs. Address map, decoder and select wiring, the shared return path, clock and reset, the upstream bridge, interrupt routing, protection attributes, tie-offs, manager-side backpressure, and doc parity — these are the natural clusters, and organising the checklist this way makes it auditable (you can ask "did we check all the return-path items?") and maps onto who owns each seam.
So the job is not "does the slave work" — you proved that in the last twelve chapters. It is to render the integration contract as a complete, atomic, categorised checklist that becomes the sign-off gate for wiring the APB subsystem together.
2. Why previous knowledge is insufficient
This module taught how a slave behaves and how to verify it. Every prior chapter is a reference the checklist points back to — but none of them is the integration checklist, because they all live inside the boundary of a single slave:
- The SoC architecture chapter taught the shape of a subsystem — bridge, decoder, one-hot
PSEL, return mux. That is the structure the checklist reviews, but it described the ideal topology, not the itemised list of ways a real instantiation of it gets mis-wired. Knowing the shape does not enumerate the connectivity facts. - The PREADY design-bugs catalogue taught the default-ready violation — an unselected slave pulling the shared
PREADYlow. That is one entry in this checklist's return-path category, framed there as an RTL bug inside the slave. Here it reappears as an integration check on the wiring — did the return path actually give an unselected slave a default-ready, or is the mux structured so an idle slave can still stall it? Same failure, different layer: the RTL chapter fixes the slave; this chapter verifies the connection. - The address-decoder and address-allocation chapters taught how one
PADDRbecomes a one-hotPSELand how to lay out windows. But they taught you to design a decoder; the checklist verifies that this peripheral's window was actually allocated without overlap, aligned, and inside the decoder's width — an audit of the outcome, not a lesson in the method.
The gap is this: prior chapters taught correct behaviour of the parts. They did not assemble the connectivity contract — the categorised list of facts that must hold about the wiring between the parts — that lets an integrator sign off a peripheral by inspection instead of hope. Building that checklist, and the insight that integration failures are non-local and invisible to unit tests, is this chapter.
3. Mental model
The model: the checklist is a pre-flight inspection, and the peripheral is an aircraft that already passed its factory test. The factory (unit verification) proved the airframe is sound. Pre-flight does not re-test the airframe — it walks the connections to the rest of the world: fuel line seated, control cables to the surfaces, instruments reading, no tool left in the engine. Every item is a connection, checked by pointing at it, and skipping one does not fail the part — it fails the flight, often catastrophically and far from where the connector was loose.
Three refinements make it precise:
- Checks cluster into ten integration categories. Address map (base/size, no overlap, alignment, fits the decoder width); decoder & PSEL (one select per slave, one-hot, illegal-address handling); shared return path (unselected slave defaults ready and stays out of the
PRDATAmux, no bus fight); clock & reset (PCLKconnected,PRESETnpolarity, reset tree reaches the slave); upstream bridge (AHB/AXI wait and error mapping wired through); interrupt routing (the slave's IRQ reaches the controller input it claims); PPROT / security (protection attributes driven and honoured); tie-offs (PSTRB,PPROT, unused inputs tied to defined values); manager backpressure (timeout/watchdog so one slave cannot hang the fabric); doc parity (the register-map document matches the RTL offsets). Every integration fact lives in one of these. - Each check catches a specific bring-up failure. A check is not just stated — it is paired with the failure it prevents: overlap →
PSELcontention and X on reads; idle slave not default-ready → a different slave hangs; wrongPRESETnpolarity → the slave never leaves reset and reads all-zero; unwired bridge error → aPSLVERRswallowed and a silent data-corruption. The "why" is the failure caught. - The failure is almost always non-local and shows up in bring-up, not sim. The address overlap surfaces when a second peripheral is added; the default-ready bug hangs whatever the manager is actually talking to; the interrupt mis-route fires the wrong handler. This is why the checklist is an integration artefact — a single-slave testbench never instantiates the neighbour that collides with it.
4. Real SoC implementation — THE CHECKLIST
In practice the integration review is a structured document: one row per check, its category, the precise WHAT, and the WHY / bring-up failure it catches. This is the deliverable. Every row is atomic and points at a wire, a constant, or a tie-off you can inspect in the netlist.
Address map
| Category | Check (WHAT) | Why / failure caught |
|---|---|---|
| Address map | Peripheral has a base address and size assigned from the SoC map | Unassigned base → the decoder never matches, PSEL never asserts, every access reads zero or hangs |
| Address map | This window does not overlap any other peripheral's window | Overlap → the decoder asserts two PSEL lines, two slaves drive the return, PRDATA contention / X on reads |
| Address map | Base is naturally aligned to the window size (e.g. 4 KB window on a 4 KB boundary) | Misalignment → the high-bit compare straddles the window, so some offsets decode to the wrong slave |
| Address map | The window fits inside the decoder's compare width and the bridge's PADDR range | Window above the decoded PADDR bits → high addresses alias down onto another peripheral silently |
Decoder & PSEL wiring
| Category | Check (WHAT) | Why / failure caught |
|---|---|---|
| Decoder & PSEL | Exactly one PSEL output feeds this slave, and it is this slave's alone | Shared/swapped PSEL → the wrong peripheral answers for this address; two respond at once |
| Decoder & PSEL | Selects are one-hot (or all-zero) across the whole fan-out | Non-one-hot → simultaneous selects = bus fight; verify with a one-hot assertion, not by eye |
| Decoder & PSEL | Every legal address selects exactly one slave; unmapped addresses hit a default that completes | No default arm → an unmapped access asserts no PSEL, nobody drives PREADY, the bus hangs forever |
| Decoder & PSEL | PSEL is gated by the bridge's live-transfer qualifier (no select when idle) | Ungated select → the slave sees phantom accesses during idle and may side-effect a register |
Shared return path
| Category | Check (WHAT) | Why / failure caught |
|---|---|---|
| Shared return path | An unselected slave drives PREADY = 1 (or stays out of the return mux) | Idle slave pulling shared PREADY low → it stalls whatever other slave the manager is accessing — a non-local hang |
| Shared return path | The PRDATA return mux selects strictly on the one-hot select — one driver at a time | Two drivers → read-data bus fight, X or corrupted reads on an unrelated transaction |
| Shared return path | Unselected slaves drive PRDATA = 0 (or are muxed out), never fighting the shared bus | An idle slave holding stale PRDATA onto the shared line corrupts the addressed slave's read |
Clock, reset, bridge, interrupt, protection, tie-offs, manager, doc
| Category | Check (WHAT) | Why / failure caught |
|---|---|---|
| Clock & reset | PCLK reaches the slave and is the same clock the return path samples | Wrong/ungated clock → CDC on the return path, metastable PREADY, intermittent hangs |
| Clock & reset | PRESETn is active-low and its polarity matches the slave's convention | Inverted polarity → the slave is held in reset forever (reads all-zero) or never resets (X registers) |
| Clock & reset | The reset tree actually reaches this slave's flops (not left floating on a new instance) | Unconnected PRESETn → registers power up X, PREADY can be X, reads are garbage after power-on |
| Upstream bridge | The bridge propagates the slave's wait states (PREADY) back to the AHB/AXI side | Wait not propagated → the fast side completes early and samples PRDATA before it is valid |
| Upstream bridge | The bridge maps PSLVERR to the upstream error response (SLVERR/HRESP) | Error swallowed → a failed write reports success upstream; silent data-corruption in software |
| Interrupt routing | The slave's interrupt output is wired to the specific controller input it is documented against | Mis-routed IRQ → the wrong ISR runs, or the interrupt is tied off and the event is lost entirely |
| PPROT / security | PPROT is driven from the manager and the slave (or a wrapper) honours the required privilege/security level | Unchecked PPROT → an unprivileged access reaches a secure register; a security-review escape |
| Unused tie-offs | PSTRB and PPROT (when unused) are tied to defined defaults, not left floating | Floating PSTRB → X byte-enables, partial or corrupted writes; floating PPROT → undefined protection |
| Manager backpressure | The manager/bridge has a timeout or watchdog on PREADY | No timeout → one slave that never asserts PREADY hangs the entire fabric, not just its own access |
| Register-map doc | The register-map document (offsets, widths, reset values, access) matches the RTL | Doc/RTL drift → firmware written to the doc reads/writes the wrong field; a bring-up software fault |
A representative slice of the return-path wiring the checklist audits — the shared PREADY/PRDATA mux with a default-ready for unselected slaves and a default arm for unmapped addresses — makes the two most dangerous integration rows concrete:
// APB integration return path — the seam the checklist reviews.
// Two integration rules are enforced HERE, not inside any slave:
// (1) an UNSELECTED slave must NOT pull the shared PREADY low (return-path row 1)
// (2) an UNMAPPED address must still COMPLETE (decoder default row)
module apb_return_mux #(
parameter int DATA_W = 32
)(
input logic psel_p0, psel_p1, psel_p2, // one-hot selects from the decoder
// each slave drives its own return; an idle slave contributes ready=1, data=0
input logic [DATA_W-1:0] prdata_p0, prdata_p1, prdata_p2,
input logic pready_p0, pready_p1, pready_p2,
output logic [DATA_W-1:0] prdata, // muxed back to the bridge/manager
output logic pready
);
// Select strictly on the one-hot vector — one driver at a time, never a bus fight.
always_comb begin
unique case ({psel_p2, psel_p1, psel_p0})
3'b001: begin prdata = prdata_p0; pready = pready_p0; end
3'b010: begin prdata = prdata_p1; pready = pready_p1; end
3'b100: begin prdata = prdata_p2; pready = pready_p2; end
// UNMAPPED (no select): complete harmlessly so the bus never hangs.
default: begin prdata = '0; pready = 1'b1; end
endcase
end
// Integration rule: each slave's OWN pready must default high when unselected,
// e.g. inside slave p1: assign pready_p1 = psel_p1 ? data_valid_q : 1'b1;
// assign prdata_p1 = psel_p1 ? rdata_q : '0;
// so an idle p1 can never stall an access that the manager runs to p0 or p2.
endmoduleTwo facts make this the right way to anchor the review. First, the two worst integration failures are enforced at the return path, not inside a slave: the default-ready for an unselected slave and the default arm for an unmapped address both live in the wiring, and both cause a whole-bus hang if missed — the highest-severity, most non-local failures on the list. Second, every row points at something inspectable — a constant (the base address), a wire (the PSEL fan-out), a tie-off (PSTRB), a polarity (PRESETn) — so the review is a mechanical netlist audit, and a gap is a row with no evidence, exactly the way the protocol-rules catalogue makes verification a rule-to-owner audit. Per AMBA APB (IHI 0024C) §2.1, PSEL, PENABLE, and PREADY are the signals whose inter-slave sharing the return-path rows govern; the spec defines their behaviour for one slave, and integration is where that behaviour is composed across many.
5. Engineering tradeoffs
Building and running the checklist is a sequence of judgement calls about granularity, structure, and how much robustness to wire in.
| Decision | Option A | Option B | When to choose which |
|---|---|---|---|
| Check granularity | One atomic connectivity fact per row | Coarse "address map OK" rows | Atomic — each maps to one inspectable wire/constant; coarse rows hide the overlap that ships |
Unselected PREADY | Slave drives ready=1 when idle | Slave muxed entirely out of the return | Either is correct; the sin is driving it low — pick one convention and enforce it fabric-wide |
| Unmapped address | Decoder default arm completes with PREADY=1 | Rely on a manager timeout to recover | Default arm — it completes cleanly; the timeout is a backstop, not the primary handling |
PPROT handling | Slave/wrapper checks and errors on violation | Tie off and ignore protection | Check it for any secure/privileged register; tie off only when the whole peripheral is non-secure by design |
| Doc parity | Generate register map and RTL from one source | Maintain doc and RTL by hand | Single-source generation — hand-maintained docs drift, and drift is a guaranteed bring-up fault |
The throughline: a good integration checklist is atomic, connectivity-grounded, and robustness-biased — where a choice exists between "complete cleanly in hardware" (default arm, default-ready) and "recover with a backstop" (manager timeout), the review wants both, because the failure modes are whole-bus hangs. Coarse rows are not a style choice; they are silent holes, because a peripheral only integrates as correctly as the list it was reviewed against.
6. Common RTL mistakes
7. Debugging scenario
The signature integration failure is a non-local hang traced back to a connectivity mistake, not an RTL bug — the peripheral with the wiring error is not the one that fails.
- Observed symptom: during bring-up, adding a newly-integrated timer peripheral to the subsystem causes an unrelated peripheral — the UART, at a completely different address — to intermittently hang. The UART's own regression is 100% clean; it was untouched. Accesses to the UART sometimes never complete; the CPU wedges on a status-register read.
- Waveform clue: on the failing trace, the manager runs a read to the UART's address. The decoder asserts
PSEL_uartcorrectly and the UART drivesPREADY=1on its completing edge — yet the sharedPREADYat the bridge stays low. Following the return mux backward, the newly-added timer — which is not selected — is drivingpready_timer = 0while idle, and the return path was wired to AND the ready lines instead of muxing on the one-hot select. The idle timer holds the shared line low and stalls the UART. - Root cause: a return-path integration mistake — the timer's
PREADYdefaults low when unselected (a default-ready violation), and the return path combines readies incorrectly. This is not a UART bug and not even really a timer behaviour bug in isolation; it is a connectivity mistake at the seam. It was invisible until a second peripheral shared the return path, which is why the standalone timer testbench never saw it — it never had a neighbour to stall. - Correct RTL: fix it at the seam, in two places. Give every slave a default-ready when idle, and mux the return strictly on the one-hot select so an unselected slave contributes nothing:
Azvya Education Pvt. Ltd.VLSI MentorSnippet
// inside each slave: default ready HIGH when unselected assign pready_timer = psel_timer ? tmr_data_valid_q : 1'b1; assign prdata_timer = psel_timer ? tmr_rdata_q : '0; // in the return path: mux on the one-hot select, never AND the readies always_comb unique case ({psel_timer, psel_uart}) 2'b01: begin prdata = prdata_uart; pready = pready_uart; end 2'b10: begin prdata = prdata_timer; pready = pready_timer; end default: begin prdata = '0; pready = 1'b1; end endcase - Verification assertion: prove no unselected slave pulls the shared
PREADYlow, and that a selected access is the only one that can hold it — a bus-level property a single-slave testbench cannot express:Azvya Education Pvt. Ltd.VLSI MentorSnippet// an unselected slave must not drive the shared PREADY low (per slave) assert property (@(posedge pclk) disable iff (!presetn) (!psel_timer) |-> pready_timer); - Debug habit: when adding a peripheral makes a different, previously-working peripheral hang, do not debug the peripheral that hangs — debug the seam you just changed. A non-local hang after an integration change is almost always a return-path or address-map connectivity mistake in the new block. Trace the shared
PREADY/PRDATAbackward through the mux and check every idle slave's default, exactly the discipline the checklist's return-path category enforces up front.
8. Verification perspective
Because integration failures are non-local, they need bus-level and multi-slave checks that a single-slave testbench structurally cannot express — plus a connectivity audit that no simulation performs at all.
- One-hot select and no-fight on the return. Assert that the select vector is one-hot-or-zero and that at most one slave drives the shared return — the address-overlap and
PSEL-contention rows in one property:Azvya Education Pvt. Ltd.VLSI MentorSnippet// every legal transfer selects at most one slave; unmapped selects none assert property (@(posedge pclk) disable iff (!presetn) $onehot0({psel_p2, psel_p1, psel_p0})); - Unselected-slave-does-not-stall. For every slave, prove an unselected instance cannot pull the shared
PREADYlow — the non-local hang the debugging scenario found, and the single most valuable integration assertion because directed single-slave tests cannot reach it:assert property (@(posedge pclk) disable iff(!presetn) (!psel_p1) |-> pready_p1);per slave, plus a bus-level bounded-completion ((psel && penable) |-> ##[1:N] pready) that catches the missing default arm and the missing manager timeout together. - Connectivity is audited, not simulated. The address-map no-overlap,
PRESETn-polarity, bridge error-mapping, interrupt-route, tie-off, and doc-parity rows are checked by inspection and traceability — walk the netlist and the address map and confirm each row has evidence, the way a formal connectivity check or a lint/CDC pass proves a wire goes where the doc says. This is the integration counterpart to the assertions that prove in-slave behaviour: the assertions guard the protocol, the connectivity audit guards the wiring, and integration sign-off needs both.
The point: an integration verification plan is "a one-hot select property, an unselected-does-not-stall property per slave, a bus-level bounded-completion, and a connectivity/doc-parity audit" — targeted precisely at the non-local, multi-slave failures that the slave's own clean regression can never surface.
9. Interview discussion
"You integrated a peripheral, its unit tests all pass, and now a different peripheral hangs during bring-up — how do you approach it?" is a senior integration question, because a weak answer debugs the peripheral that hangs while a strong answer immediately suspects the seam.
Lead with the principle: a verified slave is not an integrated slave; integration failures are connectivity failures and they are non-local — the block with the wiring mistake is usually not the block that fails. Then frame the checklist: ten categories — address map (no overlap, aligned, fits the decoder width), decoder and PSEL (one-hot, default arm for unmapped), the shared return path (unselected slave defaults PREADY high, muxes out of PRDATA), clock and reset (PCLK connected, PRESETn polarity and connectivity), the upstream bridge (wait propagation, PSLVERR mapped to the fast-side error), interrupt routing, PPROT/security, tie-offs (PSTRB/PPROT), manager-side timeout, and register-map doc parity. Land the depth points: the two worst failures are whole-bus hangs enforced at the return path — the default-ready for an unselected slave and the default arm for an unmapped address — and both are non-local, so you debug the seam you just changed, not the block that hangs. Closing with "and I'd want a bus-level assertion that no unselected slave can pull the shared PREADY low, because a single-slave testbench structurally can't see that" signals real silicon integration, not spec reading — the difference between someone who has brought up a subsystem and someone who has only verified a slave.
10. Practice
- Categorise the checks. For a GPIO peripheral being added to an existing UART+timer subsystem, list one atomic check in each of the ten categories, phrased as a single inspectable proposition.
- Find the overlap. Given a UART at
0x4000_0000size 4 KB and a timer at0x4000_0800size 4 KB, state whether they overlap, what breaks if they do, and which checklist row catches it. - Reason about the default-ready. Explain why an unselected slave driving
PREADYlow hangs a different slave, and write the per-slave assertion that proves it never does. - Trace the error path. Describe what happens to a failed write (
PSLVERR) if the bridge does not map it to the upstream error response, and which category and row would have caught the missing mapping. - Audit reset. For a freshly-instanced slave, list the two distinct
PRESETnfailures (wrong polarity, unconnected) and the symptom each produces after power-on, and state why they are connectivity checks, not simulation checks.
11. Q&A
12. Key takeaways
- A verified slave is not an integrated slave. Unit verification proves the slave behaves; the integration checklist proves it was wired correctly into the SoC. Most bring-up failures are connectivity mistakes at the seam, not RTL bugs inside the block.
- Integration failures are non-local. An overlapping window, an idle slave pulling the shared
PREADYlow, or a mis-routed interrupt shows up on a different transaction or peripheral than the one with the mistake — so you debug the seam you changed, not the block that fails. A single-slave testbench structurally cannot see these. - The checklist has ten categories: address map (no overlap, aligned, fits the decoder width), decoder &
PSEL(one-hot, default arm), shared return path (unselected defaults ready, muxes out ofPRDATA), clock & reset (PCLK,PRESETnpolarity and connectivity), upstream bridge (wait propagation,PSLVERRmapped), interrupt routing,PPROT/security, tie-offs (PSTRB/PPROT), manager timeout, and register-map doc parity. - The two worst failures are whole-bus hangs enforced at the return path — the default-ready for an unselected slave and the default arm for an unmapped address — plus a manager timeout as the backstop. Wire all three, because the failure mode is the entire fabric wedging.
- Verify the non-local class with bus-level, multi-slave assertions — one-hot select, unselected-does-not-stall per slave, bounded completion — and audit the connectivity rows (overlap, reset polarity, error mapping, doc parity) by inspection and traceability, because the slave's own clean regression can never surface them.