APB (Advanced Peripheral Bus) is the simplest member of the AMBA family, designed for low-bandwidth, low-power peripherals where speed is irrelevant but gate count and static timing simplicity are everything. It never sits directly on the main system bus — instead, an AHB-to-APB or AXI-to-APB bridge translates the high-performance protocol down to APB's much simpler signaling for everything behind it: UARTs, GPIO banks, timers, watchdogs, interrupt controllers, and the dozens of small configuration and status registers every SoC accumulates.
Because none of those blocks need real bandwidth, APB trades pipelining for simplicity. Every transfer walks through three states — IDLE, SETUP, and ACCESS — one at a time, with no overlap between transfers the way AHB or AXI allow. In IDLE the bus sits quiet. When a master needs to talk to a peripheral, the bus moves to SETUP for exactly one cycle: the address (PADDR), the selected slave (PSELx), and the direction (PWRITE) are all driven, but PENABLE stays low and no data moves yet.
On the next clock edge the bus moves to ACCESS and PENABLE goes high, telling the slave the transfer is actually happening — for a write, PWDATA is sampled; for a read, the slave drives PRDATA. If the slave needs more time it holds PREADY low and the ACCESS phase simply stretches for as many cycles as needed, with every other signal frozen until PREADY goes high.
A slave can also flag a problem transfer with PSLVERR instead of completing normally. Once the transfer completes, the bus either drops straight back to IDLE, or — if the very next transfer targets the same peripheral — skips straight back into SETUP without an idle cycle in between. This non-pipelined structure is exactly why APB works so well as the terminus of a bus hierarchy: with only one transfer in flight at any time, timing closure across a peripheral block with forty-plus scattered instances is dramatically easier than it would be if every peripheral had to speak AHB or AXI natively.
The price is throughput — an APB bus simply cannot be used for anything performance-sensitive — but for register pokes and status polling, that tradeoff is the entire point.