Wishbone · Module 29
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.
Every system in Modules 1–28 was built to teach something. This one was built to run on an FPGA, and that changes what it looks like.
Read the source, reconstruct the machine. Module 28 argued you should be able to derive an answer rather than recall one. This module points that at code you did not write.
1. What We Inspected
| item | evidence |
|---|---|
| system | SERV, and the servant SoC that wraps it |
| repository | github.com/olofk/serv |
| commit | f200eb2ed7b69ac1c6b8eddd47654522aeee5ce8 |
| commit date | 2026-08-25 |
| inspected | 2026-09-22 |
| files | servile/servile_mux.v, servile/servile_arbiter.v, servile/servile.v, servant/servant.v, rtl/serv_state.v |
| licence | ISC at the repository root; the servile files carry SPDX-License-Identifier: Apache-2.0 |
| status | canonical upstream, not a fork, not archived; an open-source FPGA SoC |
This is an open-source FPGA SoC. Every claim below is scoped to that commit. It is not a statement about SERV's whole history, about FPGA SoCs in general, or about anything having shipped in silicon.
2. Architecture, Reconstructed From Source
| item | evidence |
|---|---|
| Wishbone profile | Classic-style; no STALL, no pipelining, no CTI/BTE |
| masters | one CPU presenting two request ports, instruction and data |
| slaves | RAM, plus a timer and a GPIO behind an "external" port |
| address width | 32 |
| data width | 32 |
| interconnect | two combinational modules, servile_mux and servile_arbiter |
| arbitration | instruction bus wins; relies on a stated CPU property |
| decode | two address bits, ADR[31:30] |
| termination | ACK only |
ERR usage | NOT PRESENT in the inspected files |
RTY usage | NOT PRESENT in the inspected files |
| timeout policy | NOT PRESENT in the inspected files |
| byte selects | present, 4 bits, carried to the memory port |
| memory map | below 0x40000000 is memory; at or above is "external" |
| verification | a testbench directory and a RISC-V compliance flow exist; NOT inspected for this chapter |
| source revision | f200eb2e |
Two rows deserve attention before anything else. There is no ERR_O, no RTY_O and no timeout anywhere in the bus path. The entire termination vocabulary of this system is one signal.
3. The Decode Is Two Bits
Here is the whole address decision, from servile_mux.v:
wire ext = (i_wb_cpu_adr[31:30] != 2'b00);
assign o_wb_cpu_rdt = ext ? i_wb_ext_rdt : i_wb_mem_rdt;
assign o_wb_cpu_ack = i_wb_ext_ack | i_wb_mem_ack | sim_ack;ORIGINAL SOURCE EXCERPT — olofk/serv, servile/servile_mux.v, commit f200eb2e.
Module 12 built base-and-mask comparators, overlap detection and a default responder. This design has none of them, and it is not an oversight: with one memory region and one aggregate "everything else" port, two address bits are a complete decode. There is nothing to overlap and nothing to leave unmapped.
The acknowledgement is an OR across the two targets with no provenance latch, which is safe here for the same reason — only one of the two strobes is ever asserted, because they are derived from the same ext term.
SOURCE FACT: the decode uses ADR[31:30].
DERIVED INFERENCE: therefore every address from 0x00000000 to 0x3FFFFFFF reaches the memory port, whatever the memory's actual size — the aliasing is absorbed by the RAM's own narrower index, which servant.v takes as wb_mem_adr[$clog2(memsize)-1:2].
4. The Arbiter, And The Comment That Makes It Correct
assign o_wb_cpu_dbus_ack = i_wb_mem_ack & !i_wb_cpu_ibus_stb;
assign o_wb_cpu_ibus_ack = i_wb_mem_ack & i_wb_cpu_ibus_stb;
assign o_wb_mem_adr = i_wb_cpu_ibus_stb ? i_wb_cpu_ibus_adr : i_wb_cpu_dbus_adr;ORIGINAL SOURCE EXCERPT — olofk/serv, servile/servile_arbiter.v, commit f200eb2e.
Look at what steers the response: i_wb_cpu_ibus_stb, the same live signal that steers the request. If that signal changes while a transfer is in flight, the acknowledgement is delivered to the other requester.
Chapter 27.4 built exactly this structure on purpose and measured 99 misdelivered terminations with zero protocol violations. Chapter 28.4 named it as the finding in a design review.
So why is it right here? Because of the first lines of the file:
/*
* servile_arbiter.v : I/D arbiter for the servile convenience wrapper.
* Relies on the fact that not ibus and dbus are active at the same time.
*/ORIGINAL SOURCE EXCERPT — same file.
The assumption is written down next to the code that depends on it. That is the difference between engineering and luck. The selector cannot move under a live transfer because SERV never asserts both request lines, and serv_state.v is where that property is produced.
Lift this arbiter out and put it in front of two independent masters and it becomes the Chapter 27.4 defect on the first clock they overlap.
So our reconstruction does not take the comment on trust. It counts the clocks on which the assumption would have been violated:
// ── the assumption, made measurable ─────────────────────────────────
// The inspected file says it relies on ibus and dbus never being active
// together. This counter is how Lab A checks whether the stimulus has
// honoured that, instead of assuming it.
logic [15:0] nboth_q, nmem_q, next_q;VLSI MENTOR RECONSTRUCTION — wb_sv_soc.sv. Preserves the two-bit split, the I-wins merge and the live-selector response steering. Omits SERV's bit-serial datapath, the simulation hooks in servile_mux, and the peripherals. No byte-equivalence to SERV source is claimed or intended.
The result:
memory phases completed 3
external phases completed 1
clocks with I and D both active 05. One Wire, Two Names
This is the finding that repays reading the source rather than the block diagram.
serv_state.v produces the core's request qualifiers:
assign o_dbus_cyc = !o_cnt_en & init_done & i_dbus_en & !i_mem_misalign;ORIGINAL SOURCE EXCERPT — olofk/serv, rtl/serv_state.v, commit f200eb2e.
The core drives o_ibus_cyc and o_dbus_cyc. There is no separate strobe. servile.v then connects them to wires named for the other signal:
.o_ibus_cyc (wb_ibus_stb), .o_dbus_cyc (wb_dbus_stb),and servant.v completes the round trip, connecting a port named cyc to a wire named stb:
.i_wb_cpu_cyc (wb_ext_stb),ORIGINAL SOURCE EXCERPTS — servile/servile.v and servant/servant.v, commit f200eb2e.
The same physical wire is CYC at one end and STB at the other, because in this system they are the same signal. B3 permits precisely that, with a condition:
PERMISSION 3.40 — If a MASTER doesn't generate wait states, then
STB_OandCYC_OMAY be assigned the same signal.
Chapter 28.1 taught that permission and its condition. Here is a shipped FPGA SoC using it, and the naming inconsistency is the visible trace of the decision. A reader who insists that CYC and STB must be distinct signals will conclude this design is broken. It is not; it has taken an option the specification offers.
DO NOT GENERALIZE: this works because this master never negates its strobe inside a cycle. A master that throttles — one performing block cycles with gaps between transfers — cannot collapse the two, and PERMISSION 3.40 does not permit it to.
6. One Transaction, Traced
Three data accesses through our reconstruction of that path:
=== LAB A - SERVANT TRANSACTION PATH ===
step address decode target cycle data returned
0 0x00000010 adr[31:30]=0 memory 1 0xc0de0004
1 0x40000000 adr[31:30]=1 ext 5 0x713e7000
2 0x00000020 adr[31:30]=0 memory 7 0xc0de0008OUR MEASUREMENT, from a VLSI Mentor reconstruction — not from running SERV.
Step by step, and every decision names the boundary it happens at:
| step | boundary | what decides | result |
|---|---|---|---|
| 1 | CPU data port | the load's address | 0x00000010 presented |
| 2 | servile_mux | ADR[31:30] != 2'b00 | false → memory side |
| 3 | servile_arbiter | is the instruction bus asking? | no → data address forwarded |
| 4 | memory port | RAM index adr[7:2] | word 4 selected |
| 5 | memory | its own acknowledgement | ACK |
| 6 | servile_arbiter | ack & !ibus_stb | steered to the data bus |
| 7 | CPU | data captured | 0xc0de0004 |
Step 2 is the only address decision in the entire system.
7. Local Policies
Everything in this list is this SoC's choice, not Wishbone's:
- the split at
0x40000000, and that the upper three-quarters of the space is one aggregate port; - that an unmapped address does not exist as a concept — there is no hole to fall into and therefore no default responder;
- that termination is
ACKonly, so a peripheral has no way to refuse an access; - that the instruction bus outranks the data bus;
- that the arbiter may steer by a live selector, licensed by a CPU property;
- that
CYCandSTBare one wire.
The trade-off, stated
Every omission above buys something and costs something, and the design is coherent because the same answer fits all of them:
| decision | what it costs | what it buys |
|---|---|---|
ACK only, no ERR/RTY | a peripheral cannot refuse or defer | no error path in the CPU, no policy to define |
| two-bit decode | no room to grow the map without changing it | a decoder that is one comparison |
| live-selector response steering | correctness depends on a CPU property | no ownership state anywhere |
CYC and STB as one wire | the master can never throttle inside a cycle | one fewer wire on every port |
The pattern is the same each time: this design consistently declines optional capability in exchange for not having the state that implements it. That is a defensible position for a core whose selling point is its size, and an indefensible one for a system that needs any of the four.
8. What Not To Generalize
Do not conclude that Wishbone systems do not need ERR. This one does not, because nothing in it can fail an access in a way software could act on. A system with an address hole, a read-only region or a peripheral that can be busy needs a vocabulary this one has deliberately not paid for.
Do not conclude that two-bit decodes are sufficient. They are sufficient for two targets.
Do not copy the arbiter. Its correctness is a property of the master in front of it. Copying it without the comment — or worse, with the comment and without checking that the new master obeys it — reproduces a defect Module 27 spent a chapter measuring.
Do not read "small" as "simplistic". Every omission in this design is an omission somebody could name. That is what distinguishes a minimal design from an incomplete one.
9. What To Carry Forward
- Find the address decision. In this system it is one line, and everything else about routing follows from it.
- A live selector steering a response is a question, not a verdict. Ask what prevents it from moving; if the answer is written down, that is a good design.
- Naming inconsistencies in source are evidence.
cycconnected tostbis PERMISSION 3.40 leaving a fingerprint. - Absent signals are decisions. No
ERR, noRTY, no timeout — three deliberate omissions, each with a consequence. - One repository proves one repository. Everything here is scoped to commit
f200eb2e.
Chapter 29.2 looks at a system nobody wrote by hand.
Continue learning
Related tutorials
- Related topic
FPGA Design Challenges
Six peripherals and two masters on one FPGA is where on-chip integration stops being theory. The decode that must be exhaustive and one-hot, the read multiplexer that grows with every target and carries the critical path, the latency and reset conventions that refuse to agree, and the point at which the fabric rather than the peripherals starts failing timing.
- Related topic
Address Space
An address is a number until a decoder turns it into a target selection and a local offset. Range comparison and mask comparison are different engineering choices with different costs; exhaustive, mutually exclusive decode is a property to be proved rather than assumed; and a flat decoder's critical path is what eventually forces a hierarchy.
- Related topic
The Interconnect
Two conforming Wishbone interfaces still cannot talk without something between them. The INTERCON holds the address map, distributes exactly one strobe, merges read data and terminations, and answers for addresses nobody owns — and the specification defines it by its job rather than by a signal set.
- Related topic
Address Decoding
A bus address answers two questions, not one. Measured across a three-target SoC, including the boundary where one window ends and the next begins.
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.
