AMBA APB · Module 10
APB2 Baseline
The original APB — the minimal two-phase, single-cycle, always-completes protocol with no PREADY, no PSLVERR, no byte strobes, and no protection. Understanding the baseline is what makes every later APB feature legible: each one is an answer to a specific limitation of this design.
Every APB feature you have learned — wait states, error responses, byte strobes, protection — was added to something. This chapter is that something: APB2, the original protocol, deliberately stripped to the bone. The single idea to carry: APB2 is the minimal, fully-synchronous, two-phase bus where every transfer takes exactly two cycles, always completes, always succeeds, and writes a full word — because it has no PREADY, no PSLVERR, no PSTRB, and no PPROT. Learn what that minimalism bought and what it cost, and every later version becomes legible as a targeted fix to one specific limitation of this baseline.
1. Problem statement
The problem APB2 was built to solve is attaching slow, simple control peripherals to a system bus as cheaply as possible — registers, timers, GPIO, UART config — without paying the area, timing, and verification cost of a high-performance bus.
In the original AMBA world, the high-performance bus (ASB, later AHB) carried the traffic that mattered for throughput: memory, DMA, the CPU instruction/data path. But hanging every trivial peripheral register directly off that bus was wasteful — each one would need to participate in pipelining, arbitration, and complex handshakes it did not need. APB2 was the answer: a bridge sits on the high-performance bus and translates accesses for a quiet, simple peripheral bus behind it. That bus had exactly three design goals:
- Trivially simple to implement in a peripheral. A slave should be almost combinational: decode the address, mux the register, done. No FSM beyond setup/access, no flow control to honour.
- Low power and low gate count. Signals toggle only during an active transfer; the bus is quiet otherwise. No speculative or pipelined activity.
- Easy to get right. A protocol a junior engineer can implement correctly the first time, because there is almost nothing to get wrong.
So the job of APB2 was not to be fast or flexible — it was to be the cheapest correct way to read and write a peripheral register. Every limitation we are about to list is a direct, intentional consequence of those three goals.
2. Why previous knowledge is insufficient
You have spent whole modules on PREADY (Module 8), PSLVERR (Module 9), and PSTRB/PPROT. That knowledge describes modern APB (APB3/APB4) — and it quietly assumes features that the original protocol does not have. To understand the evolution, you have to be able to subtract them:
- You have been assuming flow control that did not always exist. Every wait-state mental model rests on
PREADY. APB2 has noPREADYat all — a transfer is unconditionally two cycles, the slave must be ready, and there is no mechanism to stall. Reasoning about APB2 means reasoning about a bus where the subordinate has no voice in when the transfer completes. - You have been assuming a failure channel that did not always exist. Every error mental model rests on
PSLVERR. APB2 has no error reporting whatsoever — every transfer succeeds by definition. An unmapped address, a fault, a bad access: the bus cannot say so. Reasoning about APB2 means reasoning about a bus that cannot fail, only silently misbehave. - You have been assuming write granularity and access context that did not always exist.
PSTRB(byte lanes) andPPROT(privilege/security) are APB4. APB2 writes a full data word every time and carries no protection context. Reasoning about APB2 means a world with no partial writes and no access control.
So the model to build is subtractive: take everything you know about modern APB and remove the four signals that were added later. What remains — and what that bare protocol can and cannot do — is APB2, and it is the reference point the rest of this module measures against.
3. Mental model
The model: APB2 is a vending machine with no "please wait" light, no "out of stock" light, and no coin-value sensor. You make a selection (address + control), the machine always dispenses on a fixed two-step cycle, and it always claims success — there is no way for it to tell you it was busy, that the slot was empty, or that your coin was the wrong kind. Simplicity is the whole point; the cost is that every failure is silent.
Three refinements make it precise:
- Two phases, always, unconditionally. Every APB2 transfer is SETUP (one cycle:
PSELhigh,PENABLElow, address/control/write-data driven) then ACCESS (one cycle:PENABLEhigh, the slave captures a write or returns read data). It completes at the end of ACCESS — always, because there is noPREADYto extend it. Fixed two-cycle latency, no exceptions. - The slave has no voice and no verdict. It cannot say "not yet" (no
PREADY) and cannot say "that failed" (noPSLVERR). It must present read data or accept write data within the single ACCESS cycle, and the manager will assume success. The peripheral is effectively a clocked register file with an address decoder — nearly combinational. - Full-word, context-free. A write updates the whole data word — there is no byte-strobe to write just one lane (no
PSTRB) — and the access carries no privilege or security tag (noPPROT). What you get is the simplest possible read/write of a register, and nothing more.
4. Real SoC implementation
In silicon, an APB2 slave is almost the simplest synchronous block there is: an address decode, a register array, and a read mux — with no PREADY or PSLVERR to generate, because those signals do not exist in APB2.
// A minimal APB2 slave: no PREADY, no PSLVERR, no PSTRB, no PPROT.
// The transfer is unconditionally two cycles; the slave MUST keep up.
module apb2_slave #(parameter AW = 8) (
input pclk, presetn,
input psel, penable, pwrite,
input [AW-1:0] paddr,
input [31:0] pwdata,
output reg [31:0] prdata
// NOTE: there is no pready output -> the manager cannot be stalled.
// NOTE: there is no pslverr output -> the slave cannot report an error.
);
reg [31:0] ctrl, status; // a tiny CSR bank
// WRITE commits in the ACCESS cycle (psel & penable & pwrite).
// Full word only: every bit of pwdata is written — no byte strobes exist.
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) ctrl <= 32'b0;
else if (psel && penable && pwrite && (paddr == 8'h00))
ctrl <= pwdata; // whole 32-bit word, unconditionally
end
// READ is a combinational mux presented during ACCESS. It MUST be ready
// this cycle — there is no way to insert a wait. An unmapped address simply
// returns a default; the bus has no channel to flag it as illegal.
always_comb begin
case (paddr)
8'h00: prdata = ctrl;
8'h04: prdata = status;
default: prdata = 32'h0; // silent: no PSLVERR to say "bad address"
endcase
end
endmoduleTwo facts define APB2 design. First, the slave must be able to respond in the single ACCESS cycle, every time — there is no back-pressure, so a peripheral whose data genuinely needs more time (a slow memory, a clock-crossing) simply cannot be a correct APB2 slave; you either guarantee single-cycle response or you need APB3's PREADY (the rationale for which is its own chapter). Second, every failure is silent: an unmapped address returns whatever the default mux gives, a fault is invisible, and the manager records success regardless — which is exactly the gap APB3's PSLVERR was created to close (its rationale chapter). APB2's RTL is short precisely because it shoulders none of these responsibilities.
5. Engineering tradeoffs
APB2's minimalism is a set of deliberate trades — each "missing" feature bought simplicity and cost capability.
| What APB2 omits | What the omission buys | What it costs | The later fix |
|---|---|---|---|
PREADY (flow control) | Trivial slaves; fixed two-cycle latency; no handshake logic | Slow peripherals cannot be attached correctly | APB3 PREADY (wait states) |
PSLVERR (error channel) | No error logic; every transfer "succeeds" | Faults and bad addresses are silent | APB3 PSLVERR |
PSTRB (byte lanes) | Simplest write path — one full word | No partial-word / sub-word writes | APB4 PSTRB |
PPROT (access context) | No protection logic anywhere | No privilege/security enforcement | APB4 PPROT |
| Pipelining / bursts (never added) | A genuinely quiet, low-power control bus | Low throughput — one transfer at a time | (by design — use AHB/AXI for throughput) |
The throughline: APB2 was right for its job. The features added later did not fix "mistakes" — they extended APB into domains (slow peripherals, fault reporting, sub-word writes, security) the original deliberately excluded. Knowing which limitation each version targets is the entire point of being "version-aware," and it is what lets you read a datasheet that says "APB3-compliant" and immediately know what the IP can and cannot do.
6. Common RTL mistakes
7. Debugging scenario
The signature APB2-era bug is a version mismatch at integration — wiring a peripheral or bridge that expects modern APB into an APB2 environment (or vice versa), where the missing signals default to values that look fine until a specific case hits.
- Observed symptom: a peripheral that worked in unit simulation hangs or returns stale data when integrated. A register read intermittently returns the previous register's value, and one specific peripheral occasionally locks the bus during bring-up — but only under load.
- Waveform clue: on the bus trace there is no
PREADYline at all (it is an APB2 segment), yet the integrated slave is one that internally needs two cycles for its read data. In the ACCESS cycle the manager samplesPRDATAunconditionally — before the slave's data mux has settled — and captures the stale value. There is noPREADYgoing low to protect it, because the protocol has none. - Root cause: an APB3-class slave (one that assumes it can assert
PREADY=0to buy a cycle) was dropped onto an APB2 manager that has noPREADYinput — so the slave's wait request is simply not connected to anything. The manager completes every transfer in two cycles regardless, and any access the slave could not actually service in one cycle returns garbage. The "hang" case is the same slave tied behind a bridge that does wait on aPREADYthat the APB2 manager never drives — so it floats and is read as perpetually-not-ready. - Correct RTL: match the protocol version on both sides. Either make the slave a true single-cycle APB2 slave (guarantee combinational read data in the ACCESS cycle, no internal wait), or upgrade the segment to APB3 so a real
PREADYexists end-to-end and is wired through. Never leave aPREADYdangling: tie an absentPREADYinput high (always-ready) at the boundary so a mixed-version stitch degrades safely rather than floating. - Verification assertion: assert the APB2 timing contract explicitly — every transfer completes in exactly two cycles:
assert property (@(posedge pclk) disable iff(!presetn) (psel && !penable) |=> (psel && penable));(SETUP is always followed by ACCESS next cycle) — and, at any APB2/APB3 boundary, assert that no signal the manager does not drive is left floating:assert property (@(posedge pclk) !$isunknown(pready_at_boundary));. - Debug habit: when an integrated APB peripheral misbehaves, first establish the protocol version of the segment — is there a
PREADY? aPSLVERR? aPSTRB? Check the signal list, not just the values. A huge class of APB bugs is a version mismatch where a feature one side relies on simply does not exist on the other, so its signal floats or is ignored. Identify the version before you trace a single edge.
8. Verification perspective
Verifying an APB2 component is about pinning the fixed contract and proving the component never relies on a feature the version does not have — and a modern UVM APB agent must be told it is in APB2 mode or it will check for signals that are not there.
- Assert the unconditional two-cycle transfer. APB2's defining property is that every transfer is SETUP then ACCESS with no extension. Bind a property that
PENABLEalways rises the cycle afterPSELand the transfer completes at that ACCESS edge — there is noPREADY, so any check that waits for one is wrong for APB2. This is the single most important APB2 assertion: it proves the absence of flow control is respected. - Prove no reliance on absent signals. A slave intended for APB2 must produce valid read data combinationally in the ACCESS cycle and must commit writes that same cycle — verify there is no internal state that would need a wait. Conversely, configure the bus VIP/agent for the correct version so it does not flag a missing
PREADY/PSLVERRas an error, and so it does not drive signals the DUT will not read. A version-mismatched testbench produces false failures (or, worse, false passes) before the DUT is even exercised. - Cover the version boundary explicitly. When an APB2 component sits at a bridge to an APB3/APB4 segment, functional coverage must include the adaptation: an APB3 access down-converted to APB2 (what happens to
PSTRB/PPROT/PSLVERR?), and an APB2 access seen by an APB3 master (isPREADYcorrectly tied high?). The bugs live at these seams, so the coverage model must name them.
The point: APB2 verification is the discipline of checking a smaller contract correctly — every transfer two cycles, always completes, no flow control, no error channel — and of configuring the testbench to the right version so it neither demands nor ignores the wrong signals.
9. Interview discussion
"What's the difference between APB2 and APB3/APB4?" is a fast integration-experience filter, and the cleanest way to answer it is to start from the baseline: describe APB2 as the minimal core, then name each later signal as a fix to one specific limitation.
Frame it as subtraction: APB2 is two-phase (SETUP/ACCESS), fully synchronous, unconditionally two cycles per transfer, with no PREADY, no PSLVERR, no PSTRB, and no PPROT — so every transfer always completes, always "succeeds," writes a full word, and carries no access context. Then map the evolution: PREADY (APB3) added flow control so slow peripherals can insert wait states; PSLVERR (APB3) added an error channel so faults and bad addresses stop being silent; PSTRB (APB4) added byte lanes for sub-word writes; PPROT (APB4) added privilege/security context. The depth signal is to stress that the two-phase foundation never changed — every version is "APB2 plus signals," which is why the baseline is worth knowing — and to close with the practical payoff: when a datasheet says "APB3-compliant," you instantly know it has wait/error support but may lack byte strobes, and you know to check the bridge for how it adapts versions. That is exactly the version-aware judgment integration work demands.
10. Practice
- List the baseline. From memory, write the complete APB2 signal set and then add, in order, the signals each later version introduced — labelling which version added which.
- Time the transfer. Draw an APB2 read and an APB2 write; mark SETUP and ACCESS and the exact cycle the transfer completes, and explain why the latency is fixed.
- Find the silent failure. Given an APB2 read of an unmapped address, state exactly what the manager observes and why it cannot tell the access was illegal — then name the version/signal that fixes it.
- Spot the impossible slave. Given a peripheral whose read data needs two internal cycles, explain why it cannot be a legal APB2 slave and what minimum version it requires.
- Diagnose the mismatch. Given an integrated peripheral returning stale data on a bus with no
PREADY, walk the steps to identify it as a protocol-version mismatch rather than a timing bug.
11. Q&A
12. Key takeaways
- APB2 is the minimal baseline: two-phase (SETUP/ACCESS), fully synchronous, unconditionally two cycles per transfer, with no
PREADY, noPSLVERR, noPSTRB, and noPPROT. - Every transfer always completes and always "succeeds." There is no flow control to extend it and no error channel to flag it — a slow peripheral cannot be a legal APB2 slave, and a bad access fails silently.
- Writes are full-word and context-free. No byte strobes means no sub-word writes; no protection bits means no privilege or security enforcement.
- The minimalism was the point, not a mistake. APB2 was the cheapest correct way to read/write a control register; later versions added signals to extend APB into domains it deliberately excluded.
- The two-phase core never changed — APB3 and APB4 are "APB2 plus signals" — which is why knowing the baseline makes every later feature legible as a fix to one specific limitation.
- A huge class of APB bugs is a version mismatch at a boundary, where a feature one side relies on does not exist on the other and its signal floats or is dropped. Establish the segment's version — check the signal list — before tracing edges.