AMBA APB · Module 10
PSTRB Feature Deep Dive
The full mechanics of PSTRB — one write-strobe bit per byte lane of PWDATA, the write-only rule, byte-lane-to-data mapping, alignment, and the requirement that a slave gate every byte independently and preserve unstrobed bytes. The byte-granular write semantics, and the silent corruption when a slave ignores the strobes.
PSTRB is the one signal that turns APB from a full-word-write bus into a byte-granular one. It is a vector with one bit per byte lane of PWDATA: PSTRB[i]=1 means "commit byte lane i of PWDATA into the register"; PSTRB[i]=0 means "leave that byte exactly as it was." It is meaningful only on writes, and the single non-negotiable rule a slave must obey is to gate every byte lane independently on its strobe bit. Get that wrong — write the whole word on any PSTRB — and a harmless byte write silently corrupts the bytes next to it. This chapter is the full mechanics: lane mapping, alignment, the correct RTL, and the failure mode when a slave ignores the strobe.
1. Problem statement
The problem PSTRB solves is writing fewer than all the bytes of a data word in a single bus transfer. Baseline APB writes the entire PWDATA width on every write — there is no way to say "update only byte 0 and leave bytes 1–3 alone." That is fine for a register where every write replaces the whole value, but it is wrong, slow, or impossible in three very common situations:
- Packed registers. A 32-bit control register often holds four independent 8-bit fields (or two 16-bit halfwords) that different pieces of software own. Updating one field without a byte strobe forces a read-modify-write: read the word, change one byte in the CPU, write the word back — two bus transfers and a race window where another agent's write to a different byte can be lost.
- Sub-word stores from the CPU. A
STRB/STRH(store-byte / store-halfword) instruction targets one or two bytes. The bus fabric must carry that intent down to the peripheral; without a per-byte enable, the peripheral cannot honour a sub-word store and the fabric must either widen it (corrupting neighbours) or emulate it (read-modify-write in the bridge). - Memory-like and FIFO-mapped slaves. A byte-addressable SRAM or a packed buffer behind APB needs to commit exactly the bytes the master selected; writing all lanes would overwrite adjacent storage.
PSTRB answers all three with one mechanism: a strobe bit per byte lane that is a write-enable for that lane. A 32-bit bus has PSTRB[3:0] (four lanes); a 64-bit bus has PSTRB[7:0]. The master asserts exactly the lanes it intends to write, and a correct slave commits exactly those and no others.
2. Why previous knowledge is insufficient
You already know APB has PSTRB — Chapter 10.3, the APB4 introduction, placed it in the version story alongside PPROT, and the APB2 baseline taught you that the original protocol writes a full word every time because it has no strobe at all. And from Module 7's write-data timing you know when PWDATA is valid on the bus. None of that is enough to implement or debug PSTRB:
- The version delta tells you
PSTRBexists; it does not tell you its mechanics. Knowing "APB4 added byte strobes" is the headline. It does not tell you the lane-to-data mapping (PSTRB[i]↔PWDATA[8i+7:8i]), that the signal is write-only, or — the part that bites in real RTL — what happens to a byte whose strobe is low. That is the whole job of this chapter, and it is exactly where shallow knowledge produces broken slaves. - The baseline's "full-word write" is the trap, not the model. APB2 taught you writes are full-word. The danger is carrying that habit into an APB4 slave: a slave that still writes the full word — ignoring
PSTRB— looks correct for full-word writes and silently corrupts adjacent bytes on the first partial write. You have to un-learn full-word-always. - Knowing when
PWDATAis valid says nothing about which lanes to keep.PWDATAtiming tells you the data is stable in the ACCESS phase.PSTRBis an orthogonal axis — which of those stable bytes you are allowed to commit. The two combine: in the ACCESS cycle you samplePWDATA, but you only write the lanes whose strobe is set.
So this chapter goes below the version delta into the bit-level contract: one strobe per lane, write-only, deasserted-lane-preserved, and the RTL that enforces it. (PSTRB's sibling APB4 signal, PPROT, is a separate axis — privilege and security context — covered next in Chapter 10.7. Do not conflate the two: PSTRB says which bytes, PPROT says with what permission.)
3. Mental model
The model: PWDATA is a mailroom with one delivery slot per byte lane, and PSTRB is the set of "deliver this slot" flags. The mailroom always lays out all the parcels on the counter (PWDATA drives every lane), but only the slots whose flag is raised (PSTRB[i]=1) actually get delivered into the register; the rest are left untouched, and whatever was already in those register bytes stays. The flags matter only when you are delivering (a write); on a pickup (a read) there is nothing to deliver, so the flags mean nothing.
Three refinements make it precise:
- One bit per byte lane, mapped by position. For an N-byte bus,
PSTRB[N-1:0]has one bit per lane andPSTRB[i]governsPWDATA[8i+7:8i]. On a 32-bit bus:PSTRB[0]→PWDATA[7:0],PSTRB[1]→PWDATA[15:8],PSTRB[2]→PWDATA[23:16],PSTRB[3]→PWDATA[31:24]. The mapping is fixed and positional — strobe bit i always means byte lane i. - Set ⇒ commit; clear ⇒ preserve (not zero). This is the single most-missed rule.
PSTRB[i]=1commitsPWDATA[8i+:8]into the register byte.PSTRB[i]=0means the slave must leave that register byte exactly as it was — it is preserved, not cleared to zero and not written with whatever junk is on thatPWDATAlane. A deasserted strobe is a no-write for that lane. - Write-only —
PSTRBhas no meaning on reads. Strobes qualify writes (PWRITE=1). On a read,PSTRBis irrelevant; the slave returns the fullPRDATAword regardless, and the master selects the bytes it wanted itself. Many masters drivePSTRB=0(or leave the previous value) during reads — either way, a slave must ignorePSTRBentirely whenPWRITE=0and never let it gate a read.
4. Real SoC implementation
In silicon, a correct APB4 slave write path is a per-byte-lane gated register: in the ACCESS cycle of a write, each byte lane is updated only if its strobe bit is set. The idiomatic way to write it is a generate/for loop over the lanes, so the byte width scales with the bus and every lane is treated identically.
// APB4 slave write path with per-byte-lane PSTRB gating.
// Core rule: reg[8*i +: 8] is written from pwdata[8*i +: 8] ONLY when the
// transfer is an active write AND that lane's strobe is set. A deasserted
// strobe PRESERVES the existing byte (no else-branch that zeroes it).
module apb4_csr #(
parameter int AW = 8,
parameter int DW = 32,
localparam int NBYTE = DW/8 // 4 byte lanes for a 32-bit bus
)(
input logic pclk, presetn,
input logic psel, penable, pwrite,
input logic [AW-1:0] paddr,
input logic [DW-1:0] pwdata,
input logic [NBYTE-1:0] pstrb, // one bit per byte lane of pwdata
output logic [DW-1:0] prdata,
output logic pready, pslverr
);
logic [DW-1:0] ctrl; // a packed register: 4 byte fields
// ACCESS-phase write qualifier (the cycle the transfer commits).
wire wr_access = psel & penable & pwrite;
wire sel_ctrl = (paddr == 8'h00);
// Gate EACH byte lane on its own strobe bit. The for-loop body is the
// whole correctness story: lane i commits iff (wr_access & pstrb[i]);
// when pstrb[i]==0 the register byte is simply not assigned -> held.
always_ff @(posedge pclk or negedge presetn) begin
if (!presetn) begin
ctrl <= '0;
end else if (wr_access && sel_ctrl) begin
for (int i = 0; i < NBYTE; i++) begin
if (pstrb[i]) // strobe set -> commit this lane
ctrl[8*i +: 8] <= pwdata[8*i +: 8];
// strobe clear -> NO assignment -> old byte preserved (NOT zeroed)
end
end
end
// READ path: PSTRB plays NO role here. Return the full word; the master
// selects the bytes it cares about. Never let pstrb gate a read.
always_comb begin
unique case (paddr)
8'h00: prdata = ctrl;
default: prdata = '0;
endcase
end
assign pready = 1'b1; // single-cycle CSR; no wait states needed here
assign pslverr = 1'b0; // no error case modelled in this minimal slave
endmoduleTwo facts define a correct byte-strobed slave. First, the absence of an else is the whole point — when pstrb[i] is low, lane i is simply not assigned in that cycle, so the flip-flops hold their previous value and the byte is preserved. The most common bug (Beat 6) is "fixing" this by adding an else that zeroes the byte, or by replacing the loop with a single ctrl <= pwdata. Second, the read path must be strobe-blind: PSTRB never appears in the read mux. If write-data fundamentals are still fuzzy — when PWDATA is actually valid for the slave to sample — revisit PWDATA timing; PSTRB only tells you which of those valid bytes you may commit.
5. Engineering tradeoffs
There is no real "cost vs benefit" knob in PSTRB itself — a correct slave must honour it — but there is a design surface in which strobe patterns a master legally generates and what each pattern means. The table below is the working reference for a 32-bit bus (PSTRB[3:0]), mapping a pattern to the bytes it commits and its typical use. (Bit order shown MSB→LSB, i.e. PSTRB[3] PSTRB[2] PSTRB[1] PSTRB[0].)
PSTRB[3:0] | Bytes committed (PWDATA lanes) | Bytes preserved | Typical use |
|---|---|---|---|
0001 | byte 0 — [7:0] | bytes 1, 2, 3 | byte write to address offset +0 (aligned byte store) |
0010 | byte 1 — [15:8] | bytes 0, 2, 3 | byte write to offset +1 |
1000 | byte 3 — [31:24] | bytes 0, 1, 2 | byte write to offset +3 |
0011 | bytes 0–1 — [15:0] | bytes 2, 3 | aligned halfword (16-bit) write, lower half |
1100 | bytes 2–3 — [31:16] | bytes 0, 1 | aligned halfword write, upper half |
1111 | all four bytes — [31:0] | none | full-word (32-bit) write — equivalent to baseline APB |
0000 | none | all four | no-op write (idle/poison write; commits nothing) |
0101 | bytes 0 and 2 — [7:0], [23:16] | bytes 1, 3 | sparse / non-contiguous write (legal but rare; only some masters generate it) |
The judgment to carry: a naturally aligned access produces a contiguous strobe pattern aligned to its size — 0001/0010/0100/1000 for bytes, 0011/1100 for halfwords, 1111 for the full word. Sparse or unaligned patterns (0101, 1010, 1001) are legal on the wire but most CPUs and fabrics never emit them for normal aligned stores; a slave must still handle any pattern correctly because the spec permits it, but verification should treat sparse patterns as a distinct coverage corner. A slave that only works for contiguous patterns is subtly non-compliant.
6. Common RTL mistakes
7. Debugging scenario
The signature PSTRB bug is a slave that ignores the strobe and writes the full word, surfacing as a "writing one field corrupts another" report from firmware.
- Observed symptom: firmware updates a single 8-bit field (call it field A, byte 0) of a packed 32-bit control register, and an adjacent field it never touched (field B, byte 1, or fields C/D) comes back wrong on the next read. Field A itself is always correct, so the bug looks like "writing field A corrupts field B" — and it only appears once two different fields of the same register are actually used.
- Waveform clue: in the ACCESS cycle of the offending write,
PWRITE=1,PSTRB=0001(only byte 0 strobed), andPWDATA[31:8]holds stale or don't-care data on the unstrobed upper lanes. Yet at the nextPCLKedge the register's bytes 1–3 change to exactly those stalePWDATAupper-lane values. The tell is that the register byte tracked an unstrobedPWDATAlane — a clear signature that the slave wrote lanes its strobe said to leave alone. - Root cause: the slave's write path ANDs all four bytes onto a single write enable —
if (psel & penable & pwrite) ctrl <= pwdata;— and never consultsPSTRB. It commits the entirePWDATAword on any write, so the three unstrobed upper lanes (carrying stale bus data) overwrite fields B/C/D. It passes every full-word (1111) test and corrupts on the first sub-word write. - Correct RTL: gate each lane on its own strobe (the Beat 4 pattern):
always_ff @(posedge pclk or negedge presetn)
if (!presetn) ctrl <= '0;
else if (psel && penable && pwrite && sel_ctrl)
for (int i = 0; i < 4; i++)
if (pstrb[i]) ctrl[8*i +: 8] <= pwdata[8*i +: 8]; // unstrobed -> held- Verification assertion: prove that any byte whose strobe is low does not change across a write (byte-preservation):
// For each byte lane: on an accepted write, a deasserted strobe must
// leave that register byte unchanged from its prior value.
genvar gi;
generate for (gi = 0; gi < 4; gi++) begin : g_preserve
assert property (@(posedge pclk) disable iff (!presetn)
(psel && penable && pwrite && pready && !pstrb[gi])
|=> $stable(ctrl[8*gi +: 8]));
end endgenerate- Debug habit: when a write to one field disturbs another, line up the
PSTRBvector against which register bytes changed. If a byte changed while its strobe bit was 0, the slave is ignoringPSTRB— full stop. Check the strobe-to-byte mapping before suspecting timing, decode, or the master; ignored-strobe corruption is invisible to full-word tests and only the strobe-vs-changed-byte comparison exposes it.
8. Verification perspective
Verifying PSTRB is about proving the slave honours every lane independently across the full space of patterns — not just the easy full-word case — and that an unstrobed byte never moves.
PSTRBpattern coverage. Build a coverpoint on the strobe vector with bins for the meaningful classes: all-zero (0000, no-op write — must commit nothing), single-lane (each of0001/0010/0100/1000), contiguous multi-lane (0011/1100/0111/1110), full (1111), and sparse/non-contiguous (0101/1010/1001). Cross the strobe pattern with the target address so every register is exercised under partial writes, not just one. Sparse patterns are the corner most likely to be missed and most likely to expose a lane that was hardcoded.- Byte-preservation checking. The central property: for each lane, an accepted write with that strobe low must leave the register byte
$stable(the Beat 7 assertion). Equivalently, a scoreboard models the register as four independent bytes and, on each write, updates only the strobed lanes — any mismatch means the DUT touched a lane it shouldn't have. This is what catches the "ignoresPSTRB, writes full word" bug directly. - Read-modify-write equivalence. A useful golden check: a strobed partial write should produce the same final word as the software RMW it replaces — read the word, overlay only the strobed bytes of
PWDATA, write back. Comparing the DUT's post-write word against this byte-overlay reference for arbitrary patterns proves the lane mapping and preservation are both correct. - Alignment / mapping corners. Drive every single-byte offset (strobe at each of the four positions) and every aligned halfword (
0011,1100) and confirm the exactPWDATAlane lands in the exact register byte — catches off-by-one mapping (e.g.PSTRB[1]wired toPWDATA[7:0]). Also cover the read-side invariant: drive non-zeroPSTRBduring reads and assert the read result is identical to the same read withPSTRB=0— proving the slave ignores strobes on reads.
The point: PSTRB verification is per-lane and pattern-exhaustive. Full-word tests pass on broken slaves; only single-lane, sparse, and preservation checks find the bugs.
9. Interview discussion
"How does PSTRB work, and what must a slave do with it?" is a fast filter for whether someone has actually written or debugged byte-strobed RTL versus only read the spec line. The clean answer has three layers.
Start with the mechanics: PSTRB is a vector with one bit per byte lane of PWDATA — PSTRB[3:0] on a 32-bit bus — and PSTRB[i] is a write-enable for PWDATA[8i+:8]. Then the two rules that separate signal from noise: it is write-only (no meaning on reads — the slave returns the full word and the master selects bytes), and a deasserted strobe preserves the byte, it does not zero it. Close with the failure mode, which is the real depth signal: a slave that ignores PSTRB and writes the full word passes every full-word test and then silently corrupts adjacent bytes on the first partial write — presenting to firmware as "writing field A corrupts field B." If you can state the per-lane gating RTL (for i: if (write & pstrb[i]) reg[8i+:8] <= pwdata[8i+:8]) and the byte-preservation assertion (!pstrb[i] |=> $stable(reg byte)), you have demonstrated you can both build and verify it. Bonus depth: note that naturally aligned accesses yield contiguous strobes but the protocol permits sparse ones, so a compliant slave handles any pattern — and that PSTRB (which bytes) is orthogonal to PPROT (with what permission).
10. Practice
- Map the lanes. For a 32-bit bus, write out which
PWDATAbits each ofPSTRB[0]..PSTRB[3]controls, then extend the mapping to a 64-bit bus (PSTRB[7:0]). - Decode the patterns. Given
PSTRB=0110,PSTRB=1001, andPSTRB=0000, state exactly which bytes commit, which are preserved, and a plausible (or impossible) use for each. - Write the gated RTL. From memory, write the per-byte-lane write path for a 32-bit packed register using a
forloop, and explain why there is noelsebranch. - Predict the corruption. A slave does
if (psel & penable & pwrite) reg <= pwdata;. A master issuesPSTRB=0001,PWDATA=32'hDEAD_BE_5Awith the upper bytes stale. State the final register value vs. the correct one, and name the bug. - Verify it. Write the SystemVerilog assertion that proves a byte whose strobe is low does not change across a write, and describe one coverpoint bin that a full-word-only test would miss.
11. Q&A
12. Key takeaways
PSTRBis one strobe bit per byte lane ofPWDATA—PSTRB[3:0]for a 32-bit bus — andPSTRB[i]is a write-enable forPWDATA[8i+7:8i]. It turns APB from a full-word-write bus into a byte-granular one.- Set ⇒ commit, clear ⇒ preserve (not zero).
PSTRB[i]=1writes lanei;PSTRB[i]=0leaves that register byte exactly as it was. Correct RTL gates each lane and has noelsethat zeroes the byte. PSTRBis write-only. It has no meaning on reads — the slave returns the full word and the master selects bytes. A read path must be strobe-blind.- The idiomatic slave is a per-lane gated register:
for i: if (write & pstrb[i]) reg[8i+:8] <= pwdata[8i+:8];. The absence of anelseis what preserves unstrobed bytes. - Ignoring
PSTRBand writing the full word is the signature bug: it passes every full-word test and silently corrupts adjacent bytes on the first partial write — surfacing as "writing field A corrupts field B." - Verify per-lane and pattern-exhaustively: cover all-zero, single-lane, contiguous, full, and sparse patterns; assert byte-preservation (
!pstrb[i] |=> $stable); and check read-modify-write equivalence. Full-word tests alone hide the bugs.