Wait states and PREADY
Stretch a transfer with PREADY, know which signals must hold still while it waits, and say exactly when read data is valid.
- Lesson
- P3.3
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
Every register of a new peripheral read back the value of the read before it. The peripheral registered PRDATA at the completion edge, so the data arrived one cycle after the requester had taken it. Its unit test read each register twice in a row and passed.
The token is what the bridge takes: PRDATA at the completion edge.
- Cycle 4
The write of 0x11 to CTRL completes.
- Cycle 6
The read of CTRL completes.
PRDATAis still 0: the bridge takes 0. - Cycle 7
CTRL's value appears now, one cycle late, while the bus is already in the setup of the next read.
- Cycle 8
The read of the ID register completes and takes 0x11, the value of CTRL.
- Cycle 9
The ID value arrives after its transfer has ended. Nobody takes it.
Measured on the bench with p0.late_rdata = 1. No rule on the wires is broken; every value is simply one transfer behind.
PREADY stretches the access
In the access phase the completer drives PREADY. While it is 0 the transfer waits: the bus stays in ACCESS, one cycle per wait state, and the transfer completes at the first edge where it is 1. A completer may add any number of wait states, from zero upwards.
The bridge is ringed while the completer holds PREADY low.
- Cycle 3
Setup of the write of 0xbeef.
- Cycle 4
Access,
PREADY= 0: the first wait state. Every signal the bridge drives keeps its value. - Cycle 6
Second wait state over:
PREADY= 1 and the write completes. One transfer, four edges. - Cycle 8
The read's data is on
PRDATAalready, butPREADYis 0. The bridge must not take it yet. - Cycle 10
The read completes and the bridge takes 0xbeef here, at the only edge that counts.
Measured on the bench with p0.waits = 2. The checker counts four wait edges.
PREADY matters only when PENABLE is 1. In IDLE and SETUP it may take any value, so a completer that never needs a wait state can tie it high. The bench's twin p0.ready_in_setup = 1 drives it high outside the access phase: the transfers and the checker's report are identical.
Hold still
While PREADY is 0 the requester keeps everything it set in SETUP. The completer may sample any of these signals at any edge of the transfer and must see one consistent request.
| Held while PREADY is 0 | For |
|---|---|
PADDR, PWRITE, PSELx, PENABLE | every transfer |
PPROT, PAUSER | every transfer, when present |
PWDATA, PSTRB, PWUSER | writes |
PRDATA, PSLVERR | not held: completer outputs, valid only at completion |
The same values must also not change between SETUP and the first access cycle.
A requester that moves the address during a wait state writes somewhere else. The completer acts on what it sees at the completion edge, and by then the address has moved.
p0.waits = 2;
move_in_wait = 1; // PADDR moves by 4 in the first wait cycle
apb_write(32'h000, 32'h55667788);
dump(32'h000, 8);
// RULE stable broken at edge 5: PADDR changed from 0x0 to 0x4 inside a transfer
// 0x0000 00 00 00 00 88 77 66 55When read data is valid
PRDATA has to be correct at one edge only: the completion edge. Before that the completer may drive anything, and the requester must not look. The bench completer fetches its data in SETUP and holds it, as most register blocks do, but a completer may also produce the data in the last wait cycle.
Data ready by the completion edge
apb_write(32'h100, 32'h11);
apb_write(32'h004, 32'h22);
apb_read(32'h100);
apb_read(32'h004);
apb_read(32'h110);
Reads return 0x11, 0x22 and 0x41504234.
Data registered at the completion edge
p0.late_rdata = 1;
apb_write(32'h100, 32'h11);
apb_write(32'h004, 32'h22);
apb_read(32'h100);
apb_read(32'h004);
apb_read(32'h110);
Reads return 0, 0x11 and 0x22: each one the value of the read before.
No rule on the wires catches this, because PRDATA has no rule of its own. A bench catches it by reading registers whose values differ, in an order where a stale value cannot pass for the right one.
A wait with no end
The specification puts no upper limit on wait states. A completer that never raises PREADY holds its transfer, and the bridge cannot start another one. Every peripheral on that APB bus becomes unreachable, and the manager that started the access waits too. The bench checker has a watchdog that is not a protocol rule. After 16 wait cycles it names timeout.
p0.hang = 1;
apb_read(32'h100);
// RULE timeout broken at edge 19: no PREADY after 16 wait cycles
// e3-e28 READ 0x0100 no PREADY after 24 wait cycles, the bench gives up
// RULE stable broken at edge 29: PSEL dropped before PREADYSystems guard against this outside APB, with a timeout in the bridge or the interconnect that answers an error upstream. Lesson P3.4 shows how errors reach the requester.
Where you will meet it
Every completer that reads a slow memory, crosses a clock domain or waits for a hardware block uses wait states. A bench for it runs every register with zero, one and many wait states, holds every request signal still, and reads values that differ from register to register.
Asked in interviews: wait states
How does an APB completer insert wait states?
It drives PREADY low in the access phase. The transfer completes at the first access edge where PREADY is high; each low cycle adds one wait state, with no upper limit in the protocol.
What must the requester keep stable during wait states?
PADDR, PWRITE, PSELx, PENABLE and PPROT, plus PWDATA and PSTRB for a write, and any user request signals. They also must not change between SETUP and the first access cycle.
Must PREADY be low in the setup phase?
No. PREADY is a don't-care whenever PENABLE is low, so a completer with no wait states can tie it high.
When must PRDATA be valid?
Only in the cycle the read completes, when PSEL, PENABLE and PREADY are all high. The requester samples it at that edge and ignores it at any other.
Try it
A transfer with n wait states takes n + 2 edges: one setup, n waits, one completion. The same read of the ID register takes two edges with no wait states and seven with five, and returns the same value both times. Wait states cost time and change nothing else.
Add move_in_wait = 1; before the first write: the checker names stable in every transfer that waits, and the write goes to 0x104, the read-only STATUS register, which answers with an error. Replace it with p0.late_rdata = 1;: the read of 0x100 returns 0 and the next read returns 0xbeef.
e3-e6 WRITE 0x0100 data=0x0000beef strb=1111 waits=2 OKAY e7-e10 READ 0x0100 data=0x0000beef waits=2 OKAY e11-e12 READ 0x0110 data=0x41504234 waits=0 OKAY e13-e19 READ 0x0110 data=0x41504234 waits=5 OKAY checker: 1 writes, 3 reads, 9 wait edges, 0 rules broken, 0 notesRead included file: apb_periph.sv
`timescale 1ns/1ns
// apb_periph.sv: an APB4 completer (read-only). Offsets are paddr[11:0]:
// 0x000-0x0ff RAM, 256 bytes, written lane by lane under PSTRB
// 0x100 CTRL read/write, byte-strobed
// 0x104 STATUS read-only: the number of words in the FIFO
// 0x108 WCOUNT read-only: counts completed writes (a zero-strobe write counts too)
// 0x10c FIFO a write pushes PWDATA, a read pops the oldest word (0 when empty)
// 0x110 ID read-only, 0x41504234 ("APB4")
// With err_on (the default) the completer answers PSLVERR for an unmapped offset, an unaligned
// PADDR, a write to a read-only register and, with priv_ctrl, an unprivileged access to CTRL;
// a transfer that gets PSLVERR changes nothing here (IHI 0024E 3.4 allows either choice).
// Switches (set from the stimulus):
// waits wait states in every access (PREADY low for that many access cycles)
// ready_in_setup drive PREADY high outside the access phase (a don't-care: the spec allows it)
// hang never raise PREADY
// err_on, priv_ctrl
// Broken twins:
// act_every_cycle acts at every edge PSEL is high, not once at completion
// late_rdata PRDATA registered at completion, so it arrives one cycle late
// err_early PSLVERR during setup and wait cycles, low at completion
// ignore_strobes writes every lane of a RAM word or CTRL
// ignore_psel decodes PADDR and acts in access cycles without looking at its PSEL
module apb_periph #(parameter NAME = "p0") (
input logic pclk,
input logic presetn,
input logic psel,
input logic penable,
input logic pwrite,
input logic [31:0] paddr,
input logic [31:0] pwdata,
input logic [3:0] pstrb,
input logic [2:0] pprot,
output logic pready,
output logic [31:0] prdata,
output logic pslverr);
int waits = 0;
bit ready_in_setup = 0, hang = 0, err_on = 1, priv_ctrl = 0, trace = 0;
bit act_every_cycle = 0, late_rdata = 0, err_early = 0, ignore_strobes = 0, ignore_psel = 0;
logic [7:0] mem [0:255];
logic [31:0] ctrl = 0, wcount = 0;
logic [31:0] fifo [0:15];
int fhead = 0, fcount = 0;
int wcnt = 0; // wait cycles spent in this access
logic [31:0] prdata_q = 0;
initial for (int i = 0; i < 256; i++) mem[i] = 0;
localparam int RAM = 0, CTRL = 1, STATUS = 2, WCOUNT = 3, FIFO = 4, ID = 5, NONE = 6;
function automatic int region(input logic [11:0] a);
if (a < 12'h100) region = RAM;
else if (a[11:2] == 10'h040) region = CTRL;
else if (a[11:2] == 10'h041) region = STATUS;
else if (a[11:2] == 10'h042) region = WCOUNT;
else if (a[11:2] == 10'h043) region = FIFO;
else if (a[11:2] == 10'h044) region = ID;
else region = NONE;
endfunction
function automatic bit bad(input logic wr, input logic [11:0] a, input logic [2:0] prot);
int r;
r = region(a);
bad = (r == NONE) || (a[1:0] != 0) || (wr && (r == STATUS || r == WCOUNT || r == ID))
|| (priv_ctrl && r == CTRL && !prot[0]);
endfunction
function automatic logic [31:0] value(input logic [11:0] a);
case (region(a))
RAM: value = {mem[{a[7:2], 2'd3}], mem[{a[7:2], 2'd2}], mem[{a[7:2], 2'd1}], mem[{a[7:2], 2'd0}]};
CTRL: value = ctrl;
STATUS: value = fcount;
WCOUNT: value = wcount;
FIFO: value = fcount > 0 ? fifo[fhead] : 32'h0;
ID: value = 32'h41504234;
default: value = 32'h0;
endcase
endfunction
wire sel = ignore_psel ? 1'b1 : (psel === 1'b1);
wire access = sel && (penable === 1'b1);
wire err = err_on && bad(pwrite, paddr[11:0], pprot);
assign pready = hang ? 1'b0 : (!access ? (ready_in_setup ? 1'b1 : 1'b0) : (wcnt >= waits));
wire done = access && pready;
assign pslverr = err_early ? (sel && !done && err) : (done && err);
// The read value is taken at the setup edge (PSEL 1, PENABLE 0) and held on PRDATA until the next
// read, the way most register blocks do it (the bus multiplexor decides who is heard);
// late_rdata registers it at completion instead.
logic [31:0] rdata_r = 0;
always @(posedge pclk) if (sel && penable !== 1'b1 && pwrite === 1'b0) rdata_r <= value(paddr[11:0]);
assign prdata = late_rdata ? prdata_q : rdata_r;
wire act = act_every_cycle ? sel : done;
always @(posedge pclk or negedge presetn) begin
if (!presetn) begin
ctrl <= 0; wcount <= 0; wcnt <= 0;
end else begin
wcnt <= (access && !pready) ? wcnt + 1 : 0;
if (done && !pwrite) prdata_q <= value(paddr[11:0]);
if (act && !err) begin
if (pwrite) begin
wcount <= wcount + 1;
case (region(paddr[11:0]))
RAM: for (int l = 0; l < 4; l++) if (pstrb[l] || ignore_strobes) mem[{paddr[7:2], 2'b00} + l] <= pwdata[8*l +: 8];
CTRL: for (int l = 0; l < 4; l++) if (pstrb[l] || ignore_strobes) ctrl[8*l +: 8] <= pwdata[8*l +: 8];
FIFO: if (fcount < 16) begin fifo[(fhead + fcount) % 16] <= pwdata; fcount <= fcount + 1; end
default: ;
endcase
if (trace) $display(" %0s e%0d: write 0x%03h <= 0x%08h strb=%b", NAME, ($time - 5) / 10, paddr[11:0], pwdata, pstrb);
end else begin
if (region(paddr[11:0]) == FIFO && fcount > 0) begin fhead <= (fhead + 1) % 16; fcount <= fcount - 1; end
if (trace) $display(" %0s e%0d: read 0x%03h -> 0x%08h", NAME, ($time - 5) / 10, paddr[11:0], value(paddr[11:0]));
end
end else if (act && trace) $display(" %0s e%0d: error, %0s 0x%03h ignored", NAME, ($time - 5) / 10, pwrite ? "write" : "read", paddr[11:0]);
end
end
endmodule
Read included file: apb_checker.sv
`timescale 1ns/1ns
// apb_checker.sv: the rules of an APB interface, checked at every rising edge (read-only).
// reset PSEL is 1 while PRESETn is low;
// known PSEL is x, or a control signal, a strobed write byte or PREADY in access is x;
// setup PENABLE is 1 without a setup cycle (PSEL 1, PENABLE 0) before it;
// setup_len a setup cycle not followed by an access cycle;
// stable PSEL, PADDR, PWRITE, PPROT, PSTRB or the write data changed inside a transfer,
// or PSEL or PENABLE dropped before PREADY;
// enable_drop PENABLE still 1 in the cycle after a completed transfer;
// strb_read PSTRB not zero during a read;
// onehot more than one PSELx at once;
// timeout no PREADY after TIMEOUT wait cycles (a liveness watchdog, not a protocol rule).
// A NOTE (not a broken rule): PSLVERR is 1 outside the completion cycle, which the spec allows
// and recommends against (IHI 0024E 3.4).
module apb_checker #(parameter int NSEL = 1, parameter int TIMEOUT = 16) (
input logic pclk,
input logic presetn,
input logic [NSEL-1:0] psel,
input logic penable,
input logic pwrite,
input logic [31:0] paddr,
input logic [31:0] pwdata,
input logic [3:0] pstrb,
input logic [2:0] pprot,
input logic pready,
input logic pslverr);
int broken = 0, notes = 0, writes = 0, reads = 0, waits = 0, edge_n = -1, stall = 0;
logic p_any = 0, p_en = 0, p_rdy = 0, p_wr = 0;
logic [NSEL-1:0] p_sel = 0;
logic [31:0] p_addr = 0, p_wdata = 0;
logic [3:0] p_strb = 0;
logic [2:0] p_prot = 0;
task automatic report(input string rule, input string msg);
broken++;
$display("RULE %0s broken at edge %0d: %0s", rule, edge_n, msg);
endtask
task automatic note(input string rule, input string msg);
notes++;
$display("NOTE %0s at edge %0d: %0s", rule, edge_n, msg);
endtask
always @(posedge pclk) begin
logic any, en, rdy, bytes_x;
edge_n = edge_n + 1;
any = (|psel) === 1'b1; en = (penable === 1'b1); rdy = (pready === 1'b1);
if (presetn !== 1'b1) begin
if (any) report("reset", "PSEL is 1 while PRESETn is low");
any = 0; en = 0;
end else begin
bytes_x = 0;
for (int l = 0; l < 4; l++) if (pstrb[l] === 1'b1 && (^pwdata[8*l +: 8]) === 1'bx) bytes_x = 1;
if ((^psel) === 1'bx) report("known", "PSEL is x");
else if (any && (^{paddr, pwrite, penable, pstrb, pprot}) === 1'bx) report("known", "a control signal is x while PSEL is 1");
else if (any && pwrite === 1'b1 && bytes_x) report("known", "a strobed byte of PWDATA is x");
else if (any && en && pready === 1'bx) report("known", "PREADY is x in an access cycle");
if (any && en && !(p_any && (!p_en || !p_rdy)))
report("setup", "PENABLE is 1 without a setup cycle (PSEL 1, PENABLE 0) before it");
if (p_any && !p_en && !(any && en))
report("setup_len", "the setup cycle was not followed by an access cycle");
if (p_any && (!p_en || !p_rdy) && any) begin
if (psel !== p_sel) report("stable", $sformatf("PSEL changed from %b to %b inside a transfer", p_sel, psel));
if (paddr !== p_addr) report("stable", $sformatf("PADDR changed from 0x%0h to 0x%0h inside a transfer", p_addr, paddr));
if (pwrite !== p_wr) report("stable", "PWRITE changed inside a transfer");
if (pprot !== p_prot) report("stable", "PPROT changed inside a transfer");
if (pstrb !== p_strb) report("stable", "PSTRB changed inside a transfer");
if (p_wr && pwdata !== p_wdata) report("stable", $sformatf("PWDATA changed from 0x%0h to 0x%0h inside a transfer", p_wdata, pwdata));
if (p_en && !en) report("stable", "PENABLE dropped before PREADY");
end
if (p_any && p_en && !p_rdy && !any) report("stable", "PSEL dropped before PREADY");
if (p_any && p_en && p_rdy && en) report("enable_drop", "PENABLE is still 1 in the cycle after a completed transfer");
if (any && pwrite === 1'b0 && pstrb !== 4'b0000) report("strb_read", $sformatf("PSTRB is %b during a read", pstrb));
if ($countones(psel) > 1) report("onehot", $sformatf("PSELx is %b: two completers selected", psel));
if (any && en && rdy) begin
if (pwrite === 1'b1) writes++; else reads++;
stall = 0;
end else if (any && en) begin
waits++; stall++;
if (stall == TIMEOUT) report("timeout", $sformatf("no PREADY after %0d wait cycles", TIMEOUT));
end
if (pslverr === 1'b1 && !(any && en && rdy))
note("pslverr", "PSLVERR is 1 outside the completion cycle (ignored by the requester; recommended LOW)");
end
p_any = any; p_en = en; p_rdy = rdy; p_sel = psel; p_addr = paddr; p_wr = (pwrite === 1'b1);
p_wdata = pwdata; p_strb = pstrb; p_prot = pprot;
end
final $display("checker: %0d writes, %0d reads, %0d wait edges, %0d rules broken, %0d notes", writes, reads, waits, broken, notes);
endmodule
Read included file: apb_tasks.svh
// apb_tasks.svh: the requester side of the bench, as tasks for the stimulus (read-only).
// apb_write(addr, data, strb, prot) and apb_read(addr, prot) run one transfer and leave the bus
// IDLE; q_write / q_read queue transfers and apb_run() sends the queue back to back with PSEL held
// high (ACCESS goes straight to SETUP). Each transfer prints one log line at its completion edge:
// e3-e4 WRITE 0x0100 data=0x11223344 strb=1111 waits=0 OKAY
// (setup edge - completion edge). The result of a read is in prdata_q and pslverr_q; the results
// of a queue are in rdv[i] and errv[i]. `cycle` counts rising edges from 0.
int cycle = -1;
always @(posedge pclk) cycle = cycle + 1;
bit quiet = 0;
// Requester bugs, for the broken twins:
bit move_in_wait = 0; // PADDR moves by 4 in the first wait cycle
bit enable_stuck = 0; // PENABLE stays 1 into the next queued transfer (no setup cycle)
bit strb_on_read = 0; // PSTRB = 1111 on reads
bit skip_setup = 0; // PSEL and PENABLE rise together
int give_up = 24; // the bench stops waiting after this many wait cycles
logic [31:0] prdata_q = 0;
logic pslverr_q = 0;
int waits_q = 0;
bit stuck_now = 0;
bit q_wr [0:63];
logic [31:0] q_addr [0:63], q_data [0:63], rdv [0:63];
logic [3:0] q_strb [0:63];
logic [2:0] q_prot [0:63];
bit errv [0:63];
int qn = 0;
// A text waveform for the trace blocks: one line per rising edge with the values sampled there.
bit watch = 0;
int wave_edge = -1;
always @(posedge pclk) begin
wave_edge = wave_edge + 1;
`ifdef APB_DECODER
if (watch) $display("wave e%0d: psel=%b pselx=%b penable=%b pwrite=%b paddr=%h pwdata=%h pstrb=%b pready=%b prdata=%h pslverr=%b",
wave_edge, psel, pselx, penable, pwrite, paddr, pwdata, pstrb, pready, prdata, pslverr);
`else
if (watch) $display("wave e%0d: psel=%b penable=%b pwrite=%b paddr=%h pwdata=%h pstrb=%b pready=%b prdata=%h pslverr=%b",
wave_edge, psel, penable, pwrite, paddr, pwdata, pstrb, pready, prdata, pslverr);
`endif
end
// One transfer: SETUP for one cycle, then ACCESS until PREADY. last = 0 keeps PSEL high for the
// next transfer.
task automatic apb_xfer(input bit wr, input logic [31:0] addr, input logic [31:0] data, input logic [3:0] strb,
input logic [2:0] prot, input bit last);
int s_edge, w;
paddr = addr; pwrite = wr; pwdata = wr ? data : 32'h0;
pstrb = wr ? strb : (strb_on_read ? 4'hf : 4'h0);
pprot = prot; psel = 1;
s_edge = cycle + 1;
if (skip_setup || stuck_now) penable = 1;
else begin
penable = 0;
@(posedge pclk); #1 penable = 1;
end
stuck_now = 0;
w = 0;
@(posedge pclk);
while (pready !== 1'b1 && w < give_up) begin
w++;
#1 if (move_in_wait && w == 1) paddr = addr + 4;
@(posedge pclk);
end
prdata_q = prdata; pslverr_q = (pslverr === 1'b1); waits_q = w;
#1;
if (!quiet) begin
if (w >= give_up) $display("e%0d-e%0d %0s 0x%04h no PREADY after %0d wait cycles, the bench gives up", s_edge, cycle, wr ? "WRITE" : "READ ", addr[15:0], w);
else if (wr) $display("e%0d-e%0d WRITE 0x%04h data=0x%08h strb=%b waits=%0d %0s", s_edge, cycle, addr[15:0], data, strb, w, pslverr_q ? "PSLVERR" : "OKAY");
else $display("e%0d-e%0d READ 0x%04h data=0x%08h waits=%0d %0s", s_edge, cycle, addr[15:0], prdata_q, w, pslverr_q ? "PSLVERR" : "OKAY");
end
if (last) begin
psel = 0; penable = 0; pwrite = 0; pstrb = 0;
end else begin
penable = enable_stuck ? 1 : 0;
stuck_now = enable_stuck;
end
endtask
task automatic apb_write(input logic [31:0] addr, input logic [31:0] data, input logic [3:0] strb = 4'hf, input logic [2:0] prot = 3'b000);
apb_xfer(1, addr, data, strb, prot, 1);
endtask
task automatic apb_read(input logic [31:0] addr, input logic [2:0] prot = 3'b000);
apb_xfer(0, addr, 0, 0, prot, 1);
endtask
// The queue: transfers sent back to back by apb_run().
task automatic q_write(input logic [31:0] addr, input logic [31:0] data, input logic [3:0] strb = 4'hf, input logic [2:0] prot = 3'b000);
q_wr[qn] = 1; q_addr[qn] = addr; q_data[qn] = data; q_strb[qn] = strb; q_prot[qn] = prot; qn++;
endtask
task automatic q_read(input logic [31:0] addr, input logic [2:0] prot = 3'b000);
q_wr[qn] = 0; q_addr[qn] = addr; q_data[qn] = 0; q_strb[qn] = 0; q_prot[qn] = prot; qn++;
endtask
task automatic apb_run();
for (int i = 0; i < qn; i++) begin
apb_xfer(q_wr[i], q_addr[i], q_data[i], q_strb[i], q_prot[i], i == qn - 1);
rdv[i] = prdata_q; errv[i] = pslverr_q;
end
qn = 0;
endtask
task automatic idle(input int n);
repeat (n) @(posedge pclk);
#1;
endtask
// The completer's RAM, by bus address (the decoder bench picks the completer from bits 13:12).
function automatic logic [7:0] ram_byte(input int unsigned a);
`ifdef APB_DECODER
case (a[13:12]) 2'd1: ram_byte = p1.mem[a[7:0]]; 2'd2: ram_byte = p2.mem[a[7:0]]; default: ram_byte = p0.mem[a[7:0]]; endcase
`else
ram_byte = p0.mem[a[7:0]];
`endif
endfunction
// dump(addr, n): n bytes from addr, one 32-bit word per group, lane 0 (the lowest address) first.
task automatic dump(input int unsigned addr, input int n);
string s;
for (int a = addr & ~3; a < addr + n; a += 4) begin
if ((a - (addr & ~3)) % 16 == 0) begin
if (a != (addr & ~3)) $display("%0s", s);
s = $sformatf("0x%04h ", a[15:0]);
end
s = {s, $sformatf(" %h %h %h %h ", ram_byte(a), ram_byte(a + 1), ram_byte(a + 2), ram_byte(a + 3))};
end
$display("%0s", s);
endtask
// fifo_fill(n, first): put n words first, first+1, ... in completer 0's FIFO before the run.
task automatic fifo_fill(input int n, input logic [31:0] first);
for (int i = 0; i < n; i++) p0.fifo[(p0.fhead + p0.fcount + i) % 16] = first + i;
p0.fcount = p0.fcount + n;
endtask
// regs(): completer 0's registers as one line.
task automatic regs();
$display("regs: CTRL=0x%08h STATUS=%0d WCOUNT=%0d", p0.ctrl, p0.fcount, p0.wcount);
endtask
Check yourself
Remember
- PREADY low in the access phase adds a wait state; the transfer completes at the first access edge where PREADY is 1, after any number of waits.
- While the transfer waits, PADDR, PWRITE, PSELx, PENABLE, PPROT and, for writes, PWDATA and PSTRB stay exactly as they were set in SETUP.
- PREADY is a don't-care when PENABLE is low; a completer without wait states may tie it high.
- PRDATA must be valid only at the completion edge; a completer whose data comes a cycle late passes every rule and returns stale values.
- APB has no wait-state limit: a completer that never answers stalls the whole bus, so systems add a timeout outside the protocol.