Skip to content

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 PREADY pulls a shared return line low, or corrupt reads if its PRDATA fights 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 PREADY low. 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 PADDR becomes a one-hot PSEL and 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 PRDATA mux, no bus fight); clock & reset (PCLK connected, PRESETn polarity, 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 → PSEL contention and X on reads; idle slave not default-ready → a different slave hangs; wrong PRESETn polarity → the slave never leaves reset and reads all-zero; unwired bridge error → a PSLVERR swallowed 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.
A block diagram of an APB peripheral in an SoC: a manager and AHB/AXI-to-APB bridge on the left drive a shared APB bus into an address decoder that asserts one-hot PSEL to several peripherals, whose PRDATA and PREADY feed a shared return mux back through the bridge; PCLK and PRESETn fan out to all slaves and the reviewed peripheral's interrupt routes to an interrupt controller, with ten numbered labels marking where each checklist category — address map, decoder and PSEL, shared return path, clock and reset, bridge, interrupt, PPROT, tie-offs, manager timeout, and doc parity — is verified.
Figure 1 — the APB peripheral in its SoC context, with the ten checklist categories anchored to the connections they govern. The manager and upstream AHB/AXI-to-APB bridge on the left drive the shared common signals (PADDR, PWRITE, PWDATA, PENABLE) onto the APB bus; the address decoder examines PADDR against the address map and asserts exactly one PSEL per slave (one-hot). Each peripheral — the one under review plus its neighbours — receives the broadcast signals and its dedicated PSEL, and drives its own PRDATA and PREADY into a shared return mux that feeds back through the bridge to the manager. PCLK and PRESETn fan out to every slave; the reviewed peripheral's interrupt routes to the interrupt controller. Ten labelled touchpoints mark where each checklist category is verified: (1) address map at the decoder input, (2) decoder and PSEL fan-out, (3) the shared return path and its default-ready for unselected slaves, (4) the clock and reset tree, (5) the upstream bridge's wait and error mapping, (6) interrupt routing to the controller, (7) PPROT/security attributes on the bus, (8) tie-offs on unused inputs, (9) the manager-side timeout guarding the fabric, and (10) register-map doc parity against the RTL. The figure frames the checklist as a wiring audit: every category is a connection you can point at, and a missing one fails the flight, not the part.

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

CategoryCheck (WHAT)Why / failure caught
Address mapPeripheral has a base address and size assigned from the SoC mapUnassigned base → the decoder never matches, PSEL never asserts, every access reads zero or hangs
Address mapThis window does not overlap any other peripheral's windowOverlap → the decoder asserts two PSEL lines, two slaves drive the return, PRDATA contention / X on reads
Address mapBase 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 mapThe window fits inside the decoder's compare width and the bridge's PADDR rangeWindow above the decoded PADDR bits → high addresses alias down onto another peripheral silently

Decoder & PSEL wiring

CategoryCheck (WHAT)Why / failure caught
Decoder & PSELExactly one PSEL output feeds this slave, and it is this slave's aloneShared/swapped PSEL → the wrong peripheral answers for this address; two respond at once
Decoder & PSELSelects are one-hot (or all-zero) across the whole fan-outNon-one-hot → simultaneous selects = bus fight; verify with a one-hot assertion, not by eye
Decoder & PSELEvery legal address selects exactly one slave; unmapped addresses hit a default that completesNo default arm → an unmapped access asserts no PSEL, nobody drives PREADY, the bus hangs forever
Decoder & PSELPSEL 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

CategoryCheck (WHAT)Why / failure caught
Shared return pathAn 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 pathThe PRDATA return mux selects strictly on the one-hot select — one driver at a timeTwo drivers → read-data bus fight, X or corrupted reads on an unrelated transaction
Shared return pathUnselected slaves drive PRDATA = 0 (or are muxed out), never fighting the shared busAn idle slave holding stale PRDATA onto the shared line corrupts the addressed slave's read

Clock, reset, bridge, interrupt, protection, tie-offs, manager, doc

CategoryCheck (WHAT)Why / failure caught
Clock & resetPCLK reaches the slave and is the same clock the return path samplesWrong/ungated clock → CDC on the return path, metastable PREADY, intermittent hangs
Clock & resetPRESETn is active-low and its polarity matches the slave's conventionInverted polarity → the slave is held in reset forever (reads all-zero) or never resets (X registers)
Clock & resetThe 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 bridgeThe bridge propagates the slave's wait states (PREADY) back to the AHB/AXI sideWait not propagated → the fast side completes early and samples PRDATA before it is valid
Upstream bridgeThe bridge maps PSLVERR to the upstream error response (SLVERR/HRESP)Error swallowed → a failed write reports success upstream; silent data-corruption in software
Interrupt routingThe slave's interrupt output is wired to the specific controller input it is documented againstMis-routed IRQ → the wrong ISR runs, or the interrupt is tied off and the event is lost entirely
PPROT / securityPPROT is driven from the manager and the slave (or a wrapper) honours the required privilege/security levelUnchecked PPROT → an unprivileged access reaches a secure register; a security-review escape
Unused tie-offsPSTRB and PPROT (when unused) are tied to defined defaults, not left floatingFloating PSTRB → X byte-enables, partial or corrupted writes; floating PPROT → undefined protection
Manager backpressureThe manager/bridge has a timeout or watchdog on PREADYNo timeout → one slave that never asserts PREADY hangs the entire fabric, not just its own access
Register-map docThe register-map document (offsets, widths, reset values, access) matches the RTLDoc/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:

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

Two 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.

DecisionOption AOption BWhen to choose which
Check granularityOne atomic connectivity fact per rowCoarse "address map OK" rowsAtomic — each maps to one inspectable wire/constant; coarse rows hide the overlap that ships
Unselected PREADYSlave drives ready=1 when idleSlave muxed entirely out of the returnEither is correct; the sin is driving it low — pick one convention and enforce it fabric-wide
Unmapped addressDecoder default arm completes with PREADY=1Rely on a manager timeout to recoverDefault arm — it completes cleanly; the timeout is a backstop, not the primary handling
PPROT handlingSlave/wrapper checks and errors on violationTie off and ignore protectionCheck it for any secure/privileged register; tie off only when the whole peripheral is non-secure by design
Doc parityGenerate register map and RTL from one sourceMaintain doc and RTL by handSingle-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_uart correctly and the UART drives PREADY=1 on its completing edge — yet the shared PREADY at the bridge stays low. Following the return mux backward, the newly-added timer — which is not selected — is driving pready_timer = 0 while 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 PREADY defaults 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 Mentor
    Snippet
    // 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 PREADY low, 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 Mentor
    Snippet
    // 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/PRDATA backward through the mux and check every idle slave's default, exactly the discipline the checklist's return-path category enforces up front.
Two panels of the same APB read to a UART with an unselected timer sharing the return path. The top wrong panel in red shows the idle timer driving PREADY low and the return path ANDing readies, so the shared PREADY at the bridge stays low and the UART access hangs. The bottom right panel in green shows each slave defaulting PREADY high when unselected and the return path muxing on the one-hot select, so the idle timer contributes nothing and the UART access completes.
Figure 2 — a representative integration pitfall the checklist catches, shown wrong versus right. Both panels show a manager reading the UART while a newly-added, unselected timer shares the return path. Top (WRONG, red): the idle timer drives its PREADY low and the return path ANDs the ready lines, so even though the selected UART asserts PREADY=1, the shared PREADY at the bridge is pulled low by the unselected timer — the UART's access hangs, a non-local failure on a peripheral that was never touched. Bottom (RIGHT, green): each slave defaults PREADY high when unselected and the return path muxes strictly on the one-hot select, so the idle timer contributes nothing, the UART's PREADY=1 reaches the bridge, and the access completes. The figure marks the shared-line touchpoint the return-path checklist category inspects, and captions the lesson: the bug is in the wiring at the seam, not in either slave's own logic, and it only appears once a second peripheral shares the return — which is why a standalone testbench never sees it.

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 Mentor
    Snippet
    // 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 PREADY low — 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

  1. 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.
  2. Find the overlap. Given a UART at 0x4000_0000 size 4 KB and a timer at 0x4000_0800 size 4 KB, state whether they overlap, what breaks if they do, and which checklist row catches it.
  3. Reason about the default-ready. Explain why an unselected slave driving PREADY low hangs a different slave, and write the per-slave assertion that proves it never does.
  4. 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.
  5. Audit reset. For a freshly-instanced slave, list the two distinct PRESETn failures (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 PREADY low, 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 of PRDATA), clock & reset (PCLK, PRESETn polarity and connectivity), upstream bridge (wait propagation, PSLVERR mapped), 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.