Wishbone · Module 29
Educational CPUs
PicoRV32's three-state Wishbone wrapper: the capture invariant in shipped RTL, one clock of overhead per transfer, and a master that structurally cannot observe an error.
The usual claim made for small CPU projects is "Wishbone is simple, so beginners use it." That is both patronising and wrong about the interesting part.
The real claim is structural:
A stable bus contract lets CPU microarchitecture and peripheral implementation be reasoned about — and changed — separately.
That is worth as much to an expert as to a learner. This chapter reads two small CPUs that take opposite options at the same fork.
1. What We Inspected
| item | evidence |
|---|---|
| project | PicoRV32 |
| repository | github.com/YosysHQ/picorv32 |
| commit | ef203c2b0a3fb793280f5114941416c425c5b461 |
| commit date | 2026-09-07 |
| inspected | 2026-09-22 |
| module | picorv32_wb, in picorv32.v |
| licence | ISC |
| status | ARCHIVED on GitHub as of the 2026-09-22 check; head commit dated 2026-09-07 |
plus SERV at f200eb2e, already inspected in Chapter 29.1, for contrast.
The status row matters. The repository is archived, which means the code we read is a fixed historical snapshot rather than a moving target. That makes it better evidence for a case study, not worse — the commit will not change under the claims made here — but it would be wrong to describe this as current practice, and we do not.
2. The Contract Is The Point
Both projects place a bus interface between the core and everything else. That boundary does two things at once, and it is worth separating them:
- It lets the core change. PicoRV32 has parameters for dual-port registers, barrel shifters, two-cycle ALUs, compressed instructions and multipliers. None of them is visible at the Wishbone port.
- It lets the system change. The same core drives a RAM, a UART or a whole SoC without knowing which.
Module 3 called this the master interface. Here it is as an actual module boundary in shipped RTL, with a native core port on one side and Wishbone on the other.
3. Three States, And Every Output A Flop
localparam IDLE = 2'b00;
localparam WBSTART = 2'b01;
localparam WBEND = 2'b10;ORIGINAL SOURCE EXCERPT — YosysHQ/picorv32, picorv32.v, module picorv32_wb, commit ef203c2b.
IDLE: begin
if (mem_valid) begin
wbm_adr_o <= mem_addr;
wbm_dat_o <= mem_wdata;
wbm_we_o <= we;
wbm_sel_o <= mem_wstrb;
wbm_stb_o <= 1'b1;
wbm_cyc_o <= 1'b1;
state <= WBSTART;ORIGINAL SOURCE EXCERPT — same module.
Look at what that block does: the entire request is latched into output flip-flops in one clock, and then nothing writes them again until the transfer is over.
Chapter 28.2 argued that a wait-tolerant master is one with nothing live left to change, and that capturing the request is how you get there. This is what that looks like in code somebody ships. RULE 3.60's stability obligation — the master must qualify ADR_O, DAT_O(), SEL_O() and WE_O with STB_O and hold them still — is not maintained here by any logic. It is structural: the signals are registers written only in IDLE, so there is no path by which they could move while the phase is open.
The wait itself is three lines:
WBSTART:begin
if (wbm_ack_i) begin
mem_rdata <= wbm_dat_i;
mem_ready <= 1'b1;ORIGINAL SOURCE EXCERPT — same module.
No counter. No escape. No timeout. The CPU waits exactly as long as the slave takes, which is the only thing B3 permits a Classic master to do, since the specification bounds no latency and provides no stall signal.
4. What Stalls, And What Cannot
The brief question for any CPU integration is: what state waits, and what is allowed to make progress?
From the inspected source:
| question | answer, from source |
|---|---|
| what waits? | the wrapper FSM, parked in WBSTART |
| what does the core see? | mem_ready stays low; its memory interface is not answered |
| which Wishbone outputs hold? | all of them — they are flops written only in IDLE |
| what event permits progress? | wbm_ack_i, and nothing else |
| can another instruction retire meanwhile? | NOT VERIFIED — that is a property of the core, not of this wrapper, and we inspected the wrapper |
| what happens on an error? | nothing can happen — see below |
That fifth row matters. The wrapper tells you the bus is stalled; it does not tell you whether the pipeline behind it can do anything useful. Inferring CPU behaviour from bus behaviour is precisely the mistake Module 27 spent six chapters on, and the honest answer here is that we did not inspect the core's state machine.
Measured through our reconstruction of the wrapper, at two slave latencies:
=== LAB E - CPU WAIT-STATE BEHAVIOUR ===
the same four operations through the same three-state FSM
slave issued completed wait clocks WBEND clocks
0 waits 4 4 0 4
3 waits 4 4 12 4
word 1 after the run 0 waits 0x55556666 3 waits 0x55556666
last value read back 0 waits 0x55556666 3 waits 0x55556666OUR MEASUREMENT, from a VLSI Mentor reconstruction — not from running PicoRV32.
Four operations either way, identical committed data, identical value read back. Only the wait column moved. That is what a correct wait-tolerant master looks like, and it is the same differential Chapter 27.3 used to expose designs that were accidentally depending on same-cycle completion.
5. The Third State Costs A Clock
The WBEND column is 4 in both rows: one clock per transfer, regardless of how fast the slave answered.
WBEND: begin
mem_ready <= 1'b0;
state <= IDLE;
endORIGINAL SOURCE EXCERPT — same module.
That state exists to drop mem_ready after the core has seen it. It is not waste — it is the price of the clean handshake on the core side of the wrapper. But it means the floor for a transfer here is three clocks even against a zero-wait slave, and if you are counting cycles for a tight loop, the bus is not where that clock went.
DERIVED INFERENCE: any latency budget for this wrapper must be slave latency plus two, and the two are architectural rather than protocol-imposed.
6. The Same Fork, Two Answers
Now put the two CPUs beside each other at one specific decision.
PicoRV32 drives CYC and STB from two separate registers, always written with identical values:
output reg wbm_stb_o,ORIGINAL SOURCE EXCERPT — same module.
output reg wbm_cyc_o,ORIGINAL SOURCE EXCERPT — same module.
SERV collapses them into one wire, connecting a port named cyc to a wire named stb — PERMISSION 3.40, as Chapter 29.1 traced through three files.
Both are legal. The difference is what each has kept open:
| PicoRV32 wrapper | SERV / servant | |
|---|---|---|
CYC and STB | two registers, same value | one wire |
could negate STB inside a cycle | yes, structurally | no |
| cost | one extra flop | none |
| what it buys | the option of a master-throttled block cycle | nothing it needs |
Neither is better. One paid a flop for an option; the other declined the option and the flop.
7. The Master That Cannot Hear An Error
Here is the finding that makes this a real integration lesson rather than a tour.
input wbm_ack_i,ORIGINAL SOURCE EXCERPT — same module.
There is no wbm_err_i in the port list. This master literally cannot observe an error termination. Connect it to a slave that answers ERR_O and the wrapper sits in WBSTART forever, because the one event it waits for never arrives.
The specification anticipates exactly this:
OBSERVATION 3.35 — If the SLAVE supports
ERR_OorRTY_O, but the MASTER does not support these signals, deadlock may occur.
Note what that does not say. It does not say either side is non-conformant — B3 makes ERR and RTY optional on both sides through PERMISSION 3.20 and PERMISSION 3.25. Two conformant components can deadlock when integrated, and the specification tells you so in advance.
This is also why the LiteX watchdog of Chapter 29.2 answers with ACK rather than ERR: a framework that must work with masters like this one cannot assume an error will be understood.
SOURCE FACT: picorv32_wb has wbm_ack_i and no error input.
DERIVED INFERENCE: a system using it must guarantee no slave asserts ERR_O, or install something that converts errors into acknowledgements before they reach it.
8. Educational Does Not Mean Toy
Both of these CPUs are small enough to read in an afternoon, and that is a genuine property worth naming:
- the whole bus interface is one module you can hold in your head;
- every protocol obligation is visible as a line of RTL rather than buried in generated logic;
- the trade-offs are explicit enough to argue about.
What Not To Generalize
What that does not license:
Do not say "this is how CPUs talk to memory." Neither of these has a cache, a store buffer, multiple outstanding requests, or a memory ordering model. A processor with any of those has a bus interface that looks nothing like a three-state machine, and the reasoning it requires is different in kind, not just in size.
Do not read three clocks per transfer as typical. It is typical of this wrapper. It is a consequence of a specific handshake choice on the core side.
Do not conclude that Wishbone is for small CPUs. What these two demonstrate is that a small CPU can be integrated through a stable contract — not that the contract has an upper bound.
The local policies, listed
Nothing in this list is Wishbone. Each is a decision the inspected wrapper made:
- that the request is latched into output flops rather than driven live;
- that
CYCandSTBare two registers rather than one wire; - that a separate
WBENDstate exists, at one clock per transfer; - that there is no error input, and therefore no error policy at all;
- that there is no timeout, so the wait is unbounded by construction.
The fourth is the one that constrains everybody else. It is a local policy that propagates outward into a requirement on every slave in the system.
9. What To Carry Forward
- Capture the request in flops and the stability rule stops being something you maintain. PicoRV32 does it in one block; that is the whole of its RULE 3.60 story.
- Count the states, not just the waits. One of PicoRV32's three clocks per transfer is never the slave's fault.
CYCandSTBas one wire or two is an option with a price and a payoff, and two real projects took opposite sides.- A missing input is a hard integration constraint. No
ERRinput means no slave may ever answerERR. - Two conformant components can deadlock. OBSERVATION 3.35 says so, and here is a master that makes it concrete.
- Inspecting a wrapper tells you about the wrapper. What the pipeline behind it does while stalled is
NOT VERIFIED.
Chapter 29.5 asks what a bus contract is worth when the thing on the other side of it is somebody else's experiment.
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.
