Wishbone · Module 1
CPU to Peripheral Communication
A CPU reaches hardware outside itself by reading and writing addressed locations, and a peripheral is hardware it cannot execute. Everything a driver does has to be expressed as a read or a write of a location the peripheral answers for — and once more than a couple of peripherals exist, wiring each one to the core separately stops scaling. That is the problem an on-chip bus is the answer to.
Most Wishbone material opens with a signal table — an address output, a data output, a data input, a write-enable, a pair of request signals and an acknowledge. That table is correct and it is the wrong place to start, because it answers a question the reader has not yet been given.
This chapter gives the question. It contains no Wishbone signals at all, deliberately, and by the end of it the shape of the answer should already be visible.
A processor spends its life executing instructions out of memory. A UART, a timer and a GPIO block are none of those things — they are hardware that the processor cannot execute and cannot contain. So by what mechanism does software running on the core change what a piece of hardware beside it is doing?
Every on-chip bus, Wishbone included, is an answer to that sentence. Understanding the sentence first is what makes the answer look inevitable rather than arbitrary.
1. What a CPU Can Actually Do
Start with the core, because the core is more limited than it first appears.
An instruction set contains arithmetic, branches, comparisons and moves between registers — all of which are internal. The operations that reach outside the core are, with the exceptions named below, all of one shape: an addressed read or an addressed write. A load names an address and asks for the data at it; a store names an address and supplies data to be put there. Instruction fetch reaches outside too, but it is a read like any other — issued by the fetch unit rather than by a load.
Note that addressed read or write is the claim, not load and store specifically. x86's IN and OUT are also addressed reads and writes; what differs is the address space they name and the instructions that name it, not the shape of the access. Which address space peripherals live in is Chapter 1.2's question, and it has more than one defensible answer.
Strip away the mnemonic differences and every one of those has the same external shape:
| Component | What it says | Who supplies it |
|---|---|---|
| direction | is this a read or a write | the core |
| address | which location | the core |
| write data | the value being stored | the core, on a write |
| read data | the value at that location | whatever owns the location, on a read |
| completion | this operation is finished | whatever owns the location |
That table is what a core offers software for reaching hardware. There is no instruction that means raise this pin, no instruction that means start counting, and no instruction that means send this byte out of the chip. Nothing in the architecture was left out — those operations belong to hardware the core does not contain.
Three honest qualifications: the first refines the shape, the second names core-to-hardware paths that are not buses at all, and the third is a genuine second mechanism — pretending otherwise would misdescribe how every real driver works.
Some cores have operations richer than a plain read or write. Atomic read-modify-write, load-exclusive and store-conditional pairs, and cache-maintenance operations all exist. They are still addressed operations that carry a direction and a location; they add constraints on ordering and atomicity rather than a new kind of access. Wishbone has a cycle type for exactly the read-modify-write case, which Module 15 covers.
Not every register a core touches is reached this way. RISC-V control-and-status registers are read and written by dedicated CSR instructions and live inside the core; they are not on any bus and no interconnect sees them. Tightly-coupled accelerator ports and custom-instruction interfaces are the same class — real core-to-hardware paths that are not buses and that this track is not about. The distinction worth keeping straight is the core's own state and its private ports against everything reached through the interconnect.
Reads and writes only go one way, and hardware sometimes needs to speak unprompted. Every operation above is initiated by the core. A peripheral cannot issue one, so it has no way to say a byte arrived except to wait until the core asks. That is what an interrupt is for, and it is a genuinely separate mechanism: a signal out of the peripheral that makes the core stop what it is doing and run a handler. Section 5 shows why the bus alone is not enough, and what an interrupt does and does not replace.
2. What a Peripheral Is, and Why It Cannot Be Software
A peripheral is a hardware block that does something the core cannot do, or cannot do well enough.
That definition is more useful than a list, because the list is long and keeps growing while the reason stays fixed. Four recurring reasons a function ends up in hardware:
It touches the outside world. A GPIO block drives and samples physical pins. A UART turns a byte into a serial waveform with a defined bit period. No sequence of instructions can drive a pad: the pad is driven by a cell whose input has to be held at a level between instructions, and holding it is what the GPIO block's flip-flop is for.
It must keep time independently of software. A timer counts on every clock edge whether or not the CPU is running, whether or not an interrupt handler is executing, and whether or not the core is stalled. Counting in software gives a period that varies with cache behaviour and interrupt load, which for a real-time deadline is the same as not counting at all.
It runs at a rate the core should not be spending cycles on. Shifting a byte out at one bit per serial clock could be done by software toggling a pin, and it would consume the core for the entire transfer. A SPI controller does it with a shift register and a divider, and the core spends a handful of accesses instead of thousands of cycles.
It is in a different clock or power domain. A block that must keep running while the core is asleep, or that is clocked from an external reference, cannot be replaced by instructions at all — there is no core running them.
Concretely, in a small SoC:
| Peripheral | What the hardware does | What software needs from it |
|---|---|---|
| GPIO | Drives output pins; samples input pins | Set and clear outputs, read inputs, choose direction |
| UART | Serialises and deserialises bytes at a fixed bit rate | Hand it a byte, take a received byte, know when either is possible |
| Timer | Counts clock cycles, reloads, flags expiry | Set a period, start and stop, learn that it expired |
| SPI controller | Shifts a word out and in under a generated clock | Supply a word, select a device, collect the response |
| Interrupt controller | Collects and prioritises requests from other blocks | Enable sources, learn which one fired, acknowledge it |
Read the right-hand column carefully, because it is the whole chapter in disguise. Every entry is one of two things: find out something the hardware knows, or tell the hardware to behave differently. Those are a read and a write. Nothing in that column asks for a third kind of operation.
3. A Small System, Concretely
Take a deliberately small design: one core, some memory, and three peripherals. This is roughly the shape of a microcontroller, and roughly the shape of the smallest useful FPGA SoC.
Three things in that picture are worth stating explicitly, because each is a decision rather than a fact of nature.
The core has one interface, not four. It presents a single read/write port and the interconnect deals with the fact that four different blocks are reachable through it. Section 6 is about what happens when that is not true.
The SRAM and the UART are on the same footing. From the core's side, both are things at addresses that answer reads and writes. That is a deliberate simplification of the interface, not a claim that they behave alike — Section 10 is about the ways they emphatically do not.
The interconnect is drawn as a block, which hides a choice. Whether it is a shared set of wires with one active initiator at a time, a set of point-to-point connections with multiplexing, or a crossbar that lets several transfers proceed at once, is an integration decision. Wishbone is unusually explicit that this is the integrator's choice: the specification fixes the interface a block presents and deliberately does not fix the fabric behind it. Module 3 and Module 18 take that up.
4. What Has to Cross the Boundary
Now derive the interface rather than being handed it. Ask what information must move between the core and a target for a single access to be meaningful, and take each item only when it is genuinely required.
Which target. A request aimed at everything is a request aimed at nothing. Something must identify one block.
Which location inside it. A UART has more than one thing worth touching: the byte to transmit, the byte received, the bit rate, the status. One identifier per peripheral is not enough.
Those two are usually carried by one field — an address wide enough that its upper part selects the block and its lower part selects the location inside it. How that split is chosen, and what it costs to get it wrong, is Chapter 1.2's subject. It is not obvious and it is not free, which is exactly why it gets a chapter of its own rather than a sentence here.
Direction. Read and write are not the same operation and cannot be inferred from the address: the same location is frequently both readable and writable.
The data. One path carrying a value from the core to the target on a write, and one carrying a value the other way on a read. Two paths rather than one shared bidirectional path is the normal on-chip choice, because a tri-state net inside a chip costs more than it saves — Chapter 2.3 develops this.
Which bytes. A 32-bit path is not always carrying 32 meaningful bits. Writing one byte of a four-byte word has to be expressible, or software cannot touch a byte-wide register without disturbing its neighbours. Module 13 is about the signal that carries this.
Completion. This is the item that is most often missed, and it is the one that makes the rest work.
Error reporting. A request can be meaningful and still fail — an address nothing owns, a write to a read-only location, a target in reset. A completion that can only mean success forces those to be reported some other way, or not at all. Modules 10 and 11 cover the alternative terminations.
Collect the list and the interface has assembled itself:
| Information | Direction | Why it cannot be dropped |
|---|---|---|
| address | initiator → target | Names the block and the location within it |
| direction | initiator → target | Read and write are different operations at the same address |
| write data | initiator → target | The value being delivered |
| byte selects | initiator → target | Not every access uses the full data width |
| read data | target → initiator | The value being returned |
| completion | target → initiator | Lets a slow target coexist with a fast one |
| error | target → initiator | Distinguishes a failed access from a successful one |
That is a bus interface, arrived at without naming one. Every on-chip bus you will meet — Wishbone, APB, AHB, AXI — carries these, and they differ in how many of them may be in flight at once, how they are named, and what else they add. Why APB exists reaches a nearly identical list from the Arm side, which is worth reading precisely because the two specifications were written independently.
5. Three Interactions, Worked Through
Abstraction is easier to trust after it has been made concrete three times. Each of these is a real driver operation reduced to the accesses it actually performs.
Sending a byte on a UART. The transmitter holds one byte at a time and takes a known number of bit periods to shift it out. Software may not overwrite a byte that is still being sent, so it must first find out whether the transmitter is free.
read UART status → a value whose "transmitter ready" bit is 0
read UART status → still 0
read UART status → now 1
write UART transmit data ← the byte to sendThree reads and a write, and the interesting one is the read. The status value is not stored by software and not computed by software; it is produced by the hardware's own state, and reading it is how software observes state it does not own. That is the fundamental thing a read of a peripheral does, and it is why a peripheral register is not the same object as a memory word.
Driving a GPIO pin. The pin's level is held by a flip-flop in the GPIO block. Software changes it by writing the register that flop lives in.
write GPIO direction ← mark the pin as an output
write GPIO output data ← the level the pin should holdThe write completes — how many cycles that takes is the GPIO block's business, not the instruction's — and the pin then holds that level indefinitely, with the core free to do anything else. The write did not perform the action; it changed the state that performs the action continuously. Most peripheral writes are of this kind, and reading them as commands rather than as state changes is one of the more durable beginner errors.
Configuring a timer. A period, then a start.
write timer reload value ← the count the timer restarts from
write timer control ← enable, and choose what expiry does
read timer status ← later: did it expire?Once started, the timer runs on the clock. It is not executing anything belonging to the CPU, and its accuracy does not depend on what the CPU is doing. Software's involvement is two writes at the start and, later, either a read to check or an interrupt that tells it without being asked.
What all three share. Every line above is a read or a write of a location that a peripheral, not memory, answers for. No new operation appeared. The hardware's behaviour changed because a value it was holding changed, and software learned about the hardware because it read a value the hardware maintains.
6. Why One Connection per Peripheral Does Not Scale
There is an obvious alternative to a bus, and working out where it fails is more instructive than being told that it does. Give the core a dedicated port for each peripheral.
For two peripherals this is genuinely fine, and for a design with one accelerator behind one core it is often the right answer — a dedicated port with no arbitration and no decode is faster and smaller than any fabric. The problem is what happens as the number grows.
The core changes every time the system does. Adding a fourth peripheral means adding a fourth port, which means editing, re-synthesising and re-verifying the core. The block with the least reason to change becomes the block that changes most often.
The peripheral cannot be reused. A UART written against this core's port shape has to be rewritten against the next one. The economics of IP reuse — and of an open-hardware ecosystem in particular — depend on a block being usable without modification in a system its author never saw.
The count grows the wrong way. Each port carries its own address, data-out, data-in, direction, byte-select and handshake wires. Ten peripherals is ten copies of all of that converging on one block, with the routing congestion and timing pressure that implies around the core.
Verification multiplies. Each port is its own interface with its own corner cases, and there is no shared definition of correct behaviour to check against. One interface used ten times can be verified once, thoroughly, and checked by one monitor. Ten bespoke interfaces cannot.
| Dedicated port per peripheral | One shared interface | |
|---|---|---|
| Core changes when a peripheral is added | yes | no |
| Peripheral portable to another system | no | yes |
| Interface definitions to verify | one per peripheral | one, reused |
| Wires converging on the core | grows with peripheral count | fixed |
| Concurrent accesses to different targets | possible | only with a fabric that allows it |
| Arbitration and decode logic needed | none | yes |
The last two rows are the honest cost, and they are why this is a trade rather than a strict improvement. A shared interface has to decide which target a request is for, and — when more than one initiator exists — which initiator goes first. Both are logic that the dedicated-port scheme does not need, and both add delay to every access. Module 12 is the decode, Module 17 is the arbitration.
What the trade buys is independence: the core stops knowing what is attached to it, and a peripheral stops knowing what it is attached to. Everything else on the list follows from that one property.
7. RTL — A Block That Answers Reads and Writes
The argument so far is architectural. This is what it looks like in RTL, using a deliberately generic request interface — not a Wishbone one, because the point here is the shape that any of them share.
// A minimal peripheral: two writable control locations, one readable status
// location. The request interface is generic on purpose — `req` means "an
// access is being requested", `we` chooses the direction, and `done` tells the
// initiator that the access has finished. Wishbone's own names and rules are
// Module 4's subject.
module simple_periph (
input wire clk,
input wire rst_n,
// Generic request interface
input wire req, // an access is being requested
input wire we, // 1 = write, 0 = read
input wire [3:0] addr, // which location inside this block
input wire [31:0] wdata,
output reg [31:0] rdata,
output wire done, // this access is complete
// The hardware this block actually controls
output wire pin_out,
input wire pin_in
);
localparam ADDR_CTRL = 4'h0; // enable, direction
localparam ADDR_OUT = 4'h4; // value driven onto the pin
localparam ADDR_STATUS = 4'h8; // read-only: what the hardware knows
reg ctrl_enable;
reg out_value;
reg pin_in_q;
// ── Writes change state the hardware then acts on continuously ──────────
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ctrl_enable <= 1'b0;
out_value <= 1'b0;
end else if (req && we) begin
case (addr)
ADDR_CTRL: ctrl_enable <= wdata[0];
ADDR_OUT: out_value <= wdata[0];
default: ; // ADDR_STATUS is read-only
endcase
end
end
// ── Reads are a multiplexer over state the hardware already holds ───────
// Combinational, so read data is valid in the same cycle `done` is, which
// is what makes the single-cycle completion below honest.
always @(*) begin
case (addr)
ADDR_CTRL: rdata = {31'b0, ctrl_enable};
ADDR_OUT: rdata = {31'b0, out_value};
ADDR_STATUS: rdata = {31'b0, pin_in_q}; // sampled from a pin
default: rdata = 32'b0;
endcase
end
// The block is fast enough to finish in the cycle it is asked, so `done`
// simply follows `req`. A block that needed longer would hold `done` low
// until it was ready — which is the whole reason completion is a signal.
assign done = req;
// Sample the input pin every cycle, whether or not anyone is reading it.
// One flop keeps the example about the interface; a real GPIO would use a
// two-flop synchroniser here, because the pin is asynchronous to `clk`.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) pin_in_q <= 1'b0;
else pin_in_q <= pin_in;
end
assign pin_out = ctrl_enable ? out_value : 1'b0;
endmoduleFour things in that module are worth reading deliberately.
The write path and the hardware path are separate. out_value is written by software and read continuously by the output assignment. The write is an event; the effect is permanent. Nothing re-executes after the write completes.
pin_in_q is sampled unconditionally. The status location does not go and look at the pin when it is read — the hardware is already tracking it, and the read reports what the hardware knows. This is the concrete form of a read observes state the core does not own.
done exists even though it is trivial here. It is req because this block is never busy. Writing it out anyway is what lets the same initiator talk to a block that is sometimes busy without any change on the initiator's side, and it is the hook every wait-state discussion later in the track hangs on.
The address is four bits and local. The block knows nothing about where it sits in a system. Which upper address bits select it is not its business — it is the integrator's, and that separation is what makes the block reusable. Chapter 1.2 is where that split becomes the subject rather than a footnote.
8. What an Interconnect Adds
Give the core the generic port from Section 7 rather than four bespoke ones, and something has to sit between it and the four targets. That something does three jobs, and separating them is the most useful thing to take out of this chapter.
It selects a target. The address arrives; exactly one block should see the request. What decides that is an address decoder, and the map it decodes is the system's, not any peripheral's. Chapter 1.2 and Module 12 own this.
It merges the responses. Read data and completion come back from whichever target was selected and must reach the initiator as one response. On a small system that is a multiplexer driven by the same decode.
It resolves competition, where there is any. A system with one initiator has none. A system with a DMA engine, a second core or a debug port has several, and something must decide whose request proceeds. Module 16 is multi-master systems and Module 17 is arbitration.
None of these three is the peripheral's problem, and none is the core's. That is the real content of the word interconnect: the responsibilities that belong to neither end, gathered into the fabric between them so both ends can be written without knowing the system they land in.
9. Where This Leaves the Design
The chapter set out to answer one question and answered it by deriving an interface rather than by presenting one. What remains is a short list of decisions that interface does not make.
Settled here:
- A core reaches hardware through the interconnect by addressed reads and writes; interrupts are the separate, inbound path back.
- Peripherals expose their behaviour as locations that answer those reads and writes.
- A peripheral location is an interface to behaviour, not necessarily a place that stores a value.
- A shared interface is what stops the core from changing every time the system does.
- The interface has to carry an address, a direction, data both ways, byte granularity, a completion and a failure indication.
Deliberately left open, each to the chapter that owns it:
| Question | Where it is answered |
|---|---|
| How are locations named, and who assigns the addresses? | Chapter 1.2 — Memory-Mapped IO |
| Why a published standard rather than a house interface? | Chapter 1.3 — Need for Standardized Interconnects |
| What are the actual signals, and who drives each one? | Module 4 |
| How does a request and its completion interlock? | Module 5 |
| How does an address become one target select? | Module 12 |
| Who goes first when several initiators want the bus? | Module 17 |
An entry above that is bold without a link is a chapter that has not shipped yet, which is this track's convention — a reference becomes a link on the day its page ships.
10. Common Misconceptions
"A peripheral register is just a memory location."
The wrong model: the address space is uniform, so a location is a location and the only difference is what lives at it.
What it costs: every symptom in Section 5's callout gets diagnosed as a hardware fault — a status register that "changes by itself", a write that "does not stick", a received byte that "disappears" when the debugger is open. The search goes into the peripheral's RTL, where there is nothing wrong.
The corrected model: a peripheral location is a port into hardware behaviour. It may have side effects on read, it may not store what is written, and reading it twice may legitimately give different answers. Memory semantics are the special case, not the rule.
"Memory-mapped I/O is how CPUs talk to peripherals."
The wrong model: mapping peripherals into the address space is the definition of peripheral access rather than one architecture among others.
What it costs: an engineer who cannot say why a system maps peripherals into memory, because they never knew it was a choice — and who is then unable to reason about the cases where it is made differently, or about why the mapped regions need treatment ordinary memory does not.
The corrected model: memory-mapped I/O is the dominant approach and is not the only one; Section 1 separates the universal part (the access is an addressed read or write) from the part that varies (which address space, reached by which instructions). Chapter 1.2 is where the choice and its consequences are actually argued.
"The CPU is the only thing that initiates bus transfers."
The wrong model: one core, one initiator, therefore arbitration is a formality.
What it costs: a design that is correct on the bench and fails as soon as a DMA engine is added, and an engineer who cannot explain why arbitration exists at all.
The corrected model: a DMA engine, a second core, a debug access port and a bus-mastering peripheral are all initiators. A system with more than one needs something to decide whose access proceeds, and whether that decision is fair, fast or starvation-free is a design choice with measurable consequences. Modules 16 and 17 are about it.
"A bus means shared wires, so only one transfer can happen at a time."
The wrong model: the word describes the physical arrangement, and the physical arrangement is a shared medium.
What it costs: it makes a crossbar look like a violation of the standard rather than an implementation of it, and it leads to performance estimates that assume a serialisation the system does not actually have.
The corrected model: the interface is what the standard fixes; the fabric is an integration decision. A Wishbone system may connect one master to one slave directly, may share a set of signals across many, or may use a crossbar in which two unrelated transfers proceed at once. All are conformant, and they have quite different throughput.
"If a peripheral's registers are readable and writable, the interface is done."
The wrong model: getting values in and out is the hard part.
What it costs: blocks that work in isolation and fail on integration — a slave that assumes a request is never withdrawn, a master that assumes a response arrives in a fixed number of cycles, two designers who each read the interface reasonably and differently.
The corrected model: the data movement is the easy half. The rules — when a request may be presented, how long it must be held, what may change while it is outstanding, what a target must do if it cannot answer — are what makes independent implementations interoperate. That is what a specification is for, and it is why the rest of this track is longer than a signal list.
11. Interview Reasoning
Directly wiring them works, and for a small fixed system it is a legitimate choice with real advantages: no decode delay, no arbitration, minimum logic.
What a strong answer adds is where it stops working:
- Every added peripheral changes the core, so the block least likely to need modification becomes the one edited most.
- A peripheral built against one core's private port cannot be reused in another system without being rewritten, which removes the entire economic case for IP reuse.
- The wire count into the core grows with the peripheral count, with the routing and timing pressure that implies exactly where it is least welcome.
- Each bespoke interface is verified separately, with no shared notion of correct behaviour to check against.
And the cost of the alternative, stated honestly: a shared interface needs address decoding, needs response multiplexing, and — with more than one initiator — needs arbitration. All three add logic and delay to every access.
The candidate who names both sides and then says what the trade buys is independence between the core and what is attached to it has demonstrated the model rather than recited a conclusion.
12. Understanding Check
13. What's Next
This chapter established that peripheral interaction reduces to reads and writes of locations the hardware answers for, and that a shared interface is what keeps the core independent of what is attached to it. It deliberately left one thing undecided, and it is the thing everything else now waits on:
If every peripheral exposes locations the CPU reads and writes, how do those locations get their addresses — and what happens to the rest of the system when they do?
Chapter 1.2 — Memory-Mapped IO answers it. Placing peripheral locations into the same address space the core already uses for memory is the decision that makes a plain load or store sufficient to reach hardware, and it brings its own consequences: an address map that has to be allocated and documented, decoding that has to be exhaustive, regions that must not be cached or reordered, and a class of failure in which a perfectly correct access reaches a block that was never meant to answer it.
From there Chapter 1.3 asks why a published interconnect standard is worth more than a house interface, which is where Wishbone as a specification rather than as an idea begins. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- 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
Memory-Mapped IO
Memory-mapped I/O does not turn a peripheral into memory. It gives the peripheral's registers addresses in the processor's address space, so an ordinary load or store selects them. The address then does two jobs — name the target, name the register inside it — and the map that assigns them is a contract between software and RTL.
- 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
Why Wishbone Was Created
Six chapters of engineering pressure produce a specific set of requirements: a fixed interface, a signalled completion, a synchronous reference, an interconnect the integrator still owns, and a licence a volunteer project can adopt without a legal review. Wishbone is what those requirements look like written down — including the things it deliberately refuses to decide.
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.
