Wishbone · Module 25
Memory Transfers
A source read and a destination write are two bus operations with an arbitrary gap between them. Remove the holding register and six of six items corrupt, with zero protocol and zero invariant violations.
Chapter 25.1 built the engine. This chapter makes it move a block of memory and asks what the data does while it is in transit.
The structural fact that drives everything here:
One data item costs at least two bus operations. A source read, then a destination write. They are separate phases, separately terminated, with a gap between them that the DMA does not control and cannot bound.
1. Not memcpy, And Not memmove
Before any measurement, a scope statement that belongs in the datasheet rather than in a reader's assumptions.
This engine reads forward and writes forward, one item at a time. For non-overlapping regions that is a correct copy. For overlapping regions it is not memmove, and the failure is silent:
- destination below source, forward copy — works by accident
- destination above source, forward copy — each write lands on a source word the engine has not read yet, and the tail of the copy is the head of itself
-> A COPY IS NOT A MOVE. Nothing in Wishbone or in
this engine would have stopped a design that
wrote back to the source; it simply never does.
NOT memcpy, AND NOT memmove. Overlapping source and
destination ranges are UNSUPPORTED by this engine and
that is a stated LOCAL POLICY, not an oversight: a
forward read-then-write loop corrupts a forward
overlap, and adding direction control is a different
engine.Adding a direction bit is not hard; pretending the problem does not exist is what ships. The engine declares the limitation, and the alternative — descending copy when the ranges overlap upward — is a different engine with a different descriptor.
2. The Scoreboard Does Not Ask The DUT
// ── THE REFERENCE MODEL. Built by the testbench from what it POKED,
// never from anything the DUT reports. A scoreboard that asks the
// suspect for the answer proves nothing. ──
logic [DW-1:0] expect_src [0:63];
logic [DW-1:0] expect_dst [0:63];And the memory endpoint provides a back door so the check never travels over the bus under test:
// The back door matters. A scoreboard that verifies the destination BY
// READING IT THROUGH THE DMA'S OWN BUS is asking the suspect to testify.
// peek_adr_i / peek_dat_o bypass the interface entirely.The seed pattern is ordering-sensitive on purpose. An all-zero or all-0xFF region cannot distinguish a copy that moved the right values into the wrong places:
for (k=0;k<16;k++) begin
expect_src[k] = 32'h5A00_0000 + (k*32'h0001_1111) + k;
poke(SRC + (k*4), expect_src[k]);
endSix items, checked against that model, with everything outside the destination window checked too:
rig items value mm ordering mm out-of-range
zero-wait 6/6 0 0 0
source region after the copy: 0 word(s) changed3. Independent Latency At Each Endpoint
The source and destination are two separate memory instances in the rig, for a reason:
// TWO memories rather than one, because Chapter 25.2 needs the source
// and destination endpoints to have INDEPENDENT latency. A single
// instance would force both to wait the same number of clocks, which
// is the one thing the experiment is trying to separate.And the latency is a sequence, not a random number:
// ── LATENCY IS A SEQUENCE, NOT A RANDOM NUMBER ──────────────────────────
// LAT_PATTERN is a packed vector of 4-bit wait counts, consumed one per
// transfer and wrapping. A fixed LATENCY is the special case where every
// entry is the same.
//
// Deterministic because Chapter 25.2 publishes cycle counts, and a number
// produced by $random is not evidence anybody can reproduce.Four rigs, the same six-item transfer:
MEASURED CLOCKS FOR THE SAME 6-ITEM TRANSFER
rig src wait clks dst wait clks items
zero-wait 0 0 6
slow src 12 0 6
slow dst 0 12 6
both slow 6 6 6
All four moved the same 6 items. The wait clocks are
the ENDPOINTS' cost, not the DMA's, and the engine
behaved identically in every case - which is the
point of RULE 3.60.PERMISSION 3.15 lets a slave's own state participate in the termination decision, and nothing in B3 bounds how long it may take. RULE 3.60 is what makes that survivable: the master holds the entire request still, so the slave returns to a question that has not moved.
And the honest caveat:
THIS IS NOT A THROUGHPUT NUMBER. It is what THIS
engine cost against THESE endpoints at THESE
latencies. Module 22 owns throughput analysis.4. One Item, Clock By Clock
The mixed-latency rig, traced from its own master port. The source waits 2, 0, 3, 1 clocks on successive transfers and the destination 1, 3, 2, 0 — a packed pattern consumed low-nibble-first:
clk state CYC STB WE ADR ACK hold remaining
1 RD 1 1 0 0100 0 00000000 6
2 RD 1 1 0 0100 0 00000000 6
3 RD 1 1 0 0100 1 00000000 6
4 WR 1 1 1 4400 0 5a000000 6
5 WR 1 1 1 4400 1 5a000000 6
6 RD 1 1 0 0104 1 5a000000 5
7 WR 1 1 1 4404 0 5a011112 5
8 WR 1 1 1 4404 0 5a011112 5
9 WR 1 1 1 4404 0 5a011112 5
10 WR 1 1 1 4404 1 5a011112 5
11 RD 1 1 0 0108 0 5a011112 4
12 RD 1 1 0 0108 0 5a011112 4
13 RD 1 1 0 0108 0 5a011112 4
14 RD 1 1 0 0108 1 5a011112 4Read four columns against each other:
ADRagainstACK— the address is constant across clocks 7–10 while the write waits. RULE 3.60, enforced by construction: nothing but a successful termination changesdst_q.holdagainstACK—holdchanges at clocks 4 and 7, one clock after each read ACK. Never after a write ACK, never on presentation.remainingagainstWEandACK— it falls at clocks 6 and 11, one clock after each write ACK. Never after a read.state— RD and WR drive identical wires apart fromWE; the difference is only which register the answer lands in.
Read the hold column against the ACK column. It
changes on exactly the clocks a READ was answered,
and on no others - not when the read was issued, not
while the destination was waiting. Read the remaining
column against the WE and ACK columns: it falls only
where a WRITE was answered.5. Taking The Holding Register Away
LIVE_READ_DATA drives the destination write from whatever is on [DAT_I()] at that moment:
assign dat_o = LIVE_READ_DATA ? dat_i : hold_q;With a zero-wait destination this is sometimes still the right value — which is why the rig gives the destination wait states:
rig items value mm ordering mm out-of-range
slow dst 6/6 0 0 0
LIVE_DATA 6/6 6 0 0
-> LIVE_READ_DATA CORRUPTED 6 OF 6 ITEMS.
It drives the destination write from whatever is
on [DAT_I()] at that moment instead of from the
holding register. RULE 3.65 makes a slave's
[DAT_O()] valid only on ITS termination clock, so
by the time the destination write is answered the
source's data is long gone from the wire.Chapter 24.2's RULE 3.65 is the rule being broken from the other side. The slave is entirely correct to stop driving read data once its phase ends. The DMA is wrong to still be looking.
And here is the part that matters
accounting and conformance:
rig dma viol proto viol unknown remaining
zero-wait 0 0 0 0
slow src 0 0 0 0
slow dst 0 0 0 0
both slow 0 0 0 0
LIVE_DATA 0 0 0 0
-> READ THE LIVE_DATA ROW. Zero DMA invariant
violations, zero protocol violations, and 6 of
6 items wrong. It held its addresses still, it
counted every commit exactly once, it finished
with remaining=0 - AND IT MOVED THE WRONG BYTES.
No checker in this module catches it. Only the
reference model does.A conformance suite passes this design. The DMA's own invariant checker passes it. The accounting is perfect. Every byte is wrong.
That is the strongest statement this module makes, and it generalises: protocol conformance and local invariants both describe how a transfer was conducted. Neither describes what was moved. Only a reference model built independently of the DUT can say that.
6. Bus Occupancy Is Not Useful Work
The six-item transfer performs twelve bus operations — six reads and six writes — plus wait states at each. With the "both slow" pattern it spent 6 source wait clocks and 6 destination wait clocks on top.
The structural floor for this architecture is fixed and worth stating plainly:
| per item | cost |
|---|---|
| source read | 1 phase + source wait states |
| destination write | 1 phase + destination wait states |
| minimum | 2 bus operations, never fewer |
A single-entry holding register cannot overlap a read with a write. RULE 3.35 permits exactly one outstanding phase, so the read of item n+1 cannot begin until the write of item n is answered — which the checker enforces as an invariant:
// a single-entry holding register cannot be more than one ahead
if (src_ok_i > (dst_ok_i + 16'd1))
n9 <= n9 + 16'd1;A deeper engine would need somewhere to put the extra items, and Classic Wishbone would still only let it have one phase in flight. The win would be overlapping the endpoint latency, not the bus phases — which is a different design and not this one.
7. Byte Lanes Travel With The Item
assign sel_o = {SW{1'b1}};This engine moves whole 32-bit items and asserts every lane. [SEL_O()] is not decoration even so — B3's description is precise about what it means:
"The select output array
[SEL_O()]indicates where valid data is expected on the[DAT_I()]signal array during READ cycles, and where it is placed on the[DAT_O()]signal array during WRITE cycles."
A DMA with a byte-granular length would drive a partial [SEL_O()] on the first and last items, and Chapter 24.3 built the slave-side masking that makes that safe. This engine's item-count convention is exactly what lets it avoid the question, and saying so is more useful than implying byte granularity it does not have.
8. Debugging A Corrupt Copy
The value of the counters is that they narrow the search before anyone opens a waveform. Start from what the destination looks like, because each corruption signature points at a different clock.
| what the destination shows | what it means | where to look |
|---|---|---|
| every item wrong, none recognisable | the write data never came from the source | the holding register — LIVE_READ_DATA |
| every item equals the previous source word | capture is one item late | the clock hold_q is written |
| right values, wrong order | addresses advance independently of data | src_q / dst_q update conditions |
| first N−1 correct, last missing | the loop exits one item early | the final-item test |
| N items written, last one duplicated | the loop exits one item late | the same test, other direction |
| some items skipped, gaps intact | progress advanced without a commit | ADVANCE_ON_PRESENT |
| correct, but words outside the window changed | address or lane fault | [SEL_O()], lane masking, decode |
Then read the counters against each other, because their relationships are diagnostic even when each looks plausible alone:
requested items 6
source successes 6
destination successes 6
retries 1
remaining 0src_okgreater thandst_ok+ 1 — a second read started before the held item was written. Impossible for a single-entry engine, so the holding discipline is broken. The checker watches for it:
// a single-entry holding register cannot be more than one ahead
if (src_ok_i > (dst_ok_i + 16'd1))
n9 <= n9 + 16'd1;dst_okequal to N butremainingnon-zero — two counters tracking the same thing and disagreeing, which means one of them is not keyed to the commit.dst_okgreater than N — progress advanced on something that was not a completed write.COUNT_ON_RTYandADVANCE_ON_PRESENTboth land here.retriesnon-zero anddst_oktoo high by exactly that amount — retries are being counted as transfers. Chapter 25.3 measures this directly.
Only then is a waveform worth opening, and the trace in §4 shows what to align: the hold column against read ACKs, and the remaining column against write ACKs. If either changes on a clock with no corresponding ACK, the commit timing is the bug — and the address column will usually show the request moving underneath an open phase, which is also a RULE 3.60 violation a protocol monitor can see.
The reason the counters come first
LIVE_READ_DATA produces all six items wrong with every counter perfect. An engineer who started from the counters and stopped there would conclude the DMA was fine. The order is: destination contents, then counters, then waveform — because the contents are the only thing that can distinguish "moved nothing correctly" from "moved everything correctly".
9. Failure Modes This Chapter Covers
| symptom | wrong mental model | actual bug | what catches it |
|---|---|---|---|
| destination holds stale or foreign values | "the read data is still on the bus" | no holding register | reference model only |
| count falls while the write is waiting | "issuing is transferring" | progress keyed to present | v_rem_early, and RULE 3.60 |
| final item missing or duplicated | "count after the loop" | off-by-one at N=1 | item-by-item audit |
| source region modified | "a copy is a move" | write-back to source | source re-check |
| neighbouring words clobbered | "only the window matters" | address or lane fault | out-of-range check |
10. What This Chapter Did Not Build
- No overlap support. Stated in §1 as policy, not discovered.
- No byte-granular length. Items, one convention, per Chapter 25.1.
- No burst. A Wishbone BLOCK cycle has no length field; Chapter 25.4 takes up what it is for.
- No second buffer. RULE 3.35 caps the engine at one outstanding phase regardless.
- No synthesis. Every clock count here is a clock count, not a nanosecond.
Next: Chapter 25.3 — Peripheral Transfers changes one endpoint from an array into a stream, and finds that the address which must not move is the one most likely to.
Continue learning
Related tutorials
- Related topic
Bus Transactions
A transaction is the unit of bus work: one beginning, one ending, and an interval in between during which the request must not move. Making waiting expressible is what lets a slow target share a bus with a fast one, and it is what turns an initiator from a wire into a state machine with real failure modes.
- Related topic
ACK_I
The only mandatory termination. What a slave promises by asserting it, how wait states work without a wait signal, and why RULE 3.55 requires a master to keep working when a slave holds it asserted.
- Related topic
STB — Strobe
A transfer is presented for as long as the master waits and accepted in exactly one cycle. A slave that confuses the two performs its write once per waiting cycle, on a bus that stays perfectly conformant.
- Related topic
Wait States
A slave throttles a read by withholding its acknowledge. The transfer does not disappear — and a read with a side effect must fire once for the whole wait, not once per cycle.
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.
