Skip to content
VLSI Mentor

Wishbone · Module 25

Peripheral Transfers

A stream has one address and returns something different each time. Backpressure is wait states, not a ready line — and a peripheral address that increments delivers one item of six, acknowledged every time.

Chapter 25.2 moved memory to memory: two addressable arrays, both endpoints incrementing. Change one of them to a peripheral and a single property changes everything.

A memory endpoint is an ADDRESSABLE ARRAY: address N names a location, and reading it twice returns the same thing. A peripheral data port is a STREAM: there is ONE address, and reading it twice returns TWO DIFFERENT ITEMS.

Three consequences follow, and they are the chapter:

  1. the peripheral-side address must not increment
  2. the endpoint's readiness is its own, expressed only as wait states
  3. consumption must be exactly once per committed transfer

1. One Address, Not A Window

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── THE DATA REGISTER IS AT EXACTLY ONE ADDRESS ──────────────────────
// A stream endpoint has ONE data port. The offsets around it are other
// registers - status, control, a divisor - and a write landing on them
// is not an error the bus can see: it is a perfectly good write to a
// different register, which simply is not the FIFO.
//
// So an access off the data address is ACKNOWLEDGED and DISCARDED. That
// is what a real peripheral does, and it is why an incrementing
// peripheral address loses data silently rather than hanging.

That last sentence is the whole of §5. A peripheral's neighbouring offsets are not holes — Chapter 24.1 spent a chapter on what a hole does, and this is the opposite case. The offsets around a data register are other real registers, so a stray write succeeds, is acknowledged, and changes something else.

The two endpoint shapes side by side. On the memory side the DMA presents a different address for every item, walking upward through an addressable array, and the source increment is enabled. On the peripheral side the DMA presents the same single data-register address for every item, because the endpoint is a stream whose neighbouring offsets are different registers entirely, and the destination increment is disabled. A dashed edge marks where an incorrectly incrementing destination address would send its writes: onto the peripheral's neighbouring registers, which acknowledge them and discard the data. The engine's descriptor carries a separate increment enable for each side, which is what lets one address advance while the other stays fixed.wb_dmaSRC_INC=1, DST_INC=0memory 0x0200item 0memory 0x0204item 1 — address advancesmemory 0x0208item 2FIFO 0x8000every item, one address0x8004, 0x8008where INC_FIXED writes land12

2. Two Increment Enables, Because There Are Two Endpoints

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  logic [AW-1:0] src_step, dst_step;
  assign src_step = (sinc_q || INC_FIXED_ENDPOINT) ? ITEM_BYTES[AW-1:0]
                                                   : {AW{1'b0}};
  assign dst_step = (dinc_q || INC_FIXED_ENDPOINT) ? ITEM_BYTES[AW-1:0]
                                                   : {AW{1'b0}};

A step of zero is a legitimate step. The descriptor carries SRC_INC and DST_INC separately precisely so one side can advance while the other does not, and memory-to-peripheral is programmed with DST_INC clear:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    6 items, 0x0200 -> 0x8000, DST_INC disabled.

Measured — the destination address after six writes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      final peripheral-side address: correct 0x8000  INC_FIXED 0x8018
      -> THE CORRECT RIG'S DESTINATION ADDRESS NEVER
         MOVED. Six writes, one address. The DMA advanced
         only the memory side.

3. Backpressure Is Wait States. There Is No Ready Line.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── BACKPRESSURE IS WAIT STATES. THERE IS NO "READY" SIGNAL. ────────────
// Wishbone Classic has no ready line, no credit, no stall channel. An
// endpoint that cannot accept an item says so by WITHHOLDING THE
// TERMINATION, and PERMISSION 3.15 is the licence: "Other signals,
// besides [CYC_I] and [STB_I], MAY be included in the generation of the
// cycle termination signals."

Do not invent a signal Wishbone does not have. A full FIFO simply does not answer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  logic ready;
  assign ready = we_i ? can_write : can_read;

  assign ack_o = sel_xfer && waited && (at_data ? ready : 1'b1);

And RULE 3.60 is what makes that safe from the DMA's side: the request is held still, so the item is neither lost nor duplicated — it has not happened yet.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        -> EXACTLY 6 ITEMS, IN ORDER, NONE DUPLICATED.
           The FIFO applied backpressure for 72 clocks by
           simply NOT ANSWERING. There is no ready line in
           Wishbone Classic; PERMISSION 3.15 lets the
           endpoint's own state decide when it terminates,
           and RULE 3.60 holds the DMA's request still
           meanwhile.

           WAIT IS NOT ERROR. A full FIFO has not failed
           the transfer - the transfer HAS NOT HAPPENED
           YET, so there is nothing to lose or repeat.

72 clocks of waiting, and the item count did not move once. "Wait is not error" is not a slogan here — it is the reason the DMA needs no special case for a busy endpoint at all.

Why not [RTY_O] instead?

This endpoint holds the phase open rather than sending the master away. That is a policy choice and the opposite of the one Chapter 24.5 argued for a busy register bank, and the module says so:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // A write to a full FIFO, or a read from an empty one, is answered with
  // NOTHING. Not ERR - the request is valid and will succeed later.
  // Not RTY - this endpoint keeps the phase open rather than sending the
  // master away, which is a LOCAL POLICY choice and the opposite of the
  // one Chapter 24.5 argued for a busy register bank. Both are legal;
  // they differ in who waits.

Both are legal; they differ in who waits. Wait states park the master and keep the bus occupied. [RTY_O] frees the bus and makes the master come back — better on a shared fabric, worse for a master that will simply spin. The endpoint knows which situation it is in; the protocol does not.

4. The Endpoint Drains Itself

A real peripheral empties: a UART shifts a byte out, a MAC puts a word on a wire.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── THE DRAIN MODEL ─────────────────────────────────────────────────────
// A real peripheral empties itself: a UART shifts a byte out, a MAC puts
// a word on a wire. DRAIN_EVERY models that, so the FIFO fills, applies
// backpressure, drains, and accepts again - which is what makes the
// DMA's wait behaviour observable rather than theoretical.

And the drained stream is the scoreboard's truth — captured from the endpoint as it emits, never asked of the DMA:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      the stream the ENDPOINT emitted, in order:
        expected 6   observed 6   value mm 0   ordering mm 0

The first version of this experiment measured zero backpressure clocks. The FIFO was four deep and drained every seven clocks, which is faster than the DMA could fill it — so the subject of the experiment never occurred. Depth 2 draining every 25 clocks fixed it, and the testbench now asserts that backpressure actually happened:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    end else if (g_fb == 0) begin
      $display("      FAIL: the endpoint never applied backpressure -");
      $display("            the experiment did not reach its own subject");
      errs++;

An experiment that cannot reach its own subject reports a clean pass for the wrong reason. Module 24 lost a gate this way; Module 25 checks for it explicitly.

5. The Address That Must Not Move, Moving

INC_FIXED_ENDPOINT increments both endpoints regardless of the descriptor:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      rig          items  fifo accepted  wrong-offset  bp clks
      correct          6              6             0       72
      INC_FIXED        6              1             5        0

Six writes. One arrived. Five went somewhere else.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      -> INC_FIXED_ENDPOINT walked the peripheral address:
         0x8018 at the end instead of 0x8000. 5 of its
         6 writes landed on a DIFFERENT REGISTER of the
         same peripheral and were acknowledged and
         discarded. The endpoint emitted 1 item(s).

         NOTHING FAILED. Every write was answered. A
         peripheral's other offsets are real registers,
         not holes, so the bus has no way to object -
         and on real silicon those writes would have
         reprogrammed a divisor or cleared a status word.

And the accounting:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        rig          dma viol  proto viol  unknown  remaining
        correct             0           0        0          0
        INC_FIXED           0           0        0          0

Zero on every checker. The DMA reported six successful destination writes because there were six successful destination writes — each one acknowledged by a peripheral that was perfectly entitled to acknowledge it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
           address it was told to hold - and NOTHING IN
           THIS MODULE'S CHECKERS KNOWS WHICH ADDRESSES
           WERE SUPPOSED TO BE FIXED. Only the endpoint's
           own wrong-offset counter and the stream
           scoreboard catch it.

Notice the second-order effect in the table: INC_FIXED shows 0 backpressure clocks, because it only ever put one item into the FIFO. A rig that is failing can look healthier on a secondary metric than a rig that is working.

6. Exactly-Once, Under Autonomous Traffic

Chapter 24.2 found the read-side-effect class on a single read: a FIFO that pops on the presented clock pops once per wait state. The endpoint here carries the same defect parameter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  logic pop_now;
  assign pop_now = POP_ON_PRESENT ? (sel_xfer && !we_i && at_data && can_read)
                                  : (ack_o && !we_i && at_data);

Under a DMA the severity changes even though the bug does not. A CPU driver reading a FIFO does so a handful of times, under a programmer who will notice missing bytes. A DMA reads it N times per descriptor, unattended, as fast as the fabric allows — and every wait state the endpoint inserts multiplies the loss.

The bug is identical. The blast radius is the difference between a peripheral driver and an autonomous engine that will happily repeat the mistake several thousand times before anybody looks.

This is why the correct commit clock matters more here than anywhere else in the curriculum: a write can be repeated, and a popped word is gone.

7. Peripheral To Memory

The reverse direction is the same architecture with the increment enables swapped: SRC_INC clear, DST_INC set. The engine needs no new state — which is the payoff for putting two independent enables in the descriptor rather than a single "increment" bit.

One asymmetry is worth naming rather than glossing. A read from an empty stream and a write to a full one are both backpressure, and the endpoint counts them the same way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      if (sel_xfer && at_data && !we_i && !can_read) begin
        // a read of an empty stream is backpressure too
        nbp_q <= nbp_q + 16'd1;
      end

But they fail differently in a system: a full output FIFO delays a transfer that will eventually complete, while an empty input FIFO may wait for an event that never arrives. B3 bounds neither, and RECOMMENDATION 3.10's interconnect watchdog — which Chapter 24.5 measured, including the cases it cannot rescue — is the system-level answer.

8. Completion Accounting Against A Stream

A memory destination can be re-read to check it. A stream cannot — the items are gone, downstream, and nothing the DMA can address will tell you what arrived. So the accounting has to come from the endpoint itself, and the endpoint has to be built to provide it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // the stream the endpoint actually emitted, in order - the scoreboard's
  // truth, captured on the drain rather than asked of the DUT
  output logic [DW-1:0] drained_dat_o,
  output logic          drained_vld_o,

Four counters, and each answers a different question:

counterquestion it answers
accepted_ohow many items entered the FIFO through the bus
emitted_ohow many the endpoint pushed downstream
backpressure_ohow many clocks it could not accept one
wrong_adr_ohow many bus writes hit a different register

The relationships between them are the audit:

  • accepted below the DMA's dst_ok — the DMA counted writes the FIFO did not take. wrong_adr_o says where they went.
  • accepted equal to dst_ok but the emitted stream is short — items are still queued, which is legitimate mid-transfer and a bug after completion.
  • backpressure zero on a rig designed to stress it — the experiment did not reach its own subject, which is §4's failure.

Measured, the correct rig reconciles exactly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      rig          items  fifo accepted  wrong-offset  bp clks
      correct          6              6             0       72
      INC_FIXED        6              1             5        0

6 = 6, and 0 wrong. And on the defective rig, 6 = 1 + 5 — every write is accounted for, one into the stream and five into whatever register sits at 0x8004 and 0x8008.

That arithmetic is the reason the endpoint counts wrong-address writes at all. Without wrong_adr_o the two rigs are distinguishable only by the downstream stream, and a peripheral whose downstream is a wire off the chip offers no such check.

Exactly-once, stated as an invariant

For a stream destination the correctness condition is not "the memory matches" but:

Every item enters the stream exactly once, in descriptor order, and no item enters that the descriptor did not name.

Three ways to break it, all of which this module can produce:

  1. duplication — a commit clock that fires more than once per transfer (SIDE_ON_STB on the read side, §6)
  2. loss — a write that succeeds somewhere other than the data register (INC_FIXED_ENDPOINT, §5)
  3. reordering — impossible for this engine, because RULE 3.35 permits one outstanding phase and the descriptor is walked in order; an engine with a deeper pipeline would have to prove it

The third row is worth dwelling on. Ordering is free here because of a protocol constraint, not because the design earned it. A DMA on a protocol that allows multiple outstanding transactions with out-of-order completion — which Chapter 20.2 compared — must reorder explicitly or constrain itself back to one.

9. Misconceptions This Chapter Corrects

claimwhat the measurement shows
"memory-to-peripheral is memory-to-memory with a different address"one endpoint is a stream; address progression differs
"the peripheral address should increment like memory"it delivered 1 of 6 items
"a full FIFO is an error"it is a transfer that has not happened; 72 wait clocks, 0 items lost
"Wishbone needs a ready signal for flow control"PERMISSION 3.15 already covers it; no new wire
"the DMA must skip an item if the endpoint is busy"skipping would lose data; RULE 3.60 makes waiting free

10. What This Chapter Did Not Build

  • No stream descriptors or rings. One descriptor, one endpoint.
  • No interrupt or event wiring. The endpoint drains on a timer because a real trigger belongs to the peripheral's own design.
  • No CDC. The endpoint is synchronous to the same clock; asynchronous FIFOs are explicitly outside this module.
  • No byte-granular stream. Items, per Chapter 25.1's convention.

Next: Chapter 25.4 — Arbitration Impact puts a CPU on the same fabric and asks what contention does to a DMA — and finds the answer is not the one the obvious guess predicts.

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.