Skip to content
VLSI Mentor

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

itemevidence
projectPicoRV32
repositorygithub.com/YosysHQ/picorv32
commitef203c2b0a3fb793280f5114941416c425c5b461
commit date2026-09-07
inspected2026-09-22
modulepicorv32_wb, in picorv32.v
licenceISC
statusARCHIVED 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:

  1. 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.
  2. 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
	localparam IDLE = 2'b00;
	localparam WBSTART = 2'b01;
	localparam WBEND = 2'b10;

ORIGINAL SOURCE EXCERPT — YosysHQ/picorv32, picorv32.v, module picorv32_wb, commit ef203c2b.

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

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

questionanswer, 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:

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

OUR 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
				WBEND: begin
					mem_ready <= 1'b0;

					state <= IDLE;
				end

ORIGINAL 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
	output reg wbm_stb_o,

ORIGINAL SOURCE EXCERPT — same module.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
	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 wrapperSERV / servant
CYC and STBtwo registers, same valueone wire
could negate STB inside a cycleyes, structurallyno
costone extra flopnone
what it buysthe option of a master-throttled block cyclenothing 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.

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

The PicoRV32 Wishbone wrapper as a three-state machine and the path from core intent to architectural progress. The core raises a memory-valid request carrying an address, write data and write strobes. In the idle state the wrapper latches all of that into output flip-flops and asserts both cycle and strobe, moving to the wait state. In the wait state it does nothing but watch the acknowledge input; every Wishbone output is a register that nothing writes, so the request holds still by construction for as long as the slave takes. When the acknowledge arrives it captures the read data, signals ready to the core and moves to the end state, which spends one further clock dropping ready before returning to idle. There is no error input anywhere on the path, so an error termination is an event this wrapper cannot observe.coremem_valid, addr,wdata, wstrbIDLELATCH the wholerequestWBSTARTwait for ACK — nocounter, no escapeWBENDone clock to dropmem_readycore proceedsmem_ready seenoutputs areflopsRULE 3.60 holdsstructurallyno ERR inputan error isunobservable here12

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 CYC and STB are two registers rather than one wire;
  • that a separate WBEND state 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.
  • CYC and STB as 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 ERR input means no slave may ever answer ERR.
  • 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

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.