Wishbone · Module 14
Memory Accesses
Six words checked against a reference model, four phases at four latencies, and a master performing five transfers when four were asked for — every one of them legal.
Chapter 14.3 settled the vocabulary. A block is a grouping; the master owns the sequence; nothing declares a length.
Which puts the entire burden of getting the count right on the master, with no help from the bus.
What does a block against real memory require, and what happens when the master counts its own transfers wrong?
1. The Memory Side of a Block
Chapter 12.5 established the address split; a block changes only how many times it happens.
Each phase carries a full address, which the interconnect decodes and the target turns into a local offset. The block does not shortcut that — there is no "same target as last time" fast path, because the slave was never told there was a last time.
And the target's own bounds still apply per phase. The RAM answers offsets below DEPTH and refuses the rest, exactly as in a single cycle. A block that runs off the end of a memory produces an ERR on the phase that does, not on the block.
What the master must supply, per phase: the address, the write data, SEL, and WE — the same payload Chapter 5.5 had it present to begin a single transfer, and terminated the way Chapter 5.6 described. What it must not do is change any of them while a phase is outstanding, which is Section 5's measurement.
2. Simulation — SIM G: A Block Write Into RAM
Six words from word address 8, one cycle, zero-wait RAM, checked against an independent reference array over the whole memory.
=== SIM G - a block write into RAM ===
6 words from word address 8, one cycle, zero-wait RAM.
phase ADR (word) DAT SEL RAM index
0 0x00000008 0xa5000000 1111 8
1 0x00000009 0xa5000001 1111 9
2 0x0000000a 0xa5000002 1111 10
3 0x0000000b 0xa5000003 1111 11
4 0x0000000c 0xa5000004 1111 12
5 0x0000000d 0xa5000005 1111 13
bus cycles 1 phases 6 ACKs 6 elapsed 8 clocks
RAM words written 6
reference comparison over all 64 RAM words
WRITE MISMATCHES: 0Reading it
Six phases, six ACKs, six words written, and the reference comparison covers all 64 RAM words — not just the six that should have changed.
That scope is deliberate. Comparing only the touched words would pass a master that wrote the right values to the right places and also wrote somewhere else. The 58 words that should have stayed zero are as much a part of the result as the six that changed.
The data pattern is 0xA500_000k, carrying the phase index in its low half. A repeated address, a skipped address or a swapped pair is visible in the value itself rather than having to be inferred from which slot is wrong.
Eight clocks for six transfers — six presenting, plus the cycle's start and finish, consistent with Chapter 14.2's accounting.
3. Simulation — SIM H: Reading It Back
The same master, the same cycle structure, WE_O negated. Every returned word compared against the reference.
=== SIM H - reading the same block back ===
phase ADR (word) returned expected match
0 0x00000008 0xa5000000 0xa5000000 yes
1 0x00000009 0xa5000001 0xa5000001 yes
2 0x0000000a 0xa5000002 0xa5000002 yes
3 0x0000000b 0xa5000003 0xa5000003 yes
4 0x0000000c 0xa5000004 0xa5000004 yes
5 0x0000000d 0xa5000005 0xa5000005 yes
words checked 6 READ MISMATCHES: 0Reading it
Six words checked, zero mismatches, and the read block has the same structure as the write — one cycle, six phases, six terminations.
The read direction is where a phase-boundary mistake becomes visible immediately. A master that latched DAT_I a clock late would return the next word each time and the last one would be junk; one that latched early would return the previous. Both produce a cleanly shifted sequence, which is why the expected column is worth printing beside the returned one rather than reporting a single pass.
SEL_O is all-ones throughout. These are whole-word transfers, so Module 13's lane machinery is present and idle — and every phase would carry its own operand classification under Chapter 13.4 — a block of partial writes is entirely possible, and each phase would carry its own select pattern exactly as a single transfer does. The two mechanisms compose without interacting.
4. Simulation — SIM I: Four Phases, Four Latencies
The RAM takes its wait count from the address, so each phase of one block is answered at a different speed — zero, one, two and three wait states, in that order.
=== SIM I - one block, four different latencies ===
the RAM takes its wait count from the address, so each
phase of this block is answered at a different speed.
phase ADR (word) wait clocks term
0 0x00000008 0 ACK
1 0x00000009 1 ACK
2 0x0000000a 2 ACK
3 0x0000000b 3 ACK
bus cycles 1 phases 4 ACKs 4
clocks presenting 10 of which waiting 6
metadata changed while a phase was outstanding: no
One cycle, four phases, four terminations, and the phase
index advanced only after each answer. ADR, WE, SEL and
DAT held still for the whole of every wait.Reading it
Four phases, four different wait counts, four terminations, one cycle. The latencies are 0, 1, 2 and 3 and they land on the phases in order.
Ten clocks presenting, of which six were waiting. Six is 0+1+2+3 — the sum of the individual latencies, which is what Chapter 14.2 meant by latency being per-transfer. The block did not amortise any of it.
metadata changed while a phase was outstanding: no is the important line, and it is a measurement rather than a claim. The testbench watches ADR, WE, SEL, DAT and the phase index on every clock where a transfer is presented and unanswered, and counts any change.
Why that check and not a visual inspection of a waveform. A metadata change during a wait is the defect Chapter 8.3 measured as early address advance: the master moves on while the slave is still working, so the slave answers a transfer whose address has already been replaced. RULE 3.60 qualifies ADR_O, DAT_O(), SEL_O() and WE_O with STB_O, and holding them still for the whole of a phase is what makes that qualification mean anything.
The phase index is in the stability check for the same reason. It advances only at a boundary — P4 in Chapter 14.1 — and a master that advanced it on presentation would skip a word while the bus looked perfectly normal.
5. Transfer-Count and Address-Sequence Audits
Section 6's defect is a counting error, so the count is worth auditing across the whole supported range first — including zero, which is where a counter is most likely to wrap.
--- transfer-count audit: LENGTH means NUMBER OF TRANSFERS ---
requested cycles phases ACKs RAM writes unique ADRs CYC after
0 0 0 0 0 0 low
1 1 1 1 1 1 low
2 1 2 2 2 2 low
3 1 3 3 3 3 low
4 1 4 4 4 4 low
5 1 5 5 5 5 low
6 1 6 6 6 6 low
7 1 7 7 7 7 low
8 1 8 8 8 8 low
Every non-zero length produced exactly that many phases in
exactly one cycle, and CYC_O was low afterwards in all of
them. Length 0 began no cycle at all - LOCAL RTL POLICY,
chosen so a zero-length request cannot wrap a counter into
a full-range block.
--- address-sequence audit, WORD units ---
expected ADR[k] = base + k for k = 0 .. length-1
sequence violations across every length: 0Reading it
Nine lengths, and the three count columns agree on every row. Phases, ACKs and RAM writes are the same number as the request, in exactly one cycle, with CYC_O low afterwards.
Length 0 begins no cycle at all. That is LOCAL RTL POLICY, and the reason for choosing it is visible in the master: the final-phase test is beat_q == len_q - 1. With len_q zero that subtraction underflows, and a counter compared against the wrap value runs for the full range instead of not at all. Diverting zero before the subtraction is what makes the rest safe.
The address audit is the second half of the same concern. ADR[k] = base + k for every k across every length — zero violations, in word units. A stride error would show here as a uniform multiple, and an advance-on-presentation error as half the addresses appearing.
Neither audit would catch the defect in Section 6, and that is worth noticing. Both check the correct master. The next section runs a different one.
6. Simulation — SIM J: One Comparison, One Extra Transfer
The same master with the final-phase test written against the count instead of the last index.
// ── THE DEFECT — a final-phase test written against the wrong bound ─────
// wb_offbyone_master. NOT A REFERENCE DESIGN.
//
// Identical to wb_seq_master except for one comparison:
//
// correct: last_phase = (beat_q == len_q - 1)
// broken: last_phase = (beat_q == len_q)
//
// The reasoning behind it is the ordinary confusion between a COUNT and a
// LAST INDEX. Four transfers are numbered 0..3, so the last one is index 3,
// not index 4. Comparing against the count runs one phase too many.
//
// What makes it worth measuring rather than describing is where the extra
// phase goes: it is a fully legal, fully acknowledged transfer to the next
// address. Nothing on the bus objects, the slave answers it, and on a write
// it lands in memory.
module wb_offbyone_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned MAXLEN = 16,
parameter bit BLOCK = 1'b1,
parameter int unsigned STRIDE = 1,
localparam int unsigned CW = (MAXLEN <= 1) ? 1 : $clog2(MAXLEN + 1)
) (
input logic clk_i,
input logic rst_i,
input logic start_i,
input logic we_i,
input logic [AW-1:0] base_i,
input logic [CW-1:0] len_i,
output logic busy_o,
output logic done_o,
output logic cyc_o,
output logic stb_o,
output logic we_o,
output logic [AW-1:0] adr_o,
output logic [DW-1:0] dat_o,
output logic [DW/8-1:0] sel_o,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
output logic [CW-1:0] beat_o
);
typedef enum logic [1:0] { S_IDLE, S_XFER, S_GAP, S_DONE } state_e;
state_e state_q;
logic [CW-1:0] beat_q, len_q;
logic [AW-1:0] adr_q;
logic we_q;
logic [15:0] gap_q;
logic phase_done, last_phase;
assign phase_done = cyc_o && stb_o && (ack_i || err_i);
// ── THE DEFECT: compared against the COUNT, not the last index. ──
assign last_phase = (beat_q == len_q);
assign cyc_o = (state_q == S_XFER) || (BLOCK && (state_q == S_GAP));
assign stb_o = (state_q == S_XFER);
assign we_o = we_q;
assign adr_o = adr_q;
assign sel_o = '1;
assign dat_o = we_q ? {16'hA500, 16'(beat_q)} : '0;
assign busy_o = (state_q != S_IDLE);
assign done_o = (state_q == S_DONE);
assign beat_o = beat_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE; beat_q <= '0; len_q <= '0;
adr_q <= '0; we_q <= 1'b0; gap_q <= '0;
end else begin
case (state_q)
S_IDLE: if (start_i) begin
if (len_i == CW'(0)) state_q <= S_DONE;
else begin
len_q <= len_i; we_q <= we_i;
beat_q <= '0; adr_q <= base_i; state_q <= S_XFER;
end
end
S_XFER: if (phase_done) begin
if (last_phase) state_q <= S_DONE;
else begin
beat_q <= beat_q + CW'(1);
adr_q <= adr_q + AW'(STRIDE);
if (BLOCK) state_q <= S_XFER;
else begin gap_q <= 16'd1; state_q <= S_GAP; end
end
end
S_GAP: if (gap_q <= 16'd1) state_q <= S_XFER;
else gap_q <= gap_q - 16'd1;
S_DONE: state_q <= S_IDLE;
default: state_q <= S_IDLE;
endcase
end
end
endmodule === SIM J - a final-phase test against the wrong bound ===
the same master with one comparison changed:
correct last = (beat == len - 1)
broken last = (beat == len)
requested transfers 4
phases observed 5
ACK terminations 5
RAM words written 5
addresses presented:
phase 0 0x00000008
phase 1 0x00000009
phase 2 0x0000000a
phase 3 0x0000000b
phase 4 0x0000000c <-- not requested
word 12 was written by a transfer nobody asked for, and
it is a completely legal transfer - presented, qualified,
acknowledged. Nothing on the bus objects to it.Reading it
Four requested, five performed. Five phases, five ACKs, five words written — and word 12 belongs to whatever was there before.
The extra transfer is completely legal. Presented with CYC_O and STB_O, carrying a valid address and valid data, acknowledged by the slave in the ordinary way. A protocol checker sees five conforming transfers, because five conforming transfers is what happened.
Nothing on the bus carries the number four. The request length lives in the master's len_q register and nowhere else, so there is no signal against which the fifth transfer could be judged wrong.
Which makes the evidence entirely count-based: phases observed against the length the client asked for. That comparison requires knowing the client's request, which a bus-level monitor does not.
The mirror-image defect is worth naming because it fails differently. Comparing against len_q - 2 performs N−1 transfers, and that one is caught immediately by a read-back: the last word is missing. One too many is silent; one too few is loud.
7. Failure Modes and Discriminating Evidence
Symptom: a block writes one word past its range.
Candidate causes. A final-phase test against the count rather than the last index; a counter initialised to 1; a length interpreted as a last index.
Discriminating evidence. Phases observed against the requested length, and the address of the last phase. Five against four, ending one stride past the intended end, is conclusive. Memory beyond the block is where to look, not within it.
Likely RTL location: the final-phase comparison.
Symptom: a block writes one word short.
Candidate causes. The same comparison, wrong in the other direction, or CYC_O dropped before the final termination.
Discriminating evidence. A read-back of the full range. The last word holds its previous value. This one announces itself, which is why it is the less dangerous of the pair.
Symptom: a block against a slow memory returns shifted data.
Candidate causes. Metadata advanced while a phase was outstanding, so the slave answered a transfer the master had already replaced.
Discriminating evidence. ADR and the phase index on every clock where a transfer is presented and unanswered. Any change is the defect. SIM I runs that check continuously and reports it as one line.
Symptom: a block completes but one word in the middle is wrong.
Candidate causes. A repeated or skipped address, or a data pattern that does not distinguish position.
Discriminating evidence. The value in the wrong slot. With 0xA500_000k the value names the phase that wrote it — a value of 0xA5000002 in slot 3 says phase 2 wrote twice, which is a different defect from slot 3 never being written.
Symptom: a block runs off the end of a memory.
Candidate causes. A length or base that puts later phases outside the target.
Discriminating evidence. Which phase returns ERR. The block does not fail as a unit — the phase that leaves the range fails, and the ones before it succeeded, so the memory is partially updated. Chapter 12.5's range test is what produces that ERR.
8. Verification
The properties in Chapter 14.1 Section 9 cover this chapter, and two of them are what SIM J violates.
P5 — no extra phase. After the final phase's boundary, STB_O is low. The broken master presents a fifth transfer instead, so P5 fails on exactly the clock where the defect happens rather than downstream where the symptom appears.
P4 — advance only on termination, and P3 — metadata stable, are what SIM I measures procedurally. Icarus cannot run the assertions; it can run the stability counter, and that counter is P3 in executable form.
And the RAM's own two properties apply per phase, not per block.
// ─────────────────────────────────────────────────────────────────────────
// wb_ram_props — the slave's side. Both LOCAL: Wishbone does not reach
// inside a target, and nothing here is a block-specific obligation.
// ─────────────────────────────────────────────────────────────────────────
module wb_ram_props #(
parameter int unsigned OFF_AW = 8,
parameter int unsigned DEPTH = 64
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic ack_i,
input logic err_i,
input logic [OFF_AW-1:0] adr_i
);
default disable iff (rst_i);
// P9 — LOCAL RTL POLICY. An acknowledged access names implemented
// storage. The same bound Chapter 12.5 measured, one module along.
property p_ram_index_in_range;
@(posedge clk_i) (cyc_i && stb_i && ack_i) |-> (32'(adr_i) < 32'(DEPTH));
endproperty
a_ram_index_in_range: assert property (p_ram_index_in_range);
// P10 — LOCAL RTL POLICY, and the one that makes this slave block-
// agnostic. It answers at most one of ACK/ERR, and only inside a
// qualified transfer — with no reference anywhere to how many phases the
// cycle contains, because it is never told.
property p_one_answer_qualified;
@(posedge clk_i) (ack_i || err_i) |-> (cyc_i && stb_i && !(ack_i && err_i));
endproperty
a_one_answer_qualified: assert property (p_one_answer_qualified);
endmoduleP10 is the property that makes the slave block-agnostic. It answers at most one of ACK/ERR, only inside a qualified transfer, with no reference anywhere to how many phases the cycle contains — because it is never told.
9. Common Mistakes
"length means the last index."
Wrong mental model: a bound rather than a count.
What is true: it is a count in this RTL, and the policy has to be written down. Four transfers are indexed 0..3, so the final-phase test is against length - 1. SIM J is what the other reading costs.
"A read-back proves the block was correct."
Wrong mental model: checking what you wrote is checking everything.
What is true: it cannot see a transfer that went somewhere else. SIM J's block reads back perfectly for all four requested words. The reference comparison has to cover the memory the block should not have touched.
"An extra transfer would show up as a protocol error."
Wrong mental model: the bus knows the length.
What is true: there is no length on the bus. The fifth transfer is presented, qualified and acknowledged like the other four. Nothing is available to object.
"A zero-length block is harmless."
Wrong mental model: nothing requested, nothing happens.
What is true: only if the RTL diverts it. With a final-phase test of beat == len - 1 and len zero, the subtraction wraps and the counter runs the full range. Section 5 sweeps zero explicitly for that reason.
"Block transfers to memory need a special memory."
Wrong mental model: the slave participates in the block.
What is true: the RAM here has no block awareness at all. It answers qualified transfers. PERMISSION 3.55 makes BLOCK support optional precisely because, for most slaves, there is nothing to support.
"A block that runs past the end of a memory fails as a block."
Wrong mental model: the cycle is the unit of failure.
What is true: the phase that leaves the range fails. Earlier phases already succeeded and their writes stand — the memory is partially updated, and the master's error policy decides what happens next.
10. Interview Reasoning
Define what the length parameter means, test against the last index, and divert zero before the subtraction.
The semantics come first because the bug is a semantic one. length is a count here: four means four transfers, indexed 0 through 3. So the final-phase test is beat == length - 1. Comparing against length runs one phase too many.
Zero needs diverting before that subtraction ever happens. length - 1 with length zero wraps to the maximum, and a counter compared against the wrap value runs the full range. This master sends zero straight to done and begins no cycle — local policy, and the audit sweeps it.
Then verify by counting, not by reading back. Phases observed against the requested length, across every supported length. A read-back of the requested words passes on a master that also wrote a fifth — measured: four words read back correctly while word 12 was corrupted.
And the reference comparison has to cover memory the block should not have touched. That is where the extra transfer is visible and the only place it is.
11. Understanding Check
None of them individually. The fifth one should not have existed.
Every transfer was legal. Presented with CYC_O and STB_O asserted, carrying a valid address and valid data, acknowledged by the slave in the ordinary way. The slave had no basis on which to refuse it — it was never told how many to expect.
The defect is in the count, which lives only in the master. beat_q == len_q instead of beat_q == len_q - 1, so the master kept going one phase past the end.
Word 12 is the damage — a location the client never asked about, overwritten with data belonging to a transfer that was never requested.
Which is why the evidence has to be a count comparison. Requested four, observed five. There is no signal on the bus that carries the number four, so nothing in the trace is marked wrong.
12. What Module 14 Established
A bus cycle groups transfers. It does not merge them, order them, bound them, or protect them.
14.1 — cycle and phase are different units. One CYC_O, four terminations, measured. The phase boundary is an answered clock edge, not an STB_O pulse — which is why STB_O edges and ACK_I-high clocks both count the wrong thing.
14.2 — the saving is a constant and the latency is a multiplier. Three clocks saved at every latency tested; a ratio that fell from 1.60× to 1.18× with nothing in either design changing.
14.3 — a block declares nothing. Four phases at words 8, 12, 9 and 15, all acknowledged. B3 does have burst metadata — CTI_O() and BTE_O() — in the Registered Feedback profile, which is not this one.
14.4 — the count lives only in the master. Six words written and read back against a reference; four phases at four latencies with nothing moving mid-phase; and one comparison changed producing a fifth transfer that no rule, checker or read-back objects to.
The thread is a single question asked four ways: what did the slave actually get told? It was told a transfer is qualified. Every expectation beyond that — length, order, exclusivity, overlap — is either the master's private state or somewhere else entirely, and every defect in this module is one of those expectations quietly assumed.
13. What's Next
Block transfers are complete. Many transfers, one tenure, measured throughput, verified counts.
And CYC_O turned out to be a statement about tenure that guarantees nothing about interference. The normative BLOCK figure has a second signal running alongside it that this module named and did not use.
What does it take to make a sequence of transfers genuinely indivisible — and why is that a different mechanism rather than a longer block?
Module 15 — Read-Modify-Write Cycles takes up LOCK_O and the third defined cycle type, where the guarantee is not grouping but exclusion. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Need for Standardized Interconnects
An address map answers where a register lives. It says nothing about which wires carry the request, when they are valid, how the target reports completion, or what happens on an error. Three peripherals with three private interfaces produce three adapters, three verification efforts and three ways to be wrong — which is the argument for standardising the interface rather than the map.
- Related topic
The Open Hardware Movement
Source availability and reusability are different properties. A published core tells you what it does; it does not tell you what it requires, and requirements are what integration runs on. What reusable open IP needs beyond the RTL — licensing, documentation, an interface contract, verification, maintenance — and where open hardware is honestly weaker than its advocates claim.
- Related topic
SoC Communication
Six chapters built the pieces; this one assembles them into a working fabric and traces three real accesses through it. The result works, and reading the nine unwritten rules a third party would need is what makes the case for a published protocol concrete rather than theoretical.
- Related topic
Transaction Lifecycle
One Wishbone transaction from a master's decision to act through the slave's termination and back to the caller: what is fixed by the protocol, what every implementation may vary, and what a real simulation of the assembled system shows at each step.
Standards & specifications
- Governing standard
- Wishbone SoC Interconnection Architecture (OpenCores)(opens OpenCores in a new tab)
Defines the Wishbone signal set, the bus cycles built from it and the interface rules a portable IP core must follow. It deliberately leaves interconnect topology, address map and arbitration policy to the integrator, so those are system decisions rather than requirements of the specification.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Wishbone curriculum.
