Strobes, protection and APB5
Write single bytes with PSTRB, keep it low on reads, align PADDR, read the three PPROT bits, and know what APB5 adds to the wires.
- Lesson
- P3.5
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A driver set the DMA channel number in byte 2 of a control register with a byte store. The bridge turned it into an APB write with PSTRB 0100. The peripheral had been written for APB3, which had no strobes, and wrote all four bytes from PWDATA. Byte 0 held the interrupt enable, and every time the channel was programmed the interrupts went quiet.
p0.ignore_strobes = 1: CTRL held 0x000000e1, the write carries 05 in lane 2. written is the lanes the completer changed.Lit cells are the bytes the completer wrote. The strobe named one.
- Cycle 0
Setup of a write to CTRL with
pstrb0100: only byte 2, the channel number, is valid. - Cycle 1
The last cycle. The completer writes all four lanes: CTRL becomes 0x00050000 and the e1 in byte 0 is gone.
The strobe was right; the completer did not look at it.
One strobe per byte lane
APB4 added PSTRB: one bit per byte of the write data bus, PSTRB[n] for PWDATA[8n+7:8n]. A lane whose strobe is 1 carries a byte to write; a lane whose strobe is 0 carries nothing, and the completer must leave that byte alone. Any pattern is legal, including 0101 (a sparse write) and 0000, which writes nothing. Play the measured run: the RAM words at 0x010 and 0x014 hold 11 in every byte, then three writes arrive.
Lane 0 is the lowest address, on the left. Lit cells are the bytes written.
- Cycle 0
Setup of a write to 0x010 with data aabbccdd and
pstrb0101. - Cycle 1
It completes. Lanes 0 and 2 take dd and bb; lanes 1 and 3 keep 11.
- Cycle 3
A write to 0x014 with
pstrb1000: one byte, 99, lands at 0x017. - Cycle 5
A write with
pstrb0000. It completes OKAY and writes nothing.
Memory afterwards: dd 11 bb 11 at 0x010 and 11 11 11 99 at 0x014.
For reads the requester drives every strobe low. The bench's checker names that rule strb_read. Set strb_on_read = 1; before a read and it fires at the setup edge and again at the access edge.
The twin, and connecting old and new
Add p0.ignore_strobes = 1; before the example's writes and the completer writes whole words. The dump shows every byte the strobes protected overwritten.
0x0010 dd cc bb aa 78 56 34 12
regs: CTRL=0x00000000 STATUS=0 WCOUNT=5
checker: 5 writes, 0 reads, 0 wait edges, 0 rules broken, 0 notesPSTRB is optional, and the specification says how to mix interfaces that have it with interfaces that do not.
| Requester | Completer | What to do |
|---|---|---|
| no PSTRB | no PSTRB | Nothing: sparse writes do not exist |
| no PSTRB | has PSTRB | Tie the completer's PSTRB inputs to PWRITE: every lane on writes, none on reads |
| has PSTRB | no PSTRB | Compatible, but the completer cannot do sparse writes; its documentation must say so |
| has PSTRB | has PSTRB | Connect them |
IHI 0024E Table 3-1. The opening bug was the third row in disguise: a port named PSTRB that the logic never read.
Align PADDR, select bytes with PSTRB
PADDR is a byte address, and the specification permits an unaligned value, but calls the result unpredictable. A completer may use the address as it is, round it down, or answer with an error. This bench's completer answers PSLVERR. The portable way to write the upper half-word of the word at 0x010 is the aligned address with the strobes of lanes 2 and 3.
e5-e6 WRITE 0x0012 data=0xaaaa0000 strb=1100 waits=0 PSLVERR
e7-e8 WRITE 0x0010 data=0xaaaa0000 strb=1100 waits=0 OKAY
0x0010 11 11 aa aaPPROT: who is asking
APB4 also added PPROT, three bits that describe the access, not the data. A completer with security or privilege checks reads them; one without them may ignore them.
| Bit | Low | High |
|---|---|---|
| PPROT[0] | normal access | privileged access |
| PPROT[1] | Secure access | Non-secure access |
| PPROT[2] | data access | instruction access (a hint) |
IHI 0024E Table 3-2. The main use is telling Secure from Non-secure.
With p0.priv_ctrl = 1; this completer refuses unprivileged accesses to CTRL. Measured: apb_write(32'h100, 32'h1) with PPROT 000 ends with PSLVERR, and the same write with PPROT 001 is OKAY. An unprivileged read returns PSLVERR together with the CTRL value: the P3.4 rule about read data, in action.
What APB5 adds
| Version | Signals added | Why |
|---|---|---|
| APB2 (AMBA 2) | PSEL, PENABLE, PADDR, PWRITE, PWDATA, PRDATA | Two-cycle register access, no waits, no errors |
| APB3 | PREADY, PSLVERR | Wait states and error responses |
| APB4 | PSTRB, PPROT | Sparse writes and access protection |
| APB5 | PWAKEUP, user signals, parity check signals, PNSE | Clock gating, custom sideband, safety, Realm Management |
IHI 0024E Preface and chapters 3 and 5. Every APB5 addition is optional and declared by a property.
PWAKEUP comes from the requester and tells a clock controller that activity is coming. It must be glitch-free, may rise before PSEL, and must stay high until PREADY once it is high together with PSEL. The parity signals carry odd parity, one bit per byte of each signal group, so a single flipped wire between two components is detected. PNSE extends PPROT[1] to name the Root and Realm address spaces.
Asked in interviews: strobes and protection
What is PSTRB during a read?
All bits low. The requester must drive them low for every read transfer.
Is a write with PSTRB 0000 legal?
Yes. It completes like any write and changes no byte.
What happens with an unaligned PADDR?
The specification calls the result unpredictable: the completer may use it, align it or answer PSLVERR. Use the aligned address and PSTRB instead.
The completer has PSTRB but the requester does not. How do you connect them?
Tie the completer's PSTRB inputs to PWRITE, so writes have every lane valid and reads have none.
What does PPROT[1] = 1 mean?
A Non-secure access. PPROT[0] = 1 is privileged and PPROT[2] = 1 is an instruction access.
Try it
The two quiet writes fill both words with 11. The dump prints the eight bytes lane 0 first: only the strobed bytes changed. WCOUNT counts five completed writes, including the one with no strobe.
Add p0.ignore_strobes = 1; as the first line and run again to see the twin. Then try strb_on_read = 1; apb_read(32'h010); for the read rule, and apb_write(32'h012, 32'haaaa0000, 4'b1100); for the unaligned refusal.
e7-e8 WRITE 0x0010 data=0xaabbccdd strb=0101 waits=0 OKAY e9-e10 WRITE 0x0014 data=0x99000000 strb=1000 waits=0 OKAY e11-e12 WRITE 0x0014 data=0x12345678 strb=0000 waits=0 OKAY 0x0010 dd 11 bb 11 11 11 11 99 regs: CTRL=0x00000000 STATUS=0 WCOUNT=5 checker: 5 writes, 0 reads, 0 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
PSTRB[n]marks lane n ofPWDATAvalid; sparse patterns and 0000 are legal writes.- Every strobe is low during a read.
- An unaligned
PADDRhas an unpredictable result; use the aligned address and the strobes. PPROTsays privileged, Non-secure and instruction in bits 0, 1 and 2; Secure versus Non-secure is the main use.- APB3 added waits and errors, APB4 strobes and protection, APB5 wake-up, user, parity and RME signals.