Skip to content
VLSI Mentor

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      -> 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // ── 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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]);
      end

Six items, checked against that model, with everything outside the destination window checked too:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      rig          items  value mm  ordering mm  out-of-range
      zero-wait     6/6          0            0             0

      source region after the copy: 0 word(s) changed

3. Independent Latency At Each Endpoint

The source and destination are two separate memory instances in the rig, for a reason:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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  4

Read four columns against each other:

  • ADR against ACK — the address is constant across clocks 7–10 while the write waits. RULE 3.60, enforced by construction: nothing but a successful termination changes dst_q.
  • hold against ACKhold changes at clocks 4 and 7, one clock after each read ACK. Never after a write ACK, never on presentation.
  • remaining against WE and ACK — 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 from WE; the difference is only which register the answer lands in.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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.
One DMA item with a slow destination. On the first cycles the engine is in its read state with CYC and STB asserted at the source address and WE low; the source acknowledges and the held data register takes the value on the following clock. The engine then moves to its write state, driving the held value at the destination address with WE high, and holds that entire request still for three wait states while the destination is not ready. Only when the destination acknowledges does the remaining count fall. A defective engine driving live bus data instead of the held value would present whatever the bus happened to carry during those wait states.source ACK — hold capturessource ACK — hold captureswrite held still for 3 waitswrite held still for 3waitsdest ACK — only now does count falldest ACK — only now doescount fallCLK_IstateRDRDWRWRWRWRWRWRRDRDWE_OADR_O0104010444044404440444044404440401080108ACK_Ihold005a0111125a0111125a0111125a0111125a0111125a0111125a0111125a011112remaining5555555544t0t1t2t3t4t5t6t7t8t9

5. Taking The Holding Register Away

LIVE_READ_DATA drives the destination write from whatever is on [DAT_I()] at that moment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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 itemcost
source read1 phase + source wait states
destination write1 phase + destination wait states
minimum2 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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 showswhat it meanswhere to look
every item wrong, none recognisablethe write data never came from the sourcethe holding register — LIVE_READ_DATA
every item equals the previous source wordcapture is one item latethe clock hold_q is written
right values, wrong orderaddresses advance independently of datasrc_q / dst_q update conditions
first N−1 correct, last missingthe loop exits one item earlythe final-item test
N items written, last one duplicatedthe loop exits one item latethe same test, other direction
some items skipped, gaps intactprogress advanced without a commitADVANCE_ON_PRESENT
correct, but words outside the window changedaddress 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      requested items          6
      source successes         6
      destination successes    6
      retries                  1
      remaining                0
  • src_ok greater than dst_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:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // 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_ok equal to N but remaining non-zero — two counters tracking the same thing and disagreeing, which means one of them is not keyed to the commit.
  • dst_ok greater than N — progress advanced on something that was not a completed write. COUNT_ON_RTY and ADVANCE_ON_PRESENT both land here.
  • retries non-zero and dst_ok too 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

symptomwrong mental modelactual bugwhat catches it
destination holds stale or foreign values"the read data is still on the bus"no holding registerreference model only
count falls while the write is waiting"issuing is transferring"progress keyed to presentv_rem_early, and RULE 3.60
final item missing or duplicated"count after the loop"off-by-one at N=1item-by-item audit
source region modified"a copy is a move"write-back to sourcesource re-check
neighbouring words clobbered"only the window matters"address or lane faultout-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

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.