Skip to content
VLSI Mentor

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

ObservesWhat it means to this slave
CYC_I and STB_IA transfer is being presented to me — both, per RULE 3.35
ADR_IA local offset, not a system address
WE_IDirection
SEL_IWhich byte lanes participate
DAT_IWrite data, on a write
ReturnsThe decision it is making
DAT_OThis is the value at that offset
ACK_ODone, successfully
ERR_ODone, and it failed
RTY_ONot 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.

A Wishbone slave has three obligations. It observes a qualified request, meaning both the cycle and strobe inputs asserted together. It decides what the local offset means and what value to return. And it returns exactly one of three terminations, ending the transfer. Separately, four things are deliberately outside a slave's knowledge, and refusing to know them is what makes it reusable: its own base address in the system map, the interconnection topology, which master is asking, and what other slaves exist. An additional obligation sits underneath all of it: every qualified transfer must eventually end, because Wishbone Classic has no stall signal and a slave that never terminates hangs the master.ObservesCYC_I and STB_I togetherDecideswhat the local offsetmeansReturnsone of three terminationsWishbone slave portanswers, never initiatesNot its baseaddressit sees an offsetNot the topologyp2p or crossbar,identicalNot which masterrequests are anonymousEvery transfer mustendno stall signal inClassic12
Figure 1 — a slave's three obligations, and the four things it must refuse to know.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// 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;
endmodule

Reading 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_i alone. Passes point-to-point, corrupts other slaves' transfers in a shared system.
  • Drop addr_legal from write_ok. A write to a non-existent offset reports ERR_O and 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_o unconditionally. On an OR-based return path, this slave corrupts every other slave's read.
  • Write status_q <= status_q & ~dat_i for 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_o and err_o. RULE 3.45 violation; here prevented by ack_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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// 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");
endmodule

Why 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 transferassign 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

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.