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 PSLVERR and 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 PENABLE is low, PREADY can 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 PREADY hangs 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:

  • PSLVERR is valid only in the last cycle of a transfer, when PSEL, PENABLE and PREADY are all high. Its value at any other time means nothing.
  • It is recommended, but not required, to drive PSLVERR low 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.
  • PSLVERR is 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
Output
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;
Output
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 PREADY low in the Access phase; the requester holds the request steady meanwhile.
  • PREADY can have any value when PENABLE is low.
  • PSLVERR is valid only in the completing cycle, when PSEL, PENABLE and PREADY are all high.
  • A failed write may or may not have changed the register, and a failed read may return invalid data.
  • Bridges turn PSLVERR into SLVERR on AXI or an ERROR response on AHB.
Did you find what you were looking for?

Quiz

No quiz questions available for this article.