Not every peripheral can answer in one cycle. A register that lives in a slow memory, or behind a clock domain crossing, needs more time, and some accesses should fail outright. APB3 added two signals for this: PREADY lets a completer stretch the Access phase with wait states, and PSLVERR lets it report an error. This article explains the rules for both and builds a completer that uses them.
8 min read | Intermediate
What You'll Learn
- Insert wait states with
PREADY - Report errors with
PSLVERRand know when it is valid - Decide what a write or read that fails should do
- Explain how bridges pass APB errors back to AXI and AHB
Wait States with PREADY
An APB transfer completes at the rising edge where PSEL, PENABLE and PREADY are all high. A completer that needs more time simply holds PREADY low in the Access phase. Each cycle it does so is one wait state. The requester keeps PENABLE high and holds every other signal steady until PREADY goes high.
The specification lists the signals that must not change while PREADY is low: PADDR, PWRITE, PSEL, PENABLE, PWDATA, and in APB4 and APB5 PSTRB, PPROT and the user signals. A completer can therefore take as long as it needs and still read a stable request.
Two rules make PREADY easy to generate:
- PREADY only matters in the Access phase. When
PENABLEis low,PREADYcan have any value. A completer can drive a ready flag directly without gating it. - The requester never gives up. APB has no timeout. A completer that never raises
PREADYhangs the bus, so a slow completer must always finish eventually.
Errors with PSLVERR
PSLVERR reports that a transfer failed. Typical reasons are an address with no register behind it, a write to a read-only register, or an access that the peripheral cannot perform in its current state. The rules are short:
PSLVERRis valid only in the last cycle of a transfer, whenPSEL,PENABLEandPREADYare all high. Its value at any other time means nothing.- It is recommended, but not required, to drive
PSLVERRlow outside that cycle. - A write that gets an error might still have changed the register. Whether it did is up to the peripheral, and software must not assume either way.
- A read that gets an error can return invalid data. The completer does not have to drive zeros, so the error cannot be used to hide a value.
PSLVERRis optional. A completer without it has the requester's input tied low.
Quick Check
Q: A completer drives PSLVERR high during the Setup phase of a transfer and low in the Access phase. Did the transfer fail?
Show Answer
No. PSLVERR only counts in the cycle where PSEL, PENABLE and PREADY are all high. It was low then, so the transfer succeeded.
A Completer with Wait States and Errors
This completer has three kinds of registers:
- CTRL at 0x000, a normal register that answers at once
- BUF at 0x010 to 0x01C, four words in a slow memory that need two wait states
- ID at 0x020, a read-only identification value; writing it is an error
Any other address is unmapped and also returns an error.
module apb_wait (
input PCLK,
input PRESETn,
input PSEL,
input PENABLE,
input PWRITE,
input [11:0] PADDR,
input [31:0] PWDATA,
output reg [31:0] PRDATA,
output PREADY,
output PSLVERR
);
reg [31:0] ctrl;
reg [31:0] buf_mem [0:3];
reg [1:0] wcnt; // wait-state counter for BUF accesses
wire hit_ctrl = (PADDR == 12'h000);
wire hit_buf = (PADDR[11:4] == 8'h01);
wire hit_id = (PADDR == 12'h020);
wire access = PSEL && PENABLE;
// BUF accesses wait two cycles in the Access phase, everything else is ready at once
assign PREADY = hit_buf ? (wcnt == 2'd2) : 1'b1;
// errors: writes to the read-only ID register, and any unmapped address
assign PSLVERR = access && PREADY && ((hit_id && PWRITE) || !(hit_ctrl || hit_buf || hit_id));
always @(posedge PCLK) begin
if (!PRESETn)
wcnt <= 2'd0;
else if (access && hit_buf && !PREADY)
wcnt <= wcnt + 2'd1;
else
wcnt <= 2'd0;
end
always @(posedge PCLK) begin
if (!PRESETn) begin
ctrl <= 32'h0;
end
else if (access && PREADY && PWRITE) begin
if (hit_ctrl)
ctrl <= PWDATA;
else if (hit_buf)
buf_mem[PADDR[3:2]] <= PWDATA;
end
end
always @(*) begin
if (hit_ctrl)
PRDATA = ctrl;
else if (hit_buf)
PRDATA = buf_mem[PADDR[3:2]];
else if (hit_id)
PRDATA = 32'h0A9B_0001;
else
PRDATA = 32'h0;
end
endmodule
The counter wcnt counts Access cycles of a BUF transfer and PREADY rises when it reaches 2. The write condition now includes PREADY, so the slow memory is written once, at the edge where the transfer completes. A write to ID matches no branch, so the register really is read-only, and the error tells software that its write did nothing.
The Simulation
The testbench uses the same write and read tasks as in APB Bus Protocol, with a print of PSLVERR at the end of each transfer:
apb_write(12'h000, 32'h0000_0003); // CTRL: no wait states
apb_write(12'h014, 32'hBEEF_0014); // BUF[1]: two wait states
apb_read (12'h014, rdata);
apb_read (12'h020, rdata); // ID register
apb_write(12'h020, 32'hFFFF_FFFF); // write to a read-only register: error
apb_read (12'h100, rdata); // unmapped: error
45 write 0x000 = 0x00000003 95 write 0x014 = 0xbeef0014 145 read 0x014 = 0xbeef0014 175 read 0x020 = 0x0a9b0001 205 write 0x020 = 0xffffffff PSLVERR 235 read 0x100 = 0x00000000 PSLVERR
The CTRL write takes 30 ns, the BUF write 50 ns: two extra cycles in the Access phase. In the waveform, PREADY is low during the Setup cycle of the BUF transfers as well. That is allowed, because PREADY only counts once PENABLE is high. The last two transfers each complete in the normal two cycles, but with PSLVERR high in the completing cycle.
Quick Check
Q: The read of 0x100 returned 0x00000000. Can software treat that as the value of the register?
Show Answer
No. The read completed with PSLVERR, so the data is not valid. This completer happens to drive zero, but another completer might drive anything.
Where the Error Goes
The requester on APB is almost always a bridge, so PSLVERR has to be turned into the error response of the bus on the other side. An AXI-to-APB bridge maps it to a SLVERR response on RRESP for reads or BRESP for writes. An AHB-to-APB bridge maps it to an HRESP ERROR response. The processor then sees a bus fault and its software can handle it.
Whether to report an error at all is a design choice. Many peripherals return zero for unmapped addresses and ignore writes to read-only registers, without any error, because a fault in a driver is harder to debug than a value that does nothing. Others report every illegal access so that bugs show up early. Either way, write the choice into the register documentation.
Running It in ChipVerify Lab
The example contains this completer and the testbench. Run it and find the two wait states of each BUF transfer and the PSLVERR pulses at the end.
Common Mistakes
Mistake #1: Writing during every Access cycle
A completer with wait states that writes on PSEL && PENABLE && PWRITE performs the write in every cycle of the Access phase. Include PREADY in the condition so that the write happens once.
Mistake #2: Sampling PSLVERR in the wrong cycle
A requester or a checker that looks at PSLVERR while PREADY is still low can see a value that the completer has not finished computing. Sample it only at the completing edge.
Mistake #3: A completer that can stall forever
APB has no timeout. If PREADY waits for an event that may never happen, such as data from an external device, the bus and every processor that uses it hang. Raise PREADY and return an error instead.
Practice Exercise
Challenge: Three Wait States
The slow memory gets slower: it now needs three wait states. Change the completer and predict the completion time of each of the six transfers before you run it.
Hint
Only PREADY for BUF changes. The two BUF transfers each take one cycle longer, and every later transfer moves along with them.
Solution
assign PREADY = hit_buf ? (wcnt == 2'd3) : 1'b1;
45 write 0x000 = 0x00000003 105 write 0x014 = 0xbeef0014 165 read 0x014 = 0xbeef0014 195 read 0x020 = 0x0a9b0001 225 write 0x020 = 0xffffffff PSLVERR 255 read 0x100 = 0x00000000 PSLVERR
Each BUF transfer is 10 ns longer, so the BUF write ends at 105 instead of 95, and everything after the BUF read is 20 ns later. The two-bit counter still fits, since it counts to 3.
Open the ready-made example in the lab and run the simulation.
Summary
- A completer adds wait states by holding
PREADYlow in the Access phase; the requester holds the request steady meanwhile. PREADYcan have any value whenPENABLEis low.PSLVERRis valid only in the completing cycle, whenPSEL,PENABLEandPREADYare all high.- A failed write may or may not have changed the register, and a failed read may return invalid data.
- Bridges turn
PSLVERRintoSLVERRon AXI or an ERROR response on AHB.