Wishbone · Module 3
The Master Interface
A Wishbone master owns the address, the direction, the write data, the byte selects and both qualifiers, and must hold them until a termination arrives. What it is deliberately not allowed to know matters as much as what it drives — a master that knows the address map or a slave's latency has been coupled to one system.
Chapter 3.2 gave the model and a master that performs a single transfer. This chapter is about responsibility, with a master that does real work.
The distinction matters because a signal list is not a job description. Module 4 details each signal; what a master is for is decided here.
What must a Wishbone master know, decide, and hold responsibility for — and what is it deliberately not allowed to know?
1. What a Master Owns
| Drives | The decision it is making |
|---|---|
CYC_O | I am taking the interconnection and holding it until this cycle ends |
STB_O | This cycle carries a transfer — the request signals are meaningful now |
ADR_O | This is the location |
WE_O | This is the direction |
SEL_O | These byte lanes participate |
DAT_O | This is the value being delivered, on a write |
| Observes | What it must do about it |
|---|---|
ACK_I | Succeeded — capture DAT_I on a read, then close or continue |
ERR_I | Failed — stop, and report upward |
RTY_I | Not now — the transfer did not happen and may be re-issued |
DAT_I | Read data, meaningful in the termination cycle |
RULE 3.60 is the obligation underneath the first table: the master must qualify ADR_O, DAT_O, SEL_O, WE_O and the tag outputs with STB_O. Those signals are meaningful only while the strobe is asserted — which makes the master responsible for their stability across the whole time a transfer is outstanding.
2. The Four Things a Master Must Not Know
The right-hand side of Figure 1 is what gets designed wrong, because each is convenient to know.
Not the address map. A master receives an address from whatever drives it — a CPU's load/store unit, a descriptor, a configuration register. It does not know which slave that address reaches. Chapter 2.2 put decode in the INTERCON precisely so neither endpoint carries the map.
Not a slave's latency. Chapter 1.5 priced this: a master that knows latencies acquires one special case per slave and cannot absorb a register slice added for timing closure. In Wishbone Classic the master waits for a termination however long it takes, and there is no alternative — Classic has no separate stall signal.
Not a slave's internals. The software-visible register contract is the whole interface.
Not the topology. Chapter 3.1 §5: the master interface is identical across point-to-point, shared bus, crossbar and data flow. Only the INTERCON changes.
Engineering interpretation, not a rule: the specification does not forbid a master from knowing these things. It defines an interface that makes them unnecessary. The discipline is the designer's, and every violation is a coupling discovered the day the system changes.
3. RTL — A Copy Engine Master
A master that performs one transfer is not worth studying. This one is programmed with a source, a destination and a length, and moves words between them.
// ─────────────────────────────────────────────────────────────────────────
// wb_copy_master — a Wishbone Classic MASTER that copies a block of words
// from a source address to a destination address.
//
// PURPOSE. Exhibit every master responsibility from Section 1 on a block of
// work large enough that the state is real: request ownership, stability
// while outstanding, termination handling for all three outcomes, read-data
// capture, and a decision about what happens next.
//
// SCOPE. One transfer per bus cycle — CYC_O and STB_O rise and fall
// together. A BLOCK cycle would hold CYC_O across several transfers, which
// is Module 8's subject. ERR_I aborts and reports; RTY_I re-issues the same
// transfer. The detailed semantics of both are Modules 10 and 11.
//
// Reset is SYNCHRONOUS, ACTIVE HIGH — RULE 2.30 and RULE 3.00. RULE 3.20
// requires STB_O and CYC_O negated at the rising edge following assertion
// of RST_I, which forcing S_IDLE achieves.
// ─────────────────────────────────────────────────────────────────────────
module wb_copy_master #(
parameter int unsigned AW = 32,
parameter int unsigned DW = 32,
parameter int unsigned CW = 16 // transfer-count width
) (
input logic clk_i,
input logic rst_i,
// ── Local control side ───────────────────────────────────────────────
input logic start_i,
input logic [AW-1:0] src_i,
input logic [AW-1:0] dst_i,
input logic [CW-1:0] count_i, // number of words
output logic busy_o,
output logic done_o, // one-cycle pulse on completion
output logic error_o, // sticky until the next start
// ── Wishbone MASTER interface ────────────────────────────────────────
output logic cyc_o,
output logic stb_o,
output logic we_o,
output logic [AW-1:0] adr_o,
output logic [DW/8-1:0] sel_o,
output logic [DW-1:0] dat_o,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic [1:0] {
S_IDLE, // no cycle open
S_READ, // cycle open, read transfer outstanding
S_WRITE, // cycle open, write transfer outstanding
S_DONE // reporting completion for one cycle
} state_e;
state_e state_q;
logic [AW-1:0] src_q, dst_q;
logic [CW-1:0] remaining_q;
logic [DW-1:0] hold_q; // the word in flight
logic error_q;
// ── Terminations decoded once. RULE 3.45 makes them mutually exclusive
// at the slave, so exactly one can be true in a cycle. Decoding them
// in one place keeps every use consistent — and keeps the three
// OUTCOMES distinct, which is the bug Section 5 is about.
logic term_ok, term_err, term_rty;
assign term_ok = ack_i;
assign term_err = err_i;
assign term_rty = rty_i;
// ── Combinational outputs. Both qualifiers come from the state; every
// other request signal comes from a REGISTER — never from the local
// control inputs. That makes RULE 3.60's stability obligation
// structural rather than a promise the client must keep.
assign cyc_o = (state_q == S_READ) || (state_q == S_WRITE);
assign stb_o = cyc_o; // one transfer per cycle, see SCOPE
assign we_o = (state_q == S_WRITE);
assign adr_o = (state_q == S_WRITE) ? dst_q : src_q;
assign dat_o = hold_q;
assign sel_o = '1; // whole-word transfers only
assign busy_o = (state_q != S_IDLE);
assign error_o = error_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE; // negates cyc_o and stb_o: RULE 3.20
src_q <= '0;
dst_q <= '0;
remaining_q <= '0;
hold_q <= '0;
error_q <= 1'b0;
done_o <= 1'b0;
end else begin
done_o <= 1'b0;
unique case (state_q)
S_IDLE: begin
if (start_i && (count_i != '0)) begin
src_q <= src_i;
dst_q <= dst_i;
remaining_q <= count_i;
error_q <= 1'b0;
state_q <= S_READ;
end
end
S_READ: begin
if (term_ok) begin
// Read data is meaningful in the TERMINATION cycle. Capturing
// it here and nowhere else is Chapter 2.3's validity-window
// rule applied to Wishbone.
hold_q <= dat_i;
state_q <= S_WRITE;
end else if (term_err) begin
error_q <= 1'b1;
state_q <= S_DONE;
end
// term_rty: stay in S_READ. The cycle stays open and the same
// transfer is presented again. Whether an unbounded retry loop
// is acceptable is Module 11's question.
end
S_WRITE: begin
if (term_ok) begin
// Pointers advance only on a SUCCESSFUL write — a word is not
// copied until it has been written. See the §8 exercise.
src_q <= src_q + AW'(DW/8);
dst_q <= dst_q + AW'(DW/8);
remaining_q <= remaining_q - CW'(1);
// Written as if/else rather than a ternary: a conditional
// expression whose branches are enum literals does not adopt
// the enum type automatically, and some tools reject the
// assignment without an explicit cast.
if (remaining_q == CW'(1)) state_q <= S_DONE;
else state_q <= S_READ;
end else if (term_err) begin
error_q <= 1'b1;
state_q <= S_DONE;
end
end
S_DONE: begin
done_o <= 1'b1;
state_q <= S_IDLE;
end
endcase
end
end
endmoduleReading this module
Purpose. Move count_i words from src_i to dst_i, one read and one write per word, reporting completion and any failure to its local client.
Interface contract. start_i is honoured only while busy_o is low. On the Wishbone side, a cycle is open exactly while the state is S_READ or S_WRITE.
Ownership, visible in the code. Every Wishbone output is either a function of the state or a register output. Not one is driven from src_i, dst_i or count_i. The client may change its inputs the cycle after start_i; the bus does not move. That is Chapter 2.5's structural-stability argument, and it satisfies RULE 3.60 by construction rather than by discipline.
Combinational decisions. Six output assignments and a three-way termination decode. The interesting one is adr_o: a single multiplexer selects source or destination by state, so one address port serves both halves of the copy.
Sequential behaviour. Six registers. src_q/dst_q advance by the bus width in bytes on each successful write; remaining_q counts down; hold_q carries the word between read and write and is the only place a copied value exists; error_q is sticky until the next start_i.
Cycle by cycle, copying two words against an immediate slave:
| Cycle | State | cyc_o/stb_o | we_o | adr_o | Termination | Effect |
|---|---|---|---|---|---|---|
| 0 | IDLE | 0 | – | – | – | start_i; src/dst/count latched |
| 1 | READ | 1 | 0 | src | ACK_I | hold_q ← dat_i |
| 2 | WRITE | 1 | 1 | dst | ACK_I | pointers advance, remaining_q → 1 |
| 3 | READ | 1 | 0 | src+4 | ACK_I | hold_q ← dat_i |
| 4 | WRITE | 1 | 1 | dst+4 | ACK_I | remaining_q → 0, go to DONE |
| 5 | DONE | 0 | – | – | – | done_o pulses |
Assumptions and simplifications. One transfer per cycle; whole-word transfers only, so sel_o is all ones; no block cycles; RTY_I re-issues indefinitely; no timeout; word-aligned addresses assumed rather than checked; source and destination assumed not to overlap.
How it could fail.
- Drive
adr_ofromsrc_iinstead ofsrc_q. Works against an always-immediate slave; breaks the first time one waits. - Capture
dat_ioutside theACK_Icycle. The copied word is whatever followed on the bus. - Treat
RTY_Ias success. The transfer never happened and stale data is copied. - Treat
RTY_Ias an error. The copy aborts on an explicitly recoverable condition. - Advance pointers on the read rather than the write. Every word lands one slot too far.
- Omit
error_q's clear onstart_i. One failure makes every later copy report an error.
Scaling. One transfer per cycle leaves the bus idle between the read's termination and the write's presentation; a block cycle would hold CYC_O across both (Module 8). And one word in flight bounds throughput by round-trip latency rather than bandwidth — pipelining is B4's mode and a later subject.
4. Verification — Master-Side Invariants
// ─────────────────────────────────────────────────────────────────────────
// wb_master_checker — invariants a conforming Wishbone Classic MASTER must
// satisfy, at the level Module 3 has established.
//
// NOT a conformance suite. Module 26 owns Wishbone verification, and the
// detailed edge relationships these would need are Module 5's.
// ─────────────────────────────────────────────────────────────────────────
module wb_master_checker #(
parameter int unsigned AW = 32,
parameter int unsigned DW = 32
) (
input logic clk_i,
input logic rst_i,
input logic cyc_o,
input logic stb_o,
input logic we_o,
input logic [AW-1:0] adr_o,
input logic [DW/8-1:0] sel_o,
input logic ack_i,
input logic err_i,
input logic rty_i
);
// RST_I is active HIGH, so the disable condition is rst_i itself.
default disable iff (rst_i);
logic terminated;
assign terminated = ack_i | err_i | rty_i;
// P1 — RULE 3.25 requires CYC_O asserted whenever STB_O is. Stated as the
// cheap implication; the full "no later / no earlier" edge
// relationship needs Module 5's timing material.
property p_stb_implies_cyc;
@(posedge clk_i) stb_o |-> cyc_o;
endproperty
a_stb_implies_cyc : assert property (p_stb_implies_cyc)
else $error("STB_O asserted without CYC_O");
// P2 — RULE 3.60 expressed as stability: while a transfer is presented
// and not yet terminated, the qualified signals must not move. This
// separates a master driven from registers from one driven from its
// client's live inputs.
property p_request_stable;
@(posedge clk_i) (stb_o && !terminated)
|=> ($stable(adr_o) && $stable(we_o) && $stable(sel_o));
endproperty
a_request_stable : assert property (p_request_stable)
else $error("a qualified request signal moved before termination");
// P3 — a master does not withdraw a transfer before it is terminated.
property p_no_withdrawal;
@(posedge clk_i) (stb_o && !terminated) |=> stb_o;
endproperty
a_no_withdrawal : assert property (p_no_withdrawal)
else $error("STB_O withdrawn before termination");
// P4 — the master must never observe more than one termination at a time.
// RULE 3.45 places the obligation on the SLAVE; asserting it here
// catches an INTERCON that merged two selected slaves' responses —
// a fault for which neither endpoint is responsible.
property p_one_termination;
@(posedge clk_i) $onehot0({ack_i, err_i, rty_i});
endproperty
a_one_termination : assert property (p_one_termination)
else $error("master saw multiple terminations: ack=%b err=%b rty=%b",
ack_i, err_i, rty_i);
endmoduleWhy P4 is on the master rather than the slave. RULE 3.45 constrains a slave, and a slave-side assertion catches a misbehaving slave. P4 catches something no slave is responsible for: an INTERCON that OR-merges the terminations of two simultaneously selected slaves. Each slave is individually conforming; the master still sees two. That fault is invisible at every endpoint and visible exactly here.
Tooling limitation, stated plainly. Icarus Verilog has no SVA support — a two-line property/assert property probe fails to parse — so this checker and every other in Module 3 was reviewed by inspection only. No tool available in this environment has executed them. The synthesisable RTL has no such gap.
5. Failure Modes and Discriminating Evidence
Symptom: the copy engine works against one peripheral and corrupts data against another.
Candidate causes. The slower peripheral is the first that ever made the master wait, exposing a stability violation. Or read data captured outside the termination cycle.
Discriminating evidence. Trigger on stb_o && !ack_i && !err_i && !rty_i and watch adr_o and we_o. Any change during that window is a stability violation — property P2 — and the fault is in the master's output assignments, not the peripheral. If the request is stable, put ack_i and the hold_q load on one waveform and check the capture edge.
Likely RTL location. The assign adr_o = … line, or hold_q's condition.
Symptom: the copy completes and the destination contains stale data.
Candidate causes. RTY_I treated as success, or an ERR_I on the write that was ignored.
Discriminating evidence. Count terminations by type across the copy. Any RTY_I at all, combined with a state machine that advanced, is conclusive — the transfer did not happen. This is precisely why the three terminations are decoded separately rather than as terminated / not terminated.
Likely RTL location. The S_READ/S_WRITE termination branches.
Symptom: the engine hangs mid-copy.
Candidate causes. A slave that never terminates; an unmapped address with no default slave; or an unbounded RTY_I loop.
Discriminating evidence. Probe cyc_o, stb_o and all three termination inputs. All three low with stb_o high means nothing is answering — then check whether any slave's strobe is asserted, which separates decode from slave. rty_i pulsing repeatedly means the retry loop is live and the master is behaving correctly against a slave that is refusing: a system problem, not a master bug.
Likely RTL location. Decode first, then the slave, then the slave's retry condition.
Symptom: every copy after the first reports an error.
Candidate causes. error_q not cleared on start_i.
Discriminating evidence. Run a failing copy then a known-good one. An error reported with no err_i asserted during the second is conclusive.
Likely RTL location. The S_IDLE start branch.
6. Common Mistakes
"A master should know how long each slave takes, so it can be efficient."
Wrong mental model: latency knowledge is an optimisation.
Concrete bug: a per-slave latency table that is silently wrong the moment a register slice is inserted for timing closure, or a peripheral is revised.
Observable evidence: reads returning the previous transfer's data after a change with no functional content.
Correct model: the master waits for a termination. In Classic there is no alternative — the absence of a termination is the wait, and there is no stall signal to interpret.
"RTY_I is a kind of error."
Wrong mental model: anything that is not an acknowledge is a failure.
Concrete bug: a copy engine that aborts on a recoverable condition — or, oppositely, treats retry as success and moves a word that was never read.
Observable evidence: spurious failures under load, or silently stale destination data.
Correct model: three terminations, three meanings. Success, failure, and did not happen, may be re-issued. Module 11 owns retry; what a master owes it here is a distinct branch.
"The master can drive its outputs from whatever produced the request."
Wrong mental model: the request exists at the moment it is issued, so the source signals suffice.
Concrete bug: Wishbone outputs assigned from the client's live inputs. Against an immediate slave the transfer lasts one cycle and nothing moves; against a waiting slave the address changes mid-transfer.
Observable evidence: correct with fast peripherals, corrupt with slow ones — and the corruption tracks the peripheral's speed.
Correct model: latch the request and drive the bus from registers. RULE 3.60 makes the request signals meaningful under STB_O; a master that cannot guarantee their stability has not met that obligation.
"A master must know the address map to reach the right peripheral."
Wrong mental model: the master picks the slave.
Concrete bug: decode logic inside the master, which works in one SoC and needs editing for the next.
Observable evidence: a master that cannot be reused without modification.
Correct model: the master drives an address; the INTERCON turns it into a selection. Neither endpoint holds the map — Chapter 2.2's argument, and the reason INTERCON is a separate module.
7. Interview Reasoning
Responsible for the entire request, and accountable for it until the slave ends it. It decides that a transfer should happen, drives the address, direction, byte selects and write data, opens the cycle and presents the transfer — and holds all of it stable until a termination arrives. RULE 3.60 makes those signals meaningful only under the strobe, so stability is the master's obligation.
It must act differently on each of three terminations: acknowledge means success and read data may be captured in that cycle; error means failure; retry means the transfer did not happen and may be re-issued.
Four things it must not know: the address map — it drives an address, the INTERCON decides who answers; a slave's latency — it waits, and in Classic waiting is the only mechanism because there is no stall signal; a slave's internals beyond the register contract; and the topology, because the master interface is identical from point-to-point to crossbar.
The framing that shows judgement: the specification does not forbid a master from knowing those things. It defines an interface that makes them unnecessary, and every one a designer builds in is a coupling to one system that surfaces the day the system changes.
8. Understanding Check
9. What's Next
The master's side is settled: it owns the request, holds it until termination, branches three ways on the outcome, and is deliberately ignorant of the map, the latency, the internals and the topology.
Half the interface remains, and it is not symmetric. The master initiates; the slave never does.
What must a Wishbone slave observe, decide, and return — and how does it stay reusable while doing it?
Chapter 3.4 — The Slave Interface builds a real peripheral against those obligations. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Bus Transactions
A transaction is the unit of bus work: one beginning, one ending, and an interval in between during which the request must not move. Making waiting expressible is what lets a slow target share a bus with a fast one, and it is what turns an initiator from a wire into a state machine with real failure modes.
- 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
Masters and Slaves
Master and slave are transaction roles, not a statement about importance or hierarchy. The role determines exactly which information each side owns: the initiator supplies address, direction and write data; the target supplies read data, completion and any error. Getting that ownership wrong is the source of an entire family of integration bugs.
- 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.
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.
