Phase 1 — Spec & Environment Setup
Before writing a single line of RTL, we lock down the parameter package, define every APB signal exactly as the spec describes them, set up the SystemVerilog interface with modports, and wire in protocol-level assertions that will fire the moment something violates the spec — in every phase that follows.
What is APB and why does it matter?
The Advanced Peripheral Bus (APB) is the simplest bus in the AMBA family. Where AXI handles high-bandwidth pipelined transactions and AHB bridges the processor to memory, APB handles the last-mile job: talking to slow, low-bandwidth control registers — UARTs, GPIO banks, timer config registers, interrupt controllers.
The spec’s guiding design constraint is stated in §1.1:
APB is optimized for minimal power consumption and reduced interface complexity. All signal transitions are related only to the rising edge of the clock. Every transfer takes at least two cycles.
Those two constraints — clock-edge alignment and 2-cycle minimum — are the root of every rule in this series. When something goes wrong in an APB design, tracing back to one of these two constraints almost always reveals the bug.
The 10 APB signals — Table 4-1
The entire APB interface is just 10 signals. Memorise the source of each — many bugs come from driving a signal from the wrong side.
| Signal | Source | Direction | Description |
|---|---|---|---|
| PCLK | Clock | IN | Rising edge clocks all transfers |
| PRESETn | System bus | IN | Active LOW reset — always connected to system reset |
| PADDR | APB bridge | OUT | Address bus, up to 32 bits |
| PSELx | APB bridge | OUT | Per-slave select. One signal per peripheral |
| PENABLE | APB bridge | OUT | HIGH in 2nd+ cycle (ACCESS phase) |
| PWRITE | APB bridge | OUT | HIGH = write, LOW = read |
| PWDATA | APB bridge | OUT | Write data, driven when PWRITE=1 |
| PREADY | Slave | IN | LOW extends transfer (wait states) |
| PRDATA | Slave | IN | Read data, driven by slave when PWRITE=0 |
| PSLVERR | Slave | IN | Error response. Optional — tie LOW if not used |
PRESETn is active LOW. Every designer wires it correctly on paper and then
forgets to invert the polarity in simulation. The first thing our Phase 3 testbench checks
is that the DUT is properly held in reset when PRESETn=0.
The 3-state machine — §3 Figure 3-1
APB has exactly three operating states. The entire bus protocol flows from this diagram. Every timing figure in Chapter 2 of the spec is just this state machine drawn on a clock grid.
PENABLE = 0
PENABLE = 0
PENABLE = 1
IDLE: default state. PSELx=0, PENABLE=0. The bus sits here until a master
initiates a transfer.
SETUP: PSELx goes HIGH, PENABLE stays LOW. This is the first clock cycle of
every transfer — address, write, and data signals are driven here and must be stable by the
next rising edge. The bus stays in SETUP for exactly one cycle.
ACCESS: PENABLE goes HIGH. This is the second cycle. The slave can extend
this by holding PREADY LOW (wait states). The transfer completes on the rising edge where
PREADY=1.
SETUP is always exactly 1 cycle. There is no “SETUP with wait states”. Wait states only exist in the ACCESS phase, controlled by PREADY. This is why every transfer is at least 2 cycles.
Code walkthrough — apb_pkg.sv
The package defines all shared types, parameters, and the state encoding. It goes into the LEFT pane (design.sv) on EDA Playground.
State encoding
typedef enum logic [1:0] { IDLE = 2'b00, // Default state — no transfer in progress SETUP = 2'b01, // PSELx=1, PENABLE=0 (exactly 1 cycle) ACCESS = 2'b10 // PSELx=1, PENABLE=1 (1+ cycles) } apb_state_t;
Using a named enum instead of bare 2-bit logic gives you readable waveforms in EPWave —
you will see SETUP and ACCESS instead of 01 and 10.
Why logic [1:0] instead of reg?
SystemVerilog’s logic type replaces both reg and wire.
It can be driven by a single always_ff or assign block.
Using reg in 2024 is legal but signals to the reader that you are writing Verilog, not SystemVerilog.
Code walkthrough — apb_if.sv
The interface bundles all 10 APB signals plus modports (which enforce direction rules at compile time) plus 6 SVA assertions that check the protocol throughout simulation.
Modport: master side
modport master_mp ( output PADDR, PSELx, PENABLE, PWRITE, PWDATA, // bridge drives input PREADY, PRDATA, PSLVERR, // bridge reads input PCLK, PRESETn );
The modport makes it a compile-time error to accidentally read PADDR from the slave side or drive PREADY from the master side. This catches a whole class of wiring bugs before simulation.
The 6 SVA assertions — always-on protocol checkers
We add assertions in Phase 1, before any RTL exists, so they are active in every subsequent phase. When Phase 3 testbench accidentally creates a race condition, the assertion fires immediately with a time-stamp — no need to hunt through waveforms.
Running Phase 1 on EDA Playground
Open EDA Playground
Go to edaplayground.com and log in with Google or GitHub. Registration is free.
Select simulator
From the Tools & Simulators dropdown, choose Aldec Riviera-PRO.
This gives full SystemVerilog + SVA support. Icarus Verilog 12 also works but won’t fire SVA assertions.
Paste design.sv → LEFT pane
Copy the entire phase1/design.sv file (package + interface with assertions) into the left editor panel.
Paste testbench.sv → RIGHT pane
Copy phase1/testbench.sv into the right panel. This drives the interface signals and exercises the SVA assertions.
Tick “Open EPWave after run”
Enable the checkbox at the bottom left. This opens the browser-based waveform viewer automatically when simulation finishes.
Click Run and check console
You should see Phase 1 PASSED - Interface & SVA OK. No [SVA FAIL] lines should appear.
Open EPWave — add signals
Click Get Signals → select all → Apply. Look for:
PCLK, PSELx, PENABLE, PSLVERR, PRESETn.
Verify PSELx goes HIGH before PENABLE.
Expected console output
SETUP phase: PSELx=1 PENABLE=0 ACCESS phase: PSELx=1 PENABLE=1 PREADY=1 Transfer complete - deasserting Testing PSLVERR in valid window Phase 1 PASSED - Interface & SVA OK Open EPWave to verify signal timing
Use $dumpfile("dump.vcd") and $dumpvars(0, tb) for EPWave.
Do not use $fsdbDumpfile — that is VCS/Verdi only and will cause a compile error on EDA Playground.
