Wishbone · Module 19
Open Hardware Systems
picorv32, SERV and NEORV32 each attach to Wishbone differently and each documented its own side because nothing else does — plus a withdrawn request, and two ways of not making progress that look identical from the core.
Chapter 19.2 looked at a generator that has already decided everything. This chapter looks at the cores it would be deciding about, and finds that no two of them present the same interface.
If three open RISC-V cores each attach to Wishbone differently, what exactly is the thing they are each doing?
1. Three Cores, Read Side by Side
| core | what its own README says about its bus |
|---|---|
| picorv32 | "The native memory interface of PicoRV32 is a simple valid-ready interface that can run one memory transfer at a time." A separate module, picorv32_wb, is "The version of the CPU with Wishbone Master interface". |
| SERV | "The bus interface is kind of Wishbone, but with most signals removed. There's an important difference though. Don't send acks on the instruction or data buses unless serv explicitly asks for something by raising its cyc signal." |
| NEORV32 | "32-bit external bus interface - Wishbone-compatible (XBUS)", with a separate "bridge for AXI4-compatible interfaces" in rtl/system_integration. |
Three cores, three shapes, one bus. The differences are not disagreements about Wishbone — every one of those right-hand edges is a conformant master port. The differences are about what sits to the left of it, which is the part no specification reaches.
Read those three rows as one finding. One core has a native interface and a separate Wishbone wrapper. One has a reduced Wishbone with an explicitly documented extra constraint. One has a Wishbone-compatible port and ships a bridge to a different protocol entirely.
None of them is wrong, and none of them is what the other two did. That is what "unspecified" looks like in practice.
2. SERV's Sentence Is the Whole Module in One Line
"Don't send acks on the instruction or data buses unless serv explicitly asks for something by raising its cyc signal."
That is a requirement on the slave side that appears in no specification. B3's RULE 3.30 says a slave may not respond when CYC_I is negated — so SERV's warning is, in one reading, just B3 being restated. But SERV's authors put it in their README in bold-adjacent terms, which tells you they had been bitten.
It also tells you something sharper. A core author does not write that sentence unless the integrator's mistake is plausible. The failure being warned about is not a protocol violation by the core; it is an integration habit — a testbench or a bridge that acknowledges eagerly.
3. What a "Native Interface" Actually Is
picorv32's README gives the cleanest statement of the contract on the core's side:
"The core initiates a memory transfer by asserting
mem_valid. The valid signal stays high until the peer assertsmem_ready. All core outputs are stable over themem_validperiod."
That last sentence is SEAM 1's first obligation, and it is the one an adapter is most likely to break. The traffic generator built for this module enforces exactly it:
// ─────────────────────────────────────────────────────────────────────────
// wb_core_model — a deterministic two-port traffic generator.
//
// THIS IS NOT A RISC-V CORE. It executes no instructions, has no register
// file, no decoder and no pipeline. It exists to produce two realistic
// master traffic streams and to consume what comes back. Nothing about
// timing, IPC or frequency is claimed for it, and its native interface
// does not match any real core's.
//
// What it DOES model is the part Module 19 is about: the shape of the
// contract a core presents to a bus adapter.
//
// ── THE NATIVE CONTRACT ─────────────────────────────────────────────────
// Two independent valid/ready request ports, one for instruction fetch and
// one for load/store. The shape is taken from a real core's documentation -
// picorv32's README states its native interface is "a simple valid-ready
// interface that can run one memory transfer at a time", and that "the
// valid signal stays high until the peer asserts mem_ready. All core
// outputs are stable over the mem_valid period."
//
// That second sentence is the whole of SEAM 1's first obligation, and this
// model enforces it: once a request is asserted it does not change and does
// not go away until it is accepted.
//4. The Failure That Breaks It
A request that has been accepted at the seam must not be withdrawn because the core stopped being ready. Readiness is about the answer, not about the request.
REQ_DROP is that mistake, made in one line of the adapter:
logic drop_now;
assign drop_now = REQ_DROP && busy_q && core_stall_i && !term;Read it as an argument somebody might make. "The core has stalled; it isn't going to take the answer; there's no point holding the bus." It is a plausible sentence and it is wrong, because the slave is already mid-transfer and the cycle is not the core's to withdraw.
SIM C puts the core into a stall while a three-wait-state RAM is still answering:
=== SIM C - the request belongs to the bus ===
the RAM inserts three wait states. The core is stalled by
something that is not the bus while the transfer is still
outstanding. picorv32's README states the contract this
tests: "All core outputs are stable over the mem_valid
period."
measure correct REQ_DROP
core requests accepted 3 3
bus transfers terminated 3 3
answers returned to the core 3 3
cycles that vanished unanswered 0 3
last value the core received correct 0xaaaa0002
broken 0xaaaa0002
-> the broken adapter withdrew a cycle the slave was
still answering. Nothing on the bus was violated;
a master simply stopped being there.Both systems delivered the same value to the core, and both counted three requests, three transfers and three answers. The only column that separates them is cycles that vanished unanswered: zero against three.
5. Two Ways of Not Making Progress
Here is the measurement that most directly repays reading a core's own documentation.
From inside the core, these two situations are indistinguishable: the pipeline is held by something that is not the bus, and a slave is withholding ACK. Nothing is progressing either way. From the seam, they are different counters, and conflating them produces performance analysis that blames the wrong subsystem.
=== SIM D - two ways of not making progress ===
first a load with no stall at all, then eight clocks of
stall with nothing outstanding.
after one load into a three-wait-state RAM
clocks the core was stalled by its pipeline 0
clocks a phase was presented and unanswered 4
after eight further clocks of stall, bus idle
clocks the core was stalled by its pipeline 8
clocks a phase was presented and unanswered 4
-> the second number did not move. A slave withholding
ACK and a pipeline holding itself are different
counters, and from the core they look identical:
nothing is progressing either way.The second number did not move. Eight clocks of pipeline stall with the bus idle added zero wait clocks, because there was no phase presented to wait on.
| who is refusing | what fixes it | |
|---|---|---|
| stall | the core | a faster core, a deeper pipeline, a different instruction mix |
| wait | the slave | a faster slave, a different address map, a cache |
These two counters are never added together anywhere in this module. Summing them produces a number that describes nothing — and the integration audit in Chapter 19.5 §8 prints them on separate lines with a note saying why.
6. Why Every Core Does This Differently
Because the choice is genuinely free, and the constraints that decide it are local.
| a core with | tends toward | because |
|---|---|---|
| a tiny area budget (SERV is bit-serial) | a reduced bus, extra documented constraints | every removed signal is real area |
| a native pipeline shape (picorv32) | a native interface plus a separate wrapper | the wrapper can be omitted when unused |
| multiple target ecosystems (NEORV32) | a compatible port plus bridges | the bridge is where the ecosystem-specific cost lives |
All three strategies are responses to the same gap. Wishbone does not describe a core; RISC-V does not describe a bus; so each core author writes down their own side and ships it.
That is why the READMEs are load-bearing documents — and why RULE 2.00 ("Each WISHBONE compatible IP core MUST include a WISHBONE DATASHEET as part of the IP core documentation") is not bureaucratic filler. It is B3 acknowledging that the specification cannot close the gap and requiring you to close it in writing.
7. What Carries Forward
| observation | where it is measured |
|---|---|
| a request, once accepted, belongs to the bus | §4 above, and 19.4 §3 |
| stall and wait are different counters | §5 above, and 19.5 §8 |
| a core is more than one master | 19.1 §5 |
what a core does with ERR_I is a documentation obligation | 19.4 §6 |
Continue learning
Related tutorials
- Related topic
RISC-V SoCs
Wishbone B3 contains the word CPU zero times and no interrupt signal at all — so this chapter builds the two seams that a conformant bus and a conformant processor still do not give you, and measures one whole system running across both.
- Related topic
FPGA SoCs
A two-bit address decode, an arbiter whose safety rests on a comment, and one wire called CYC at one end and STB at the other — reconstructed from the servant source at a recorded commit.
- Related topic
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.
- Related topic
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.
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.
