Wishbone · Module 9
Why Wait States Exist
Targets answer at different speeds and Classic Wishbone gives a slave one way to say so. The same read at 0, 1, 3 and 7 wait states produces one transfer and one value every time.
Module 8 treated slave latency as a dial. wb_perf_slave was told to produce wait states, the cycle got longer, and nothing asked what the slave was doing with those clocks.
Module 9 takes the slave's side of the handshake.
Why can a Wishbone transfer not simply complete in the clock it was asked?
1. Where Latency Actually Comes From
A wait state is not a protocol feature that someone decided to add. It is the protocol admitting that targets are built from different hardware, and that hardware answers at different speeds.
A register file answers immediately. The address selects one of a handful of flip-flop banks through a multiplexer. There is no operation to perform — the value is already sitting there, and the only cost is the multiplexer's propagation delay. This is the running peripheral's STATUS and ID registers, and it is why Modules 6 and 7 could get away with zero-wait slaves throughout.
Synchronous memory answers a clock later, by construction. An SRAM macro registers its address at a clock edge and presents data at the next one. The latency is not a design choice — it is the shape of the compiled memory, and a slave wrapping one cannot answer in the clock the address arrived.
A state machine answers when it reaches a state. A peripheral that must arm a shift register, wait for a strobe, or sequence a handful of internal steps has a response time measured in its own states.
A clock-domain crossing answers after synchronisation. Two flip-flops of synchroniser on each side is the usual minimum, and the round trip is several clocks before the answer is even formed.
A bridge answers when the far side does. A Wishbone-to-anything bridge cannot terminate the near transfer until the downstream one has completed. Its latency is not its own; it is inherited, and it is usually variable.
None of these is a slave being slow. Each is a target whose internal work takes a known or knowable number of clocks, and the protocol's job is to let it say so without the bus inventing a value in the meantime.
The alternative, and why it is worse
Suppose every slave had to terminate in the clock it was asked. Two things follow, and only the first is obvious.
Some targets become unimplementable. A synchronous RAM cannot produce data in the clock its address arrived. Wrapping one would require registering the address a clock earlier than the bus presents it, which the bus cannot arrange.
The rest become a timing problem. A slave that must answer combinationally puts address decode, the read multiplexer and the termination logic into one path from the master's STB_O to its own ACK_O, and then back into the master's next-state logic. The specification names this cost directly in the observations attached to ACK_O — combinational termination "could lead to unacceptable delay times, caused by the loopback delay from the MASTER to the SLAVE and back to the MASTER."
The trade is genuinely two-sided, and this module does not re-litigate it. Chapter 6.4 built two slaves with the same function and measured both; Chapter 7.3 did the same for writes. PERMISSION 3.30 explicitly allows a combinational path from STB_I to ACK_O, and the spec observes that doing so "assures that the interface can accomplish one data transfer per clock cycle." Neither choice is universally correct.
The one observation that belongs to this module is the specification's own remark about which is easier when latency is involved:
Combinational termination "could proof impossible to implement. For example slave wait states are easiest implemented using a registered [
ACK_O]."
That is an engineering observation about convenience, not a rule. A slave may insert wait states with either style. It happens to be much easier with a registered response, which is why every delayed slave in this module uses one.
2. Defining WAIT_CYCLES Before Using It
Every configurable-latency slave in this module takes a WAIT_CYCLES parameter, and a parameter whose meaning is not pinned down is where off-by-one bugs are born. Chapter 9.5 measures two of them.
So the definition, once, for the whole module:
WAIT_CYCLES = Nmeans the slave withholds termination atNclock edges where the transfer was presented, and terminates at the(N+1)th.
Three consequences follow directly, and Section 5 measures all three rather than assuming them:
| Wait states inserted | WAIT_CYCLES |
| Clocks the transfer occupies | WAIT_CYCLES + 1 |
WAIT_CYCLES = 0 | termination at the first presented edge — a zero-wait transfer, 1 clock, no wait states at all |
Note what WAIT_CYCLES = 0 requires. If the slave may terminate at the very edge the request first appears, there is a combinational path from STB_I to ACK_O. That is PERMISSION 3.30 exactly, and it is why zero-wait is a legal configuration of the same module rather than a special case.
WAIT_CYCLES = 0 is not "no latency configured". It is a measured point on the same scale as 1, 3 and 7, and Section 5 runs it as one.
3. RTL — A Slave That Can Be Told How Long to Take
// wb_fixed_latency_slave — the reference delayed slave for Module 9.
//
// WAIT_CYCLES PARAMETER SEMANTICS. Stated here once and used identically in
// every chapter of this module:
//
// WAIT_CYCLES = N -> the slave withholds termination for N clock edges
// at which the transfer was presented, and terminates
// at the (N+1)th.
//
// WAIT_CYCLES = 0 -> ACK_O is eligible at the SAME edge the request is
// first presented. The transfer occupies 1 clock and
// inserts 0 wait states. This is the zero-wait case,
// and it needs a combinational path from STB_I to
// ACK_O (PERMISSION 3.30 explicitly allows one).
// WAIT_CYCLES = 1 -> 1 wait clock, then termination. 2 clocks total.
// WAIT_CYCLES = N -> N wait clocks, then termination. N+1 clocks total.
//
// So "clocks the transfer occupies" is always WAIT_CYCLES + 1, and "wait
// states inserted" is always WAIT_CYCLES. Chapter 9.1 measures this table
// rather than asserting it.
//
// The specification's own words for what this slave is doing (SINGLE READ,
// section 3.2.1): the SLAVE "may insert wait states before asserting [ACK_I],
// thereby allowing it to throttle the cycle speed." Withholding termination
// is the ONLY mechanism Classic Wishbone gives a slave for this; there is no
// stall signal.
// ─────────────────────────────────────────────────────────────────────────
module wb_fixed_latency_slave #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32,
parameter int unsigned WAIT_CYCLES = 0
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic we_i,
input logic [OFF_AW-1:0] adr_i,
input logic [DW-1:0] dat_i,
input logic [DW/8-1:0] sel_i,
output logic [DW-1:0] dat_o,
output logic ack_o,
output logic err_o,
// observation only — not part of the Wishbone interface
output logic [7:0] waited_o,
output logic ready_o
);
// Running peripheral map, words 0..15. Module 9 adds no new register here;
// it varies only WHEN this slave answers.
localparam logic [OFF_AW-1:0] O_STATUS = 4'd0; // byte 0x00 RO
localparam logic [OFF_AW-1:0] O_COUNT = 4'd1; // byte 0x04 RO
localparam logic [OFF_AW-1:0] O_CTRL = 4'd2; // byte 0x08 RW
localparam logic [OFF_AW-1:0] O_ID = 4'd4; // byte 0x10 RO
localparam logic [DW-1:0] ID_VALUE = 32'h5742_0901;
logic [DW-1:0] ctrl_q;
logic [DW-1:0] count_q;
logic [7:0] waited_q;
logic xfer, mapped, ready;
// RULE 3.30 / RULE 3.35: the transfer is qualified by CYC_I AND STB_I.
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_STATUS) || (adr_i == O_COUNT)
|| (adr_i == O_CTRL) || (adr_i == O_ID);
// ── THE LATENCY COUNTER ─────────────────────────────────────────────────
// waited_q counts the presented edges: 0, 1, ... , WAIT_CYCLES.
// "ready" becomes true at the WAIT_CYCLES'th count, which is the
// (WAIT_CYCLES+1)'th presented clock. The comparison is >= so that a
// held-on transfer cannot un-ready itself; Chapter 9.5 measures what the
// two plausible wrong comparisons do instead.
assign ready = (waited_q >= 8'(WAIT_CYCLES));
assign ready_o = ready;
assign waited_o = waited_q;
assign ack_o = xfer && mapped && ready;
assign err_o = xfer && !mapped && ready;
always_comb begin
dat_o = '0;
if (xfer && !we_i) begin
case (adr_i)
O_STATUS: dat_o = 32'h0000_0001;
O_COUNT: dat_o = count_q;
O_CTRL: dat_o = ctrl_q;
O_ID: dat_o = ID_VALUE;
default: dat_o = '0;
endcase
end
end
always_ff @(posedge clk_i) begin
if (rst_i) begin
ctrl_q <= '0; count_q <= '0; waited_q <= '0;
end else begin
count_q <= count_q + 1; // free-running, as in 6.1
// The counter resets whenever no transfer is presented, so every new
// transfer starts its latency from zero.
if (!xfer) waited_q <= '0;
else if (!ready) waited_q <= waited_q + 8'd1;
else waited_q <= '0;
// COMMIT, exactly once, at the terminating edge. Not on every clock
// the write is presented — the Module 7 lesson, which Chapter 9.2
// measures again under longer latency.
if (xfer && ready && mapped && we_i && (adr_i == O_CTRL))
ctrl_q <= dat_i;
end
end
endmodule// wb_wait_monitor — per-TRANSFER instrumentation for Module 9.
//
// SIMULATION ONLY. Not synthesisable, not part of any interface.
//
// Chapter 8.1's wb_cycle_monitor counts whole bus cycles. This one works at
// the granularity Module 9 needs: it watches a single outstanding transfer
// and reports how long it was presented, how many of those clocks were wait
// clocks, and — the part that matters — whether the master kept the request
// metadata stable for the whole of it.
//
// "Outstanding" here means: presented (CYC_I and STB_I both asserted) and
// not yet terminated. That is the interval RULE 3.60's stability obligation
// applies to.
// ─────────────────────────────────────────────────────────────────────────
module wb_wait_monitor #(
parameter int unsigned AW = 30,
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 [AW-1:0] adr_i,
input logic [DW-1:0] dat_i, // the MASTER's DAT_O (write payload)
input logic [DW/8-1:0] sel_i,
input logic ack_i,
input logic err_i,
input logic rty_i,
output logic done_o, // pulses the clock after a transfer ends
output int unsigned presented_o, // clocks this transfer was presented
output int unsigned waits_o, // of those, clocks with no termination
output int unsigned transfers_o, // terminations seen, cumulative
output int unsigned unstable_o // metadata changes while outstanding
);
logic presented, terminated, was_out;
assign presented = cyc_i && stb_i;
assign terminated = presented && (ack_i || err_i || rty_i);
int unsigned n_pres;
logic [AW-1:0] adr_q;
logic [DW-1:0] dat_q;
logic [DW/8-1:0] sel_q;
logic we_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
n_pres <= 0; was_out <= 1'b0; done_o <= 1'b0;
presented_o <= 0; waits_o <= 0; transfers_o <= 0; unstable_o <= 0;
adr_q <= '0; dat_q <= '0; sel_q <= '0; we_q <= 1'b0;
end else begin
done_o <= 1'b0;
// ── STABILITY, checked only while a transfer is actually outstanding ──
// was_out is true when the PREVIOUS edge had a presented, unterminated
// transfer. Comparing this edge's metadata against what was latched
// then is exactly RULE 3.60's obligation.
if (was_out && presented) begin
if (adr_i !== adr_q) unstable_o <= unstable_o + 1;
else if (we_i !== we_q) unstable_o <= unstable_o + 1;
else if (sel_i !== sel_q) unstable_o <= unstable_o + 1;
// The write payload is only meaningful for a write.
else if (we_i && (dat_i !== dat_q)) unstable_o <= unstable_o + 1;
end
if (presented) begin
n_pres <= n_pres + 1;
adr_q <= adr_i; dat_q <= dat_i; sel_q <= sel_i; we_q <= we_i;
was_out <= !terminated;
if (terminated) begin
transfers_o <= transfers_o + 1;
presented_o <= n_pres + 1; // including this terminating clock
waits_o <= n_pres; // every earlier presented clock
n_pres <= 0;
done_o <= 1'b1;
end
end else begin
// Not presented: no transfer is outstanding and nothing accumulates.
n_pres <= 0;
was_out <= 1'b0;
end
end
end
endmoduleReading the pair
Purpose. The slave makes latency a parameter so the same logical read can be run at several speeds. The monitor measures a transfer rather than a cycle, which is the granularity the rest of this module needs.
The counter is the whole slave. waited_q counts presented edges from zero; ready is waited_q >= WAIT_CYCLES. Everything with a consequence — ack_o, err_o, the commit — hangs off ready, so there is exactly one place where "how long do I take" is decided.
Why the comparison is >= and not ==. With ==, a transfer that somehow stayed presented past its terminating edge would go un-ready again. >= is the defensive form, and Chapter 9.5 measures what the two plausible wrong comparisons actually do.
Why the counter resets on !xfer. Every new transfer must start its latency from zero. A counter that kept its value between transfers would give the next request a shorter wait — an off-by-one with a moving target.
The monitor's unstable_o is the interesting output. It compares this edge's metadata against the previous edge's, but only while a transfer was outstanding at that previous edge. That window is precisely where RULE 3.60 applies, and counting outside it would flag every legitimate move to the next request.
Timing. ack_o is combinational in xfer and ready, and ready is registered through waited_q. At WAIT_CYCLES = 0 this collapses to a combinational path from STB_I to ACK_O — the PERMISSION 3.30 shape — and at any other value the response is effectively registered, which is the arrangement OBSERVATION 3.45 calls easiest.
Reset. Active high, synchronous, consistent with the module.
Simplifications. Four registers, no byte lanes (SEL_O is ignored; Module 13 owns byte selects), no RTY_O. The count_q register free-runs as it has since Chapter 6.1, which matters in Section 5.
4. Waveform — One Read, Stretched
One transfer, three wait states
10 cyclesCount the clocks with STB_O asserted: four. Cycles 2, 3, 4 and 5. Count the terminations: one, at cycle 5.
That is the single most important reading in this module. Four clocks of STB_O are not four transfers, four requests, or four anything. They are one transfer being presented for four clocks — and Chapter 8.5 already made the general form of this point, that transfers are counted at terminations and never at STB_O clocks.
Cycles 2, 3 and 4 are the wait states. Three of them, matching WAIT_CYCLES = 3. The waited trace shows the slave's counter climbing 0, 1, 2, 3 — and ready becomes true exactly when it reaches 3, which is the fourth presented clock.
ADR_O holds 0x4 for all four clocks, which is RULE 3.60 doing its work. The address is qualified by STB_O, and STB_O is asserted for all four — so the address is obliged to stand still for all four. Chapter 9.3 is about what happens when a master fails this.
The captured value appears at cycle 6, one clock after the termination. The master samples DAT_I at edge 5 and registers it, so it is visible after edge 5. That is the same PRESENTED-versus-COMMITTED distinction Chapter 7.1 drew for writes, seen from the read side.
One honest note about this slave's read data. wb_fixed_latency_slave computes dat_o combinationally from the presented address, so the ID value is actually on the bus from cycle 2 — before the slave acknowledges. A master may not rely on that. RULE 3.65 requires the slave to qualify DAT_O() with its termination signal, which means the only edge at which read data is guaranteed valid is the terminating one. This slave is early because it has nothing to compute; Chapter 9.2 builds one that genuinely does not have the answer yet, and Chapter 9.5 measures a master that captures too soon.
5. Simulation — The Same Read, Four Latencies
SIM A — one logical read, four slave latencies. Identical master, identical request, identical address; only WAIT_CYCLES differs.
=== SIM A - one logical read, four slave latencies ===
reading word 4 (ID) with wb_wait_safe_master
WAIT_CYCLES transfers wait clks presented clks value read metadata changes
0 1 0 1 0x57420901 0
1 1 1 2 0x57420901 0
3 1 3 4 0x57420901 0
7 1 7 8 0x57420901 0Read the columns that did not change. transfers = 1 on every row. value read = 0x57420901 on every row. metadata changes = 0 on every row.
Read the columns that did. Wait clocks and presented clocks, and nothing else.
That is the central claim of this module, measured on its first page:
Latency changes how long a transfer takes. It does not change what the transfer is.
One accepted client request produced one transfer and one captured value at every latency — from an eight-clock transfer down to a one-clock one. The master's client could not tell the four runs apart from the result alone; only from how long it took.
SIM B — the WAIT_CYCLES table, measured rather than asserted.
=== SIM B - WAIT_CYCLES semantics, measured from the RTL ===
WAIT_CYCLES = N means: N wait clocks, terminating at clock N+1.
WAIT_CYCLES wait clocks measured clocks transfer occupied N+1
0 0 1 1
1 1 2 2
3 3 4 4
7 7 8 8Both identities hold at every point, including the two that are easiest to get wrong.
WAIT_CYCLES = 0 gives 0 wait clocks and a 1-clock transfer, not a 1-clock wait. A zero-wait transfer still occupies a clock — the clock in which it is presented and terminated together.
WAIT_CYCLES = 7 gives 7 wait clocks and an 8-clock transfer. The off-by-one that would put 7 clocks total, or 8 wait states, is the bug Chapter 9.5 injects deliberately.
Why this table is worth publishing at all. A parameter called WAIT_CYCLES has at least three plausible meanings — clocks before the answer, total clocks, or clocks after the first. Until the table is measured, RTL and prose can disagree silently for a whole module. Everything in Chapters 9.2 through 9.5 is written against this table.
A detail worth noticing in the ID value. Every run returned 0x57420901, not Module 8's 0x57420801. Module 9's slave is a new instance of the running peripheral, and the ID is how a trace says which one it is talking to — the convention since Chapter 6.1.
And one value that is deliberately absent. The free-running COUNT register was not read here. It advances every clock, so reading it at four different latencies would return four different values and confuse "the transfer is the same" with "the data is the same". They are different claims, and Chapter 9.4 separates them carefully.
6. Failure Modes and Discriminating Evidence
Symptom: a slave that works standalone hangs the bus when integrated.
Candidate causes. Its latency counter never reaches the ready condition — most often because the counter is gated on something that is not true for the whole transfer.
Discriminating evidence. Watch the counter, not the bus. If waited_q is stuck at a value below WAIT_CYCLES, the slave is not counting; if it is cycling back to zero repeatedly, the transfer is being de-presented and re-presented, which is a master problem wearing a slave disguise.
Likely RTL location. The !xfer reset arm of the counter.
Symptom: the first access after reset takes a different number of clocks than the rest.
Candidate causes. The counter was not reset, or was reset to a value that is already ready.
Discriminating evidence. Compare the first transfer's waits against the second's. They must be equal for a fixed-latency slave. wb_wait_monitor reports per-transfer, precisely so this is visible.
Symptom: latency is correct for reads and wrong for writes, or the reverse.
Candidate causes. The ready condition was written into one of the two paths rather than shared.
Discriminating evidence. Run the same WAIT_CYCLES in both directions and compare presented clocks. They should match — latency in this slave is a property of the counter, not of WE_I. Chapter 9.4 runs exactly that matrix.
Symptom: a transfer terminates a clock earlier or later than the parameter says.
Candidate causes. The comparison is > or == rather than >=, or the counter increments in the wrong arm.
Discriminating evidence. The measured table from Section 5. One row is enough: if WAIT_CYCLES = 3 does not produce exactly 3 wait clocks and 4 presented clocks, the parameter does not mean what the module says it means.
This is measured, not hypothesised — Chapter 9.5 builds the broken comparison and reports what it does.
7. Verification
// Properties for wb_fixed_latency_slave. Each is labelled SPECIFICATION or
// LOCAL POLICY, because a parameter's semantics are emphatically the latter.
//
// NOTE ON EXECUTION: these are SystemVerilog assertions. Icarus Verilog does
// not support SVA, so they were reviewed by inspection and are NOT claimed to
// have been executed. The measured results in Section 5 come from procedural
// checks in the testbench, which Icarus does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_fixed_latency_slave_props #(
parameter int unsigned WAIT_CYCLES = 0
) (
input logic clk_i, rst_i,
input logic cyc_i, stb_i, ack_i, err_i,
input logic [7:0] waited_i
);
default clocking cb @(posedge clk_i); endclocking
default disable iff (rst_i);
logic xfer;
assign xfer = cyc_i && stb_i;
// P1 — SPECIFICATION (RULE 3.35). A termination is generated only in
// response to the logical AND of CYC_I and STB_I. A slave that
// acknowledges an unqualified request is the bug Chapter 5.3 named.
P1_ack_qualified: assert property ( (ack_i || err_i) |-> xfer );
// P2 — SPECIFICATION (RULE 3.45). At most one termination signal at a time.
P2_one_termination: assert property ( !(ack_i && err_i) );
// P3 — LOCAL POLICY, and the reason this module defines WAIT_CYCLES.
// A termination is not produced before the counter has reached the
// configured latency. This encodes the parameter's meaning, and it
// is a statement about THIS slave, not about Wishbone.
P3_latency_honoured: assert property (
(ack_i || err_i) |-> (waited_i >= 8'(WAIT_CYCLES))
);
// P4 — LOCAL POLICY. Every new transfer starts its latency from zero, so
// the counter is zero on the first presented clock of a transfer.
// This is what makes the per-transfer latency repeatable.
P4_counter_restarts: assert property (
(!xfer ##1 xfer) |-> (waited_i == 8'd0)
);
endmoduleWhat P3 can and cannot establish. It proves this slave never terminates early relative to its own parameter. It says nothing about whether WAIT_CYCLES was set correctly for the hardware being modelled — that is a system-integration question, and no property written from these pins can reach it.
Why P1 and P2 are carried rather than invented. They are the same conformance properties every slave in this course has had since Chapter 5.8, and latency does not change them. A slave that inserts wait states has exactly the same termination obligations as one that does not — it simply discharges them later.
8. Common Mistakes
"Each clock with STB_O asserted is another request."
Wrong mental model: the bus re-issues the request every clock it is held.
What is true: it is one transfer whose presentation is being maintained. Nothing is re-issued and nothing is repeated.
Concrete bug: a slave that performs its operation on every clock where CYC_I && STB_I && WE_I. Held for four clocks, one logical write becomes four side effects. Measured in Chapter 9.2.
Observable evidence: Figure 1 — four STB_O clocks, one ACK_I, one transfer.
Correct model: count terminations. Chapter 8.5 made this the basis of an entire taxonomy.
"A wait state means the master should retry."
Wrong mental model: no response means the request failed.
What is true: a delayed ACK is the normal completion of a transfer that is still in progress. Nothing has failed and nothing needs re-sending.
Concrete bug: a master that de-asserts STB_O and re-presents after a few clocks, restarting the slave's counter each time — a transfer that can never complete against any slave slower than the retry interval.
Observable evidence: the slave's waited_q repeatedly climbing and resetting without reaching WAIT_CYCLES.
Correct model: hold the request and wait. Retry in Wishbone means RTY_I, which is a termination, and Module 11 owns it.
"WAIT_CYCLES = 3 obviously means the ACK arrives three clocks later."
Wrong mental model: the parameter's meaning is self-evident.
What is true: it means three wait clocks, so the transfer occupies four clocks and terminates on the fourth. "Three clocks later" than the presenting edge would be the fourth clock only if you count the presenting edge as clock zero — which is exactly the ambiguity that produces off-by-one bugs.
Concrete bug: a testbench that expects ACK at a different edge than the RTL produces it, "fixed" by adjusting whichever of the two was easier to reach.
Observable evidence: the SIM B table, which is why it is published.
Correct model: state the parameter's semantics, then measure them.
"Combinational ACK is always faster, so a good slave avoids wait states."
Wrong mental model: latency is a defect.
What is true: some targets cannot answer in the presenting clock — a synchronous RAM is the standard example — and for the ones that can, the combinational path costs Fmax. The specification records both sides: combinational termination "assures that the interface can accomplish one data transfer per clock cycle", and also "could lead to unacceptable delay times" in large, fast designs.
Concrete bug: forcing a zero-wait response onto a slave that needs a clock, producing a design that fails timing or returns data that is not ready.
Observable evidence: Chapter 6.4 measured both styles for the same function.
Correct model: latency is a property of the target, and the protocol exists to express it.
9. Interview Reasoning
Because targets are built from different hardware and answer at different speeds, and the bus needs a way to say so without inventing a value.
The concrete cases. A register file answers through a multiplexer with no operation to perform. A synchronous RAM registers its address and presents data a clock later — that latency is structural, not a design choice. A peripheral state machine answers when it reaches a state. A clock-domain crossing answers after synchronisation. A bridge answers when the far side does, and its latency is usually variable.
What Classic B3 gives the slave to express this: one thing. It withholds the termination. The SINGLE READ section says the slave "may insert wait states before asserting [ACK_I], thereby allowing it to throttle the cycle speed", and SINGLE WRITE adds that any number may be added. There is no stall signal, no ready line, no credit — the absence of ACK is the wait state.
The alternative is worse in two ways. Requiring same-clock termination makes some targets unimplementable, and makes the rest a timing problem: address decode, read mux and termination all land in one combinational path from the master's STB_O back to the master's next-state logic. The specification names that cost — loopback delay from master to slave and back.
What I would be careful not to claim. That registered ACK is universally better. The specification explicitly permits a combinational path and observes it "assures one data transfer per clock cycle". It is a trade, not a ranking — though the same observations note that wait states are easiest implemented with a registered response, which is why every delayed slave in this module has one.
And the result I would actually lead with, because it is the one people get wrong: latency does not change the transaction. I measured the same read at 0, 1, 3 and 7 wait states — one transfer and the same value every time. Only the duration moved.
10. Understanding Check
11. What's Next
Latency now has a cause, a parameter with a defined meaning, and a measured table showing it changes duration and nothing else.
What this chapter did not do is build a slave that actually works during those clocks. wb_fixed_latency_slave counts and then answers; it has the value the whole time. A real target is doing something — registering an address, running a state machine, waiting for a far side — and how it holds the request while it does that is a design decision with two defensible answers.
How should a slave implement internal latency without losing the request it is serving?
Chapter 9.2 — Slave Delays builds fixed and variable latency slaves, compares the two capture styles, and measures what a delayed write does to a register with a side effect. 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
ACK_I
The only mandatory termination. What a slave promises by asserting it, how wait states work without a wait signal, and why RULE 3.55 requires a master to keep working when a slave holds it asserted.
- Related topic
The Wishbone Handshake
The smallest correct conversation between a Wishbone master and slave: a master presents a transfer and holds it, a slave terminates it, and the transaction exists across an interval rather than at an instant.
- Related topic
Wait States
A slave throttles a read by withholding its acknowledge. The transfer does not disappear — and a read with a side effect must fire once for the whole wait, not once per cycle.
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.
