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:
- the peripheral-side address must not increment
- the endpoint's readiness is its own, expressed only as wait states
- consumption must be exactly once per committed transfer
1. One Address, Not A Window
// ── 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.
2. Two Increment Enables, Because There Are Two Endpoints
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:
6 items, 0x0200 -> 0x8000, DST_INC disabled.Measured — the destination address after six writes:
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.
// ── 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:
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.
-> 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:
// 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.
// ── 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:
the stream the ENDPOINT emitted, in order:
expected 6 observed 6 value mm 0 ordering mm 0The 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:
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:
rig items fifo accepted wrong-offset bp clks
correct 6 6 0 72
INC_FIXED 6 1 5 0Six writes. One arrived. Five went somewhere else.
-> 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:
rig dma viol proto viol unknown remaining
correct 0 0 0 0
INC_FIXED 0 0 0 0Zero 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.
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:
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:
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;
endBut 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:
// 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:
| counter | question it answers |
|---|---|
accepted_o | how many items entered the FIFO through the bus |
emitted_o | how many the endpoint pushed downstream |
backpressure_o | how many clocks it could not accept one |
wrong_adr_o | how many bus writes hit a different register |
The relationships between them are the audit:
acceptedbelow the DMA'sdst_ok— the DMA counted writes the FIFO did not take.wrong_adr_osays where they went.acceptedequal todst_okbut the emitted stream is short — items are still queued, which is legitimate mid-transfer and a bug after completion.backpressurezero 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:
rig items fifo accepted wrong-offset bp clks
correct 6 6 0 72
INC_FIXED 6 1 5 06 = 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_othe 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:
- duplication — a commit clock that fires more than once per transfer (
SIDE_ON_STBon the read side, §6) - loss — a write that succeeds somewhere other than the data register (
INC_FIXED_ENDPOINT, §5) - 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
| claim | what 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
Related tutorials
- Related topic
Why Multiple Masters Exist
The same four-word copy performed twice — by the CPU and by a second initiator — with the bus traffic counted both times, and the specification's own definition of a master.
- Related topic
CPU + DMA Systems
Two initiators in contention, with the clocks counted in four columns: holding work, asking for the bus, owning it, and being answered. Contention cost two clocks out of twenty-one.
- Related topic
Read Logic
RULE 3.65 qualifies DAT_O with the termination, so everything the read caused is qualified too. Fire a FIFO pop on the presented clock instead and four words leave for one read — a write can be repeated, a popped word is gone.
- Related topic
DMA Master
Wishbone B3 contains no DMA, so every decision an engine makes is local policy. Three invariants make it correct — and the defect that breaks the worst of them passes every conformance check ever written.
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.
