Wishbone · Module 9
ACK Delay
A delayed acknowledge changes a transfer's duration and nothing else. A master driving the bus from its live client asked for one register and returned another, with no error.
Chapter 9.2 built slaves that take their time. Every experiment used wb_wait_safe_master, which latches its client's request and then has nothing left that can move.
That made the slave chapters clean, and it quietly did the hardest part of the master's job.
What is a master actually obliged to do while it waits — and what breaks if its client changes its mind?
1. What "Outstanding" Obliges
RULE 3.60 is the whole of it, and it has been quoted in this course since Chapter 4.3:
RULE 3.60 — the MASTER MUST qualify the following signals with
STB_O:ADR_O,DAT_O(),SEL_O(),WE_OandTAGN_O.
The force of it only becomes visible when transfers get long. STB_O is asserted for the entire outstanding transfer — one clock at zero wait states, eight clocks at seven. Whatever STB_O qualifies must be stable for exactly as long as STB_O is asserted, so the stability obligation stretches with the latency.
For a write the specification is more explicit still. The SINGLE WRITE timing says the master's ADR_O, DAT_O(), WE_O and SEL_O() are valid from the presenting edge and remain valid "until the rising CLK_I edge following negation of STB_O".
Read that carefully, because it is stronger than a naive reading of RULE 3.60. The payload must still be coherent at the edge that ends the transfer, not merely up to it. Chapter 7.4 rests on that sentence; Module 9 simply makes the interval arbitrarily long.
What the rule does not say is equally worth stating. It says nothing about where the master keeps those values. Registers, a FIFO output, a constant, or a live wire from somewhere else are all permitted — so long as the result is stable. Section 5's broken master satisfies the letter of "drive these signals" and fails the substance of "hold them stable", which is precisely why it compiles, elaborates and passes casual review.
Termination is still a level
One carry-over deserves an explicit mention, because wait states are where it is most tempting to get wrong.
ACK_I is a level sampled at each clock edge, not an event to detect. Chapter 5.4 established this and measured the consequence; the relevant point here is that a long stretch of ACK_I low followed by one clock high looks exactly like an edge, and a master written as if ($rose(ack_i)) will work perfectly in every simulation in this chapter.
It breaks against a slave that holds ACK_O asserted — which PERMISSION 3.35 explicitly allows, and which RULE 3.55 requires masters to cope with:
RULE 3.55 — MASTER interfaces MUST be designed to operate normally when the SLAVE interface holds
ACK_Iasserted.
So edge-detection is a bug that wait states hide rather than expose. wb_wait_safe_master samples the level: terminated = cyc_o && stb_o && (ack_i || err_i || rty_i).
2. RTL — Two Masters, One Line Apart
The correct master was published in Chapter 9.1 §3 and is reproduced here in full, because the comparison is the chapter.
// wb_wait_safe_master — a master that is correct under ANY response latency.
//
// The design rule it embodies is one line: the Wishbone request is driven
// from REGISTERS that were loaded when the client request was accepted, and
// nothing downstream of those registers can change until the transfer
// terminates.
//
// That is what makes it safe. It is not that the master "waits properly" —
// waiting requires no effort at all. It is that the master has nothing left
// that CAN move while it waits.
//
// RULE 3.60 requires ADR_O, DAT_O(), SEL_O, WE_O and the tags to be
// qualified by STB_O. Section 3.2.2 is stronger for a write: the payload is
// valid from the presenting edge and remains valid "until the rising CLK_I
// edge following negation of STB_O". Driving any of them from a live client
// input satisfies neither, and Chapter 9.3 measures what that costs.
// ─────────────────────────────────────────────────────────────────────────
module wb_wait_safe_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32
) (
input logic clk_i,
input logic rst_i,
// ── client side ──
input logic req_i,
input logic req_we_i,
input logic [AW-1:0] req_adr_i,
input logic [DW-1:0] req_dat_i,
input logic [DW/8-1:0] req_sel_i,
output logic busy_o,
output logic done_o, // one pulse per accepted request
output logic err_o,
output logic [DW-1:0] rdat_o,
// ── Wishbone side ──
output logic cyc_o,
output logic stb_o,
output logic we_o,
output logic [AW-1:0] adr_o,
output logic [DW-1:0] dat_o,
output logic [DW/8-1:0] sel_o,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic { S_IDLE, S_XFER } state_e;
state_e state_q;
// ── THE LATCHES. Loaded once, at acceptance; read-only thereafter. ──────
logic [AW-1:0] adr_q;
logic [DW-1:0] dat_q;
logic [DW/8-1:0] sel_q;
logic we_q;
logic terminated;
// One transfer per cycle, so CYC_O and STB_O coincide here. They are still
// written as two expressions: Chapter 8.3 showed why they are not the same
// signal in general, and PERMISSION 3.40's shortcut is a licence, not a
// requirement.
assign cyc_o = (state_q == S_XFER);
assign stb_o = (state_q == S_XFER);
assign adr_o = adr_q;
assign dat_o = dat_q;
assign sel_o = sel_q;
assign we_o = we_q;
assign busy_o = (state_q != S_IDLE);
// Termination is a LEVEL sampled at the clock edge, not an edge to detect.
// Chapter 5.4 settled this; a wait state changes nothing about it.
assign terminated = cyc_o && stb_o && (ack_i || err_i || rty_i);
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE;
adr_q <= '0; dat_q <= '0; sel_q <= '0; we_q <= 1'b0;
done_o <= 1'b0; err_o <= 1'b0; rdat_o <= '0;
end else begin
done_o <= 1'b0;
case (state_q)
S_IDLE: if (req_i) begin
// ── ACCEPTANCE. The client's request is copied here and the copy
// is what reaches the bus. After this edge the client may do
// whatever it likes; Chapter 9.3 has it do exactly that.
adr_q <= req_adr_i;
dat_q <= req_dat_i;
sel_q <= req_sel_i;
we_q <= req_we_i;
state_q <= S_XFER;
end
S_XFER: begin
// Nothing is assigned to adr_q / dat_q / sel_q / we_q here. That
// absence IS the stability guarantee, and it holds for a 1-clock
// transfer and a 50-clock transfer identically.
if (terminated) begin
// Capture at the TERMINATING edge, and only then. RULE 3.65 says
// the slave qualifies DAT_O() with its termination signal, so
// this is the only edge at which read data is guaranteed valid.
if (!we_q) rdat_o <= dat_i;
err_o <= err_i;
state_q <= S_IDLE;
done_o <= 1'b1; // exactly one pulse per accepted request
end
end
default: state_q <= S_IDLE;
endcase
end
end
endmodule// wb_passthrough_master — THE BUG, isolated. NOT A REFERENCE DESIGN.
//
// Identical to wb_wait_safe_master in every respect except one: the request
// metadata is driven COMBINATIONALLY from the live client inputs instead of
// from registers latched at acceptance.
//
// assign adr_o = adr_q; becomes assign adr_o = req_adr_i;
//
// The sequencing, the capture edge, the done pulse and the termination
// detection are all unchanged and all correct. The only defect is that the
// master has kept a path from its client straight onto the bus.
//
// Against a zero-wait slave the two masters are INDISTINGUISHABLE: the
// transfer is presented and terminated in the same clock, so there is no
// interval during which the client could change anything. The bug needs
// wait states to exist at all.
//
// What it violates: RULE 3.60 requires the MASTER to qualify ADR_O, DAT_O(),
// SEL_O(), WE_O and the tags with STB_O. STB_O is asserted for the whole
// outstanding transfer, so those signals are obliged to be stable for the
// whole outstanding transfer. This master cannot make that promise, because
// it does not own the values it is driving.
// ─────────────────────────────────────────────────────────────────────────
module wb_passthrough_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32
) (
input logic clk_i,
input logic rst_i,
input logic req_i,
input logic req_we_i,
input logic [AW-1:0] req_adr_i,
input logic [DW-1:0] req_dat_i,
input logic [DW/8-1:0] req_sel_i,
output logic busy_o,
output logic done_o,
output logic err_o,
output logic [DW-1:0] rdat_o,
output logic cyc_o,
output logic stb_o,
output logic we_o,
output logic [AW-1:0] adr_o,
output logic [DW-1:0] dat_o,
output logic [DW/8-1:0] sel_o,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic { S_IDLE, S_XFER } state_e;
state_e state_q;
logic terminated;
assign cyc_o = (state_q == S_XFER);
assign stb_o = (state_q == S_XFER);
// ── THE BUG. These four lines are a wire from the client to the bus. ────
assign adr_o = req_adr_i;
assign dat_o = req_dat_i;
assign sel_o = req_sel_i;
assign we_o = req_we_i;
assign busy_o = (state_q != S_IDLE);
assign terminated = cyc_o && stb_o && (ack_i || err_i || rty_i);
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE; done_o <= 1'b0; err_o <= 1'b0; rdat_o <= '0;
end else begin
done_o <= 1'b0;
case (state_q)
// Nothing is latched here. There is nothing TO latch — the bus is
// already being driven from the client's outputs.
S_IDLE: if (req_i) state_q <= S_XFER;
S_XFER: if (terminated) begin
if (!req_we_i) rdat_o <= dat_i;
err_o <= err_i;
state_q <= S_IDLE;
done_o <= 1'b1;
end
default: state_q <= S_IDLE;
endcase
end
end
endmoduleReading the pair
The difference is four assign statements. One master drives the bus from registers loaded at acceptance; the other drives it from the client's live outputs. Everything else — the state machine, the termination detection, the capture edge, the done_o pulse — is identical.
The correct master's guarantee is an absence. Look at its S_XFER state: nothing is assigned to adr_q, dat_q, sel_q or we_q. That absence is the stability guarantee, and it holds identically for a one-clock transfer and a fifty-clock one. There is no "hold" logic to get wrong because there is no path by which the values could change.
This is the design idea worth taking away. The correct master is not correct because it waits carefully. It is correct because it has nothing left that can move. Safety by construction beats safety by discipline, especially in logic that only misbehaves under latency nobody tested.
The broken master's defect is not laziness. Omitting four registers is a real saving in a wide-address, wide-data design, and the result is a master that works flawlessly against every zero-wait slave. It is a design that has outsourced a protocol obligation to its client without saying so.
Why the bug is invisible at zero wait states. The transfer is presented and terminated in the same clock, so there is no interval during which the client could change anything. The two masters are bit-identical until a slave inserts a wait state — the same structural precondition that hid the defects in Chapter 6.2, Chapter 7.4, Chapter 8.3 and Chapter 9.2.
One honest note about rdat_o in the broken master. It captures at the terminating edge, correctly, and it tests !req_we_i — the live client signal — to decide whether to capture. Even its capture logic is contingent on the client having not moved.
3. Waveform — Same Request, Two Addresses
The client moves; one master follows it
10 cyclesFollow the client adr row first. It reads 0x4 while the client wants the ID register, and changes to 0x0 at cycle 5. The client has done nothing wrong — it issued a request, the master accepted it, and it moved on to thinking about its next one. Nothing in the Wishbone specification governs a master's internal client interface.
ADR_O safe ignores it. Word 4 for all four presented clocks, because the value on the bus is a copy taken at acceptance and the client has no path to it.
ADR_O pass follows it, changing at cycle 5 — which is the terminating clock. The slave decodes whatever is presented at that edge, sees word 0, and returns STATUS.
That timing is the worst case and it is not a coincidence. The change could have landed on any of the four presented clocks; landing on the terminating one means the wrong address is the one that decides the answer. A change on cycle 3 or 4 would still be a RULE 3.60 violation and might still produce a wrong result depending on the slave's style — Chapter 9.2's capturing slave would have latched word 4 at acceptance and been immune, which is exactly the coupling that chapter described.
Both masters report one transfer and no error. ACK_I rises once for each; neither sees ERR_I. The protocol is satisfied from the slave's point of view in both cases — it was asked for word 0 and it answered word 0, correctly.
The two rdat rows are the damage. One client asked for ID and got ID. The other asked for ID and got STATUS, with no indication that anything went wrong.
4. Simulation — SIM F
=== SIM F - the client moves while the transfer is outstanding ===
client asks for word 4 (ID = 0x57420901),
then switches its address input to word 0 (STATUS = 0x00000001)
two clocks later, while the 3-wait slave is still working.
correct master address at termination 0x4
value returned 0x57420901
metadata changes while outstanding 0
transfers 1
passthrough address at termination 0x0
value returned 0x00000001
metadata changes while outstanding 1
transfers 1Read the two value returned lines. The correct master returned 0x57420901 — the ID register, which is what its client asked for. The passthrough master returned 0x00000001, which is STATUS.
The client asked for one register and received another's contents. There is no error, no retry, no flag. err_o is low on both masters and both report transfers = 1.
metadata changes while outstanding is the line that assigns blame. Zero for the correct master, one for the broken one. That counter is a direct RULE 3.60 check — it compares the presented metadata against the previous clock's, but only while a transfer was outstanding — and it is the single number that says the master is at fault rather than the slave.
Why that matters more than the wrong value. A wrong value is a symptom that could come from anywhere: a decode bug, a slave bug, a data-path bug, a testbench bug. A non-zero stability count is conclusive and local. It names the master and it names the rule.
Both masters were driven by the same client stimulus, and both were paired with the same conformant slave. The slave did nothing wrong in either run — Chapter 9.1's Style A slave reads adr_i live, which is sound precisely because RULE 3.60 promises it will be stable. One master kept that promise; the other did not.
And the failure is silent at zero wait states. Re-run the same stimulus against a WAIT_CYCLES = 0 slave and there is no interval for the client to move in: the transfer is presented and terminated in the same clock. The two masters produce identical results, and the test passes.
5. Failure Modes and Discriminating Evidence
Symptom: a read returns the contents of a different register than the one requested.
Candidate causes. Three, and one observation separates them.
Discriminating evidence. The wait monitor's unstable count. Non-zero means the master moved its metadata mid-transfer — a RULE 3.60 violation, and conclusive. Zero means look elsewhere: either the address decode is wrong, or the master's client interface delivered the wrong address in the first place.
Likely RTL location. A combinational path from a client input to a bus output, which is often a single assign.
Symptom: a design works in unit test and returns wrong data in the system.
Candidate causes. The unit test used a zero-wait slave, so no transfer was ever outstanding long enough for anything to move.
Discriminating evidence. Re-run the failing case with WAIT_CYCLES = 0. If it passes, the defect is latency-dependent, and metadata stability is the first thing to check. This is a cheap and decisive triage step.
Symptom: a write commits the wrong payload, intermittently.
Candidate causes. Same class, applied to DAT_O(). The write payload must remain coherent at the terminating edge, per the SINGLE WRITE timing — not merely until shortly before it.
Discriminating evidence. Compare the payload at the presenting edge against the payload at the terminating edge. The monitor checks dat_i only when we_i is asserted, precisely so a read's undefined DAT_O does not raise false alarms.
Symptom: a master hangs against a slave that holds ACK_O asserted.
Candidate causes. Termination detected as an edge rather than a level.
Discriminating evidence. ACK_I already high when STB_O rises. An edge-detecting master never sees a rising edge and waits forever. PERMISSION 3.35 allows the slave to do this and RULE 3.55 requires the master to cope, so the fault is the master's. Chapter 5.4 measured this directly.
Symptom: a master re-presents a request it has already issued.
Candidate causes. Treating a delayed acknowledge as a failure and retrying.
Discriminating evidence. STB_O negating and re-asserting with no termination in between, and a slave-side latency counter that repeatedly climbs and resets without reaching its target. A transfer that is restarted can never complete against any slave slower than the retry interval.
6. Verification
// Properties for a Wishbone master under arbitrary response latency.
// These are written from the master's own port, which is deliberate: they
// are the obligations a master can be held to without knowing anything
// about the slave it is talking to.
//
// NOTE ON EXECUTION: Icarus Verilog does not support SVA. These were
// reviewed by inspection and are NOT claimed to have been executed. The
// numbers in Section 4 come from procedural checks, which Icarus does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_master_wait_props #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32
) (
input logic clk_i, rst_i,
input logic cyc_o, stb_o, we_o,
input logic [AW-1:0] adr_o,
input logic [DW-1:0] dat_o,
input logic [DW/8-1:0] sel_o,
input logic ack_i, err_i, rty_i,
input logic done_o
);
default clocking cb @(posedge clk_i); endclocking
default disable iff (rst_i);
logic outstanding;
// Presented and not terminated: exactly the interval RULE 3.60 governs.
assign outstanding = cyc_o && stb_o && !(ack_i || err_i || rty_i);
// P1 — SPECIFICATION (RULE 3.25). CYC_O is asserted no later than the
// edge that qualifies STB_O. Unchanged by latency.
P1_cyc_qualifies: assert property ( stb_o |-> cyc_o );
// P2..P4 — SPECIFICATION (RULE 3.60). The qualified master outputs are
// stable while a transfer is outstanding. This is THE property of
// this chapter, and it is what wb_passthrough_master fails.
P2_adr_stable: assert property ( outstanding |=> $stable(adr_o) );
P3_we_stable: assert property ( outstanding |=> $stable(we_o) );
P4_sel_stable: assert property ( outstanding |=> $stable(sel_o) );
// P5 — SPECIFICATION (RULE 3.60, and the SINGLE WRITE timing, which
// requires the payload to remain valid until the edge following
// negation of STB_O). Checked only for writes: DAT_O is undefined
// during a read and flagging it would be a false positive.
P5_wdata_stable: assert property ( (outstanding && we_o) |=> $stable(dat_o) );
// P6 — LOCAL POLICY. One accepted client request produces exactly one
// completion report, whatever the latency was. Wishbone has no
// opinion about a master's client interface; this is a contract
// this master offers. Chapter 9.4 generalises it across the matrix.
P6_one_done: assert property (
done_o |-> $past(cyc_o && stb_o && (ack_i || err_i || rty_i))
);
endmoduleP2 through P5 are the chapter, and they are pure specification. They restate RULE 3.60 as four checkable obligations, and they are written against the outstanding window rather than against the whole cycle — which is what makes them usable. A property that demanded stability whenever CYC_O was asserted would fire on every legitimate move to the next transfer in a block cycle.
P5's we_o guard is not caution, it is correctness. Chapter 9.1's slave and the SINGLE READ timing figure both leave DAT_O undefined during a read. Asserting stability on it would make a conformant master fail.
What these properties deliberately do not include is an obligation about detecting termination as a level rather than an edge. That failure mode is real — PERMISSION 3.35 lets a slave hold ACK_O asserted and RULE 3.55 requires masters to cope — but it shows up as a hang, and a liveness property written from these pins alone cannot distinguish "hung master" from "very slow slave". The check that finds it is a testbench with an always-acknowledging slave, which is what Chapter 5.4 built.
P6 is local policy and says so. "One completion report per accepted request" is this master's contract with its client, and Wishbone governs none of it.
7. Common Mistakes
"Nothing has completed yet, so the address can still change."
Wrong mental model: an unterminated transfer is provisional.
What is true: it is outstanding, not pending. RULE 3.60 qualifies the address with STB_O, and STB_O is asserted for every clock of the wait.
Concrete bug: wb_passthrough_master. Measured: the client asked for word 4 and the master returned word 0's contents, with one transfer and no error.
Observable evidence: metadata changes while outstanding = 1 on the wait monitor.
Correct model: the transfer's identity is fixed at the presenting edge. Only its duration is still open.
"Driving the bus straight from the client saves registers and is equivalent."
Wrong mental model: stability is about the values, not about who owns them.
What is true: the master is accountable for RULE 3.60 whether or not it owns the source. Driving from a live client outsources a protocol obligation without a contract.
Concrete bug: four assign statements, and a master that is correct against every zero-wait slave.
Observable evidence: the identical S_XFER states of the two masters — the difference is entirely in what feeds the outputs.
Correct model: latch at acceptance. The guarantee is then an absence of logic rather than a discipline.
"A delayed ACK means something went wrong."
Wrong mental model: silence is failure.
What is true: it is the normal progress of a transfer that is still running. The specification's own words are that the slave "may insert wait states" to "throttle the cycle speed".
Concrete bug: a master that de-asserts and re-presents after a timeout of its own, restarting the slave's latency counter each time. The transfer can never complete against any slave slower than the retry interval.
Observable evidence: a slave-side counter that repeatedly climbs and resets without reaching its target.
Correct model: hold and wait. A failed transfer in Wishbone is ERR_I; a deferred one is RTY_I. Module 10 and Module 11 own those.
"$rose(ACK_I) is a fine way to detect completion."
Wrong mental model: termination is an event.
What is true: it is a level, sampled at the clock edge. PERMISSION 3.35 lets a slave hold ACK_O asserted and RULE 3.55 requires masters to work anyway.
Concrete bug: a master that hangs against an always-acknowledging slave — and which passes every test in this chapter, because a delayed ACK does produce a rising edge.
Observable evidence: ACK_I already high when STB_O rises.
Correct model: sample the level, as Chapter 5.4 measured.
8. Interview Reasoning
Everything that STB_O qualifies, for as long as STB_O is asserted — and the second half of that sentence is where wait states change the picture.
The rule is RULE 3.60: the master qualifies ADR_O, DAT_O(), SEL_O(), WE_O and the tags with STB_O. At zero wait states that is a one-clock obligation and it is hard to violate. At seven wait states it is an eight-clock obligation, and now it takes real design to honour.
For writes the specification is more specific still. The SINGLE WRITE timing says the payload is valid from the presenting edge and stays valid until the rising edge following negation of STB_O — so it must still be coherent at the terminating edge, not merely up to it.
Why the obligation exists. The slave may be reading those signals live. That is a legitimate implementation — it is relying on a stated promise — so a master that breaks the promise corrupts a slave that did nothing wrong.
What I measured. Two masters, same client, same slave, three wait states. The correct one latches the request at acceptance; the broken one drives the bus from the client's live outputs. When the client changed its address mid-transfer, the broken master returned the contents of a completely different register — STATUS instead of ID — with one transfer, no error and nothing on the bus to suggest a problem.
The design conclusion I would give. Latch the request at acceptance. Then the guarantee is an absence of logic rather than a discipline — there is simply no path by which the values could change, and that holds for a one-clock transfer and a fifty-clock one identically.
And the thing I would add about testing, because it is why this bug ships: at zero wait states the two masters are bit-identical. The defect is unreachable unless the suite includes a slave that waits.
9. Understanding Check
10. What's Next
Both sides now have their obligations. A slave may take as long as it needs and must not act more than once; a master must hold what it presented and must not capture early.
Each chapter has demonstrated this on one operation at a time — a read here, a write there, one latency per experiment.
If a transfer can be stretched to any length, what exactly is preserved across all of those lengths?
Chapter 9.4 — Transaction Extension runs the same read and the same write across a matrix of delays and measures what stays constant. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
STB — Strobe
A transfer is presented for as long as the master waits and accepted in exactly one cycle. A slave that confuses the two performs its write once per waiting cycle, on a bus that stays perfectly conformant.
- 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.
- Related topic
Wait States
While a read waits, the master waits for an answer. While a write waits, it holds the data that will mutate the device. Payload coherence and one-shot commits are the two obligations that follow.
- Related topic
Transaction Extension
Stretching a transfer from one clock to six changes its duration and nothing else. A measured matrix: same transfers, same terminations, same value, same single side effect.
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.
