Wishbone · Module 3
The Slave Interface
A Wishbone slave never initiates. It observes a qualified request, interprets a local offset, and ends the transfer with exactly one of three terminations. Everything that makes it reusable comes from what it refuses to know: its own base address, the topology, and which master is asking.
Chapter 3.3 gave the initiating side. The responding side is not its mirror image — a slave has a genuinely different job, and the asymmetry is the first thing worth naming.
What must a Wishbone slave observe, decide, and return — and how does it stay reusable while doing it?
1. Observe, Decide, Return
| Observes | What it means to this slave |
|---|---|
CYC_I and STB_I | A transfer is being presented to me — both, per RULE 3.35 |
ADR_I | A local offset, not a system address |
WE_I | Direction |
SEL_I | Which byte lanes participate |
DAT_I | Write data, on a write |
| Returns | The decision it is making |
|---|---|
DAT_O | This is the value at that offset |
ACK_O | Done, successfully |
ERR_O | Done, and it failed |
RTY_O | Not now — ask again |
The asymmetry with the master. A master decides whether and when; a slave decides only what and how it ended. There is no slave-side equivalent of CYC_O because a slave has no tenure to claim, and nothing a slave drives is a request.
2. Latency Is a Slave-Side Property
This is the consequence that shapes slave design more than any other.
A slave decides how long a transfer takes, and it expresses that decision by withholding a termination. There is no separate readiness or stall signal in Wishbone Classic — the absence of ACK_O, ERR_O and RTY_O is the wait.
Three consequences follow.
A slave may take as long as it needs, and nothing in the master changes. That is the property Chapter 1.5 argued for structurally and Chapter 3.3 built the master half of.
A slave must be certain it will eventually terminate. A condition that never becomes true is a system hang, and there is no protocol-level timeout to save it. Engineering interpretation, not a rule: this is why a slave whose completion depends on an external event — a clock-domain crossing, an off-chip response — needs a deliberate answer to what if the event never arrives, and ERR_O is usually that answer.
A slave must not terminate a transfer it was not given. RULE 3.35 makes termination a response to CYC_I and STB_I; a slave that terminates on STB_I alone corrupts other slaves' transfers in any shared interconnection while working perfectly point-to-point.
3. RTL — A Real Wishbone GPIO Slave
Four registers with genuinely different semantics, byte-lane handling, and a termination for every case including the illegal one.
// ─────────────────────────────────────────────────────────────────────────
// wb_gpio_slave — a Wishbone Classic SLAVE controlling a set of pins.
//
// PURPOSE. Exhibit every slave obligation from Section 1 on a peripheral
// with real register semantics: read/write, read-only, write-one-to-clear,
// and an offset that does not exist.
//
// Register map, by LOCAL offset. The slave has no idea where it sits in the
// system map — that is the INTERCON's business (Chapter 3.5).
// 0x00 DIR r/w 1 = pin is an output
// 0x04 OUT r/w value driven on pins configured as outputs
// 0x08 IN r/o synchronised pin state
// 0x0C STATUS w1c sticky "a pin changed" flags
//
// SCOPE. ERR_O is used for an offset that does not exist and for a write to
// a read-only register — that is enough to show termination is a THREE-way
// choice. The full semantics of error termination are Module 10's, and RTY_O
// is Module 11's; this slave never asserts it.
//
// Reset is SYNCHRONOUS, ACTIVE HIGH — RULE 2.30 and RULE 3.00.
// ─────────────────────────────────────────────────────────────────────────
module wb_gpio_slave #(
parameter int unsigned AW = 8, // LOCAL offset width
parameter int unsigned DW = 32,
parameter int unsigned PINS = 8
) (
input logic clk_i,
input logic rst_i,
// ── Wishbone SLAVE interface ─────────────────────────────────────────
input logic cyc_i,
input logic stb_i,
input logic we_i,
input logic [AW-1:0] adr_i, // LOCAL offset
input logic [DW/8-1:0] sel_i,
input logic [DW-1:0] dat_i,
output logic [DW-1:0] dat_o,
output logic ack_o,
output logic err_o,
output logic rty_o,
// ── The hardware this slave exists to control ────────────────────────
output logic [PINS-1:0] pins_out,
output logic [PINS-1:0] pins_oe,
input logic [PINS-1:0] pins_in
);
localparam logic [AW-1:0] OFF_DIR = 'h00;
localparam logic [AW-1:0] OFF_OUT = 'h04;
localparam logic [AW-1:0] OFF_IN = 'h08;
localparam logic [AW-1:0] OFF_STATUS = 'h0C;
logic [PINS-1:0] dir_q, out_q, status_q;
logic [PINS-1:0] in_meta_q, in_sync_q, in_prev_q;
// ── RULE 3.35: the qualified-transfer term. BOTH inputs, always.
// Using stb_i alone works point-to-point and breaks in any shared
// interconnection — the conformance error Section 6 is about.
logic xfer;
assign xfer = cyc_i & stb_i;
// ── Offset classification. Three outcomes, mutually exclusive by
// construction: a legal operation, a write to a read-only register,
// and an offset that does not exist.
logic addr_legal, write_ro;
always_comb begin
unique case (adr_i)
OFF_DIR, OFF_OUT, OFF_IN, OFF_STATUS: addr_legal = 1'b1;
default: addr_legal = 1'b0;
endcase
end
assign write_ro = xfer & we_i & (adr_i == OFF_IN);
// ── Termination. This slave is never busy, so it ends every qualified
// transfer in the cycle it is presented. A slave that needed time
// would hold ALL THREE low — that absence is the wait, because
// Classic has no stall signal.
//
// RULE 3.45: never more than one of the three at a time. Here ack_o
// and err_o are complementary within `xfer`, and rty_o is tied low,
// so exclusivity holds by construction.
assign err_o = xfer & (~addr_legal | write_ro);
assign ack_o = xfer & ~err_o;
assign rty_o = 1'b0;
// A write may change state only if it was a legal, non-errored write.
logic write_ok;
assign write_ok = xfer & we_i & addr_legal & ~write_ro;
// ── Sequential state ─────────────────────────────────────────────────
always_ff @(posedge clk_i) begin
if (rst_i) begin
dir_q <= '0;
out_q <= '0;
status_q <= '0;
in_meta_q <= '0;
in_sync_q <= '0;
in_prev_q <= '0;
end else begin
// Two-flop synchroniser: pins_in is asynchronous to clk_i.
in_meta_q <= pins_in;
in_sync_q <= in_meta_q;
in_prev_q <= in_sync_q;
// Hardware sets the sticky change flags; the bus can only clear them.
status_q <= status_q | (in_sync_q ^ in_prev_q);
if (write_ok) begin
unique case (adr_i)
// Byte-lane honoured: these registers live on lane 0, so a byte
// write to a neighbouring lane must leave them alone.
OFF_DIR: if (sel_i[0]) dir_q <= dat_i[PINS-1:0];
OFF_OUT: if (sel_i[0]) out_q <= dat_i[PINS-1:0];
OFF_STATUS: if (sel_i[0])
// WRITE-ONE-TO-CLEAR. Note the ordering: the hardware set
// above and this clear are both assignments to status_q in the
// same always_ff, so the LAST one wins for the bits it
// touches. Writing the clear as a read-modify-write of the
// freshly-set value keeps a change that arrives in the same
// cycle as the clear from being lost.
status_q <= (status_q | (in_sync_q ^ in_prev_q)) & ~dat_i[PINS-1:0];
default: ; // OFF_IN is read-only
endcase
end
end
end
// ── Combinational read path. Zero-extended and assigned on every path,
// so no latch is inferred. Driving '0 when not selected matters: on a
// shared return path built as an OR reduction, a non-zero value from
// an unselected slave corrupts the selected one's data (Chapter 3.5).
always_comb begin
dat_o = '0;
if (xfer && !we_i && addr_legal) begin
unique case (adr_i)
OFF_DIR: dat_o = {{(DW-PINS){1'b0}}, dir_q};
OFF_OUT: dat_o = {{(DW-PINS){1'b0}}, out_q};
OFF_IN: dat_o = {{(DW-PINS){1'b0}}, in_sync_q};
OFF_STATUS: dat_o = {{(DW-PINS){1'b0}}, status_q};
default: dat_o = '0;
endcase
end
end
assign pins_out = out_q;
assign pins_oe = dir_q;
endmoduleReading this module
Purpose. A reusable peripheral that meets every slave obligation while knowing nothing about the system it lands in.
Interface contract. cyc_i & stb_i means a transfer is being presented to me. adr_i is a local offset in the range this slave defines. Everything else is qualified by that transfer.
Ownership. The slave drives exactly three things: dat_o, ack_o, err_o — plus rty_o, tied low. It reads everything else. No port is driven from both ends.
Combinational decisions. Five: is this a transfer for me; is the offset legal; is this a write to a read-only register; which termination; and what value to present on a read. All are functions of the current inputs and the current state.
Sequential behaviour. Six registers. dir_q and out_q are ordinary read/write state. status_q is hardware-set, bus-cleared, which is the semantics a read/write register cannot express. The three synchroniser stages exist because pins_in is asynchronous, and in_prev_q is what makes edge detection possible.
Timing. Every qualified transfer terminates in the cycle it is presented, because this slave is never busy. That is a property of this slave, not of the protocol.
Assumptions and simplifications. No wait states; rty_o never asserted; registers live on lane 0 only; no interrupt output; ack_o is combinational from cyc_i/stb_i, which constrains what a master may make its qualifiers depend on.
How it could fail — each of these is a real bug with a distinct symptom.
- Terminate on
stb_ialone. Passes point-to-point, corrupts other slaves' transfers in a shared system. - Drop
addr_legalfromwrite_ok. A write to a non-existent offset reportsERR_Oand lands in whichever register the case falls through to. - Drop the
sel_i[0]gate. A byte write to a neighbouring lane overwrites the register. - Drive
dat_ounconditionally. On an OR-based return path, this slave corrupts every other slave's read. - Write
status_q <= status_q & ~dat_ifor the clear. A pin change arriving in the same cycle as the clear is lost, because the hardware-set term is discarded. - Assert both
ack_oanderr_o. RULE 3.45 violation; here prevented byack_o = xfer & ~err_o.
Scaling. Adding registers costs one case arm each in two places. Adding wait states is the change that matters: ack_o stops being a function of the inputs and becomes a function of internal state, and the slave must then guarantee that state always reaches a terminating condition.
4. Verification — Slave-Side Invariants
// ─────────────────────────────────────────────────────────────────────────
// wb_slave_checker — invariants a conforming Wishbone Classic SLAVE must
// satisfy at Module 3's level.
//
// NOT a conformance suite; Module 26 owns Wishbone verification.
// ─────────────────────────────────────────────────────────────────────────
module wb_slave_checker #(
parameter int unsigned DW = 32
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic we_i,
input logic ack_o,
input logic err_o,
input logic rty_o,
input logic [DW-1:0] dat_o,
input logic [DW-1:0] ro_shadow // white-box: expected read-only state
);
default disable iff (rst_i);
logic term;
assign term = ack_o | err_o | rty_o;
// P1 — RULE 3.35. Termination is generated in response to the logical
// AND of CYC_I and STB_I. The single most common slave conformance
// error, and invisible in a point-to-point bring-up.
property p_term_qualified;
@(posedge clk_i) term |-> (cyc_i && stb_i);
endproperty
a_term_qualified : assert property (p_term_qualified)
else $error("termination asserted without CYC_I && STB_I");
// P2 — RULE 3.45. Never more than one termination at a time.
property p_term_exclusive;
@(posedge clk_i) $onehot0({ack_o, err_o, rty_o});
endproperty
a_term_exclusive : assert property (p_term_exclusive)
else $error("multiple terminations: ack=%b err=%b rty=%b", ack_o, err_o, rty_o);
// P3 — architectural, not a numbered rule. An unselected slave must not
// drive read data, or a shared OR-based return path merges two
// slaves' values into a number belonging to neither. The obligation
// belongs here because the INTERCON cannot enforce it.
property p_quiet_when_unqualified;
@(posedge clk_i) (!(cyc_i && stb_i)) |-> (dat_o == '0);
endproperty
a_quiet_when_unqualified : assert property (p_quiet_when_unqualified)
else $error("slave drove dat_o=%h outside a qualified transfer", dat_o);
// P4 — a read-only register is not modified by a bus write. Expressed on
// the STATE rather than the interface, because that is where the
// damage would be, and because an interface-only property cannot
// catch a slave that reports ERR_O and changes state anyway.
property p_ro_immutable;
@(posedge clk_i) $changed(ro_shadow) |-> !$past(term && we_i);
endproperty
a_ro_immutable : assert property (p_ro_immutable)
else $error("read-only state changed on a terminating write");
endmoduleWhy P4 is a state property. P1 to P3 constrain the interface. P4 catches the worst failure in Section 3's list — a slave that correctly reports ERR_O for a write to a read-only register and applies the write anyway. Software has been told the access failed; the register moved. No interface-level property can see that.
Tooling limitation. Icarus Verilog has no SVA support, so this checker — like every checker in Module 3 — was reviewed by inspection only. No tool available here has executed it. The synthesisable RTL is elaborated.
5. Failure Modes and Discriminating Evidence
Symptom: a slave works point-to-point and corrupts other slaves' transfers once an INTERCON is added.
Candidate causes. The slave terminates on stb_i alone. Or the INTERCON broadcasts an undecoded strobe.
Discriminating evidence. During an access aimed elsewhere, probe this slave's cyc_i, stb_i and ack_o. stb_i asserted at a slave that should not be selected means the INTERCON is broadcasting — a Chapter 3.5 fault. stb_i low with ack_o asserted means the slave is terminating on something else.
Property that catches it. P1.
Symptom: a read returns a value that looks like two registers merged.
Candidate causes. An unselected slave driving non-zero dat_o into an OR-based return path.
Discriminating evidence. Compare the returned value against each slave's dat_o in the termination cycle. A bitwise OR of two of them is conclusive and recognisable by eye.
Property that catches it. P3, at the offending slave.
Symptom: the master hangs on one particular offset.
Candidate causes. The slave asserts no termination for that offset — a default branch that falls through without terminating. Or the offset is outside the slave's decode and nothing else answers.
Discriminating evidence. Probe the slave's three termination outputs with stb_i asserted. All three low is conclusive: the slave received the transfer and did not end it. In wb_gpio_slave this cannot happen, because ack_o and err_o partition xfer — a structure worth copying.
Likely RTL location. The termination expressions, specifically any path where a case arm can be reached without asserting one.
Symptom: a write to a read-only register reports an error and takes effect.
Candidate causes. The write-enable term omits the read-only condition.
Discriminating evidence. Write to OFF_IN, confirm err_o, then read back. Both true is conclusive.
Property that catches it. P4.
Symptom: pin-change flags are occasionally lost.
Candidate causes. The write-one-to-clear discards a change that arrived in the same cycle as the clear.
Discriminating evidence. Force a pin change in the exact cycle a clear is written and check whether the flag survives. This is a one-cycle race that random stimulus finds rarely and a directed test finds immediately.
Likely RTL location. The status_q clear expression — whether it clears from status_q or from the freshly-set value.
6. Common Mistakes
"A slave can terminate on STB_I; CYC_I is redundant."
Wrong mental model: both are asserted together, so checking one is enough.
Concrete bug: in a shared interconnection, the slave responds to strobe activity belonging to a cycle it is not part of — terminating other slaves' transfers and, on a merged return path, contributing data to them.
Observable evidence: a slave that passes every point-to-point test and breaks the day a second slave is added.
Correct model: RULE 3.35 requires termination to be generated in response to the logical AND of CYC_I and STB_I. Both, always.
"A slave should check that the address belongs to it."
Wrong mental model: the slave is responsible for recognising its own transfers.
Concrete bug: a base address inside the slave, which cannot then be instantiated twice or relocated — and which answers at unintended addresses if the comparison is incomplete.
Observable evidence: adding a second instance of the peripheral requires editing the peripheral.
Correct model: selection is the INTERCON's job. The slave receives an already-decoded strobe and interprets a local offset. That split is what makes it reusable.
"A never-busy slave can tie its acknowledge high."
Wrong mental model: nothing to wait for, so permanent readiness is fine.
Concrete bug: the slave terminates every transfer in the system, including ones aimed elsewhere.
Observable evidence: transfers completing immediately and returning data from the wrong peripheral.
Correct model: RULE 3.35 again. A never-busy slave asserts its termination as a function of the qualified transfer — assign ack_o = xfer & ~err_o; — not as a constant.
"Read data only needs to be right when I am selected."
Wrong mental model: the INTERCON will ignore an unselected slave's data.
Concrete bug: on a return path built as an AND-OR reduction, an unselected slave driving non-zero data ORs into the selected slave's value.
Observable evidence: reads returning a value sharing bits with two different registers.
Correct model: the INTERCON cannot enforce this — it can only select. Driving '0 when unqualified is the slave's obligation, which is why P3 is bound at the slave.
7. Interview Reasoning
Responsible for three things. Observing a qualified request — CYC_I and STB_I together, per RULE 3.35. Deciding what the local offset means and what value to return. And ending the transfer with exactly one of ACK_O, ERR_O or RTY_O, which RULE 3.45 makes mutually exclusive.
And an obligation underneath those: every qualified transfer must eventually end. Wishbone Classic has no stall signal, so withholding a termination is how a slave makes the master wait — and a condition that never becomes true is a system hang with no error anywhere.
It must refuse to know four things: its own base address, because that is the INTERCON's; the topology, because the slave interface is identical from point-to-point to crossbar; which master is asking, because requests are anonymous; and what other slaves exist.
The framing that shows understanding: a slave's reusability comes entirely from the refusals. Every one of them is convenient to violate, and each violation welds the block into one system.
8. Understanding Check
9. What's Next
Both endpoints are now specified by responsibility rather than by signal list. A master owns the request and holds it; a slave observes, decides and terminates; and each stays reusable by refusing to know things the other end or the fabric owns.
Those refusals have to be somebody's job. The slave does not know its base address; the master does not know which slave an address reaches. Something in between holds the map.
If masters and slaves implement compatible Wishbone interfaces, what still has to happen between them?
Chapter 3.5 — The Interconnect builds it. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- 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.
- 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
Control Signals
Address and data are payload; they say what values are involved and nothing about what should happen to them. Control information is what makes a bus interpretable: a qualifier that says a request is real, a direction, lane enables, a completion and an error — each derived from a failure that occurs without it.
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.
