Wishbone · Module 4
RTY_I
A slave saying "not now" rather than "no". Unlike a wait state it releases the bus, which breaks a whole class of deadlock — and unlike an error it invites another attempt, which is what makes livelock possible.
Chapter 4.11 established that retrying an error is a bug. But some refusals are not permanent, and for those, retrying is the entire point.
How does a slave say "not now" rather than "no" — and why is retry the one termination that can deadlock a system?
1. Why Not Just Insert Wait States?
A slave that is momentarily busy already has a mechanism: withhold the acknowledge, as Chapter 4.10 §3 described. So why does RTY_O exist at all?
Because a wait state holds the bus, and a retry releases it.
Withholding the acknowledge keeps the master's transfer presented. CYC_O stays asserted, the arbiter keeps the grant, and no other master can use the bus until this slave is ready.
Asserting RTY_O terminates the transfer. The master negates CYC_O and STB_O, the bus becomes available, and the master comes back later.
When the slave will become ready on its own — a memory refreshing, a pipeline draining — waiting is fine and simpler. Holding the bus for a few cycles costs a little throughput.
2. What a Retry Obliges the Master to Do
The specification says when and how is supplier-defined, so this is engineering judgement, presented as such.
Release the bus. Genuinely negate CYC_O — not merely drop STB_O and keep the tenure. Holding the cycle across a retry recreates the deadlock the retry was meant to break, while looking like correct retry handling.
Do not treat it as success. Nothing happened. No data was transferred, no state changed.
Do not treat it as failure either. Reporting a retry upward as an error converts a transient condition into a spurious failure.
Bound the attempts. This is where retry differs from every other termination, and it deserves its own section.
3. Livelock, and Why Retry Needs a Bound
Unbounded retry is not a safe default. A master that retries forever will retry forever if the condition never clears, and unlike a hang it does so while generating bus traffic — which makes it worse in two ways.
It consumes bandwidth. A hung master sits still. A retrying master occupies the bus repeatedly, delaying everyone else and possibly preventing the very progress that would clear its condition.
It looks healthy. The bus is active, transfers are terminating, no rule is violated, no signal is stuck. Nothing about the waveform says "this system is not making progress", which is what makes livelock harder to recognise than deadlock.
Two masters can retry each other indefinitely. Each holds a resource the other needs, each releases the bus politely on RTY_I, each retries, and the interleaving repeats. Every individual action is correct. The system does nothing, busily.
What a real master does instead:
Count the attempts and escalate after a bounded number — report a failure upward, where a caller can decide.
Back off between attempts, so a retrying master does not monopolise the bus with the requests that prevent the condition clearing. Even a small fixed delay changes the dynamics materially.
Avoid lock-step, where two masters retry in phase and collide every time. A varying backoff — even a crude one derived from the master's ID — breaks the symmetry. Unlike the other two, this one only matters with multiple masters, and a single-master design that omits it will not reveal the omission until a second master arrives.
Document the policy, per RULE 2.15.
4. RTL — A Retrying Slave and a Backing-Off Master
// ─────────────────────────────────────────────────────────────────────────
// wb_rty_slave — a mailbox FIFO that RETRIES rather than waits when full.
//
// WHY RETRY AND NOT A WAIT STATE (Section 1): this FIFO's readiness depends
// on ANOTHER master draining it. Holding the bus while waiting for space
// would prevent the drain and deadlock the system. Terminating with RTY_O
// releases the bus so the drain can happen.
//
// A single-master version of this same FIFO could safely use wait states.
// The RTL would be correct in one system and deadlock the other, which is
// why the choice belongs in the datasheet (RULE 2.15).
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00).
// ─────────────────────────────────────────────────────────────────────────
module wb_rty_slave #(
parameter int unsigned OFF_AW = 2,
parameter int unsigned DW = 32,
parameter int unsigned DEPTH = 4
) (
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,
output logic [DW-1:0] dat_o,
output logic ack_o,
output logic err_o,
output logic rty_o,
output logic [3:0] level_o
);
localparam logic [OFF_AW-1:0] W_DATA = 'd0; // push (W) / pop (R)
localparam logic [OFF_AW-1:0] W_LEVEL = 'd1; // read-only occupancy
logic [DW-1:0] mem_q [DEPTH];
logic [3:0] lvl_q;
assign level_o = lvl_q;
logic xfer;
assign xfer = cyc_i & stb_i; // RULES 3.30 & 3.35
logic full, empty;
assign full = (lvl_q == 4'(DEPTH));
assign empty = (lvl_q == 4'd0);
// ── TERMINATION SELECTION ──────────────────────────────────────────────
// Three conditions, three different answers, and the distinction between
// the last two is the chapter:
//
// e_perm : this can NEVER work -> ERR_O (permanent)
// r_now : this cannot work RIGHT NOW -> RTY_O (transient)
// else -> ACK_O
//
// A full FIFO is not an error. The same write will succeed once a drain
// happens, so reporting ERR_O would turn a transient condition into a
// permanent failure and the master would correctly never try again.
logic e_perm, r_now;
always_comb begin
unique case (adr_i)
W_DATA: e_perm = 1'b0;
W_LEVEL: e_perm = we_i; // level is read-only
default: e_perm = 1'b1; // unmapped offset
endcase
end
always_comb begin
r_now = 1'b0;
if (!e_perm && (adr_i == W_DATA)) begin
r_now = we_i ? full : empty; // full: no space to push
end // empty: nothing to pop
end
// RULE 3.45: mutually exclusive by construction. Exactly one is asserted
// for any qualified transfer, and none otherwise.
assign err_o = xfer & e_perm;
assign rty_o = xfer & ~e_perm & r_now;
assign ack_o = xfer & ~e_perm & ~r_now;
// ── A RETRIED TRANSFER CHANGES NOTHING ─────────────────────────────────
// The same obligation an errored transfer carries (Chapter 4.11 Sec. 2),
// and for a sharper reason: the master WILL come back. A retry that
// half-completed would apply its effect once per attempt.
logic push, pop;
assign push = ack_o & we_i & (adr_i == W_DATA);
assign pop = ack_o & ~we_i & (adr_i == W_DATA);
always_ff @(posedge clk_i) begin
if (rst_i) begin
lvl_q <= 4'd0;
for (int unsigned i = 0; i < DEPTH; i++) mem_q[i] <= '0;
end else begin
if (push) begin
mem_q[lvl_q[1:0]] <= dat_i;
lvl_q <= lvl_q + 4'd1;
end else if (pop) begin
for (int unsigned i = 0; i < DEPTH-1; i++) mem_q[i] <= mem_q[i+1];
lvl_q <= lvl_q - 4'd1;
end
end
end
always_comb begin
dat_o = '0; // RULE 3.65
if (xfer && !we_i && ack_o) begin
dat_o = (adr_i == W_LEVEL) ? {{(DW-4){1'b0}}, lvl_q} : mem_q[0];
end
end
endmodule// ─────────────────────────────────────────────────────────────────────────
// wb_rty_master — bounded retry with backoff.
//
// POLICY (the material RULE 2.15 requires in a datasheet):
// * RTY_I releases the bus completely — CYC_O is negated, not just STB_O.
// * The transfer is re-presented after a backoff delay.
// * Backoff grows with each attempt, and is seeded from MASTER_ID so two
// masters do not retry in lock-step.
// * After MAX_TRIES attempts the transfer is abandoned and reported as a
// failure, so a never-clearing condition cannot livelock the system.
// * ERR_I is NOT retried (Chapter 4.11).
// ─────────────────────────────────────────────────────────────────────────
module wb_rty_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned MAX_TRIES = 8,
parameter int unsigned MASTER_ID = 0
) (
input logic clk_i,
input logic rst_i,
input logic go_i,
input logic we_i,
input logic [AW-1:0] adr_i,
input logic [DW-1:0] wdat_i,
output logic done_o,
output logic ok_o,
output logic gave_up_o,
output logic [3:0] tries_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,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic [1:0] { T_IDLE, T_XFER, T_BACKOFF } tstate_e;
tstate_e state_q;
logic [AW-1:0] adr_q;
logic [DW-1:0] wdat_q;
logic we_q;
logic [7:0] wait_q;
// ── THE BUS IS GENUINELY RELEASED IN T_BACKOFF ─────────────────────────
// cyc_o is low there, not merely stb_o. Holding CYC_O across a backoff
// would keep the arbiter's grant and recreate the deadlock the retry
// exists to break — while looking like correct retry handling.
assign cyc_o = (state_q == T_XFER);
assign stb_o = (state_q == T_XFER);
assign we_o = we_q;
assign adr_o = adr_q;
assign dat_o = wdat_q;
// Backoff grows with the attempt count. MASTER_ID offsets it so two
// masters retrying the same resource do not collide every time — the
// lock-step hazard of Section 3, which only appears with more than one.
function automatic logic [7:0] backoff_of(input logic [3:0] n);
backoff_of = 8'((32'(n) << 2) + 32'(MASTER_ID) + 1);
endfunction
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= T_IDLE; // RULE 3.20 via cyc/stb
adr_q <= '0;
wdat_q <= '0;
we_q <= 1'b0;
wait_q <= '0;
tries_o <= '0;
rdat_o <= '0;
done_o <= 1'b0;
ok_o <= 1'b0;
gave_up_o <= 1'b0;
end else begin
done_o <= 1'b0;
unique case (state_q)
T_IDLE: begin
if (go_i) begin
state_q <= T_XFER;
adr_q <= adr_i;
wdat_q <= wdat_i;
we_q <= we_i;
tries_o <= 4'd1;
gave_up_o <= 1'b0;
end
end
T_XFER: begin
if (ack_i) begin
if (!we_q) rdat_o <= dat_i; // Chapter 4.5's capture
state_q <= T_IDLE;
done_o <= 1'b1;
ok_o <= 1'b1;
end else if (err_i) begin
// Permanent. Retrying would loop forever (Chapter 4.11).
state_q <= T_IDLE;
done_o <= 1'b1;
ok_o <= 1'b0;
end else if (rty_i) begin
if (tries_o >= 4'(MAX_TRIES)) begin
// ── THE BOUND ────────────────────────────────────────────
// Escalate rather than retry forever. A condition that has
// not cleared in MAX_TRIES attempts is reported upward,
// where a caller can decide. Without this, a never-clearing
// condition livelocks the bus while looking healthy.
state_q <= T_IDLE;
done_o <= 1'b1;
ok_o <= 1'b0;
gave_up_o <= 1'b1;
end else begin
state_q <= T_BACKOFF; // release the bus
wait_q <= backoff_of(tries_o);
tries_o <= tries_o + 4'd1;
end
end
end
T_BACKOFF: begin
if (wait_q == 8'd0) state_q <= T_XFER; // re-present unchanged
else wait_q <= wait_q - 8'd1;
end
default: state_q <= T_IDLE;
endcase
end
end
endmoduleReading the pair
Purpose. The slave shows a transient condition distinguished from a permanent one. The master shows the bounded, backed-off retry that keeps a transient condition from becoming a livelock.
Ownership. The slave drives all three terminations; the master consumes them and drives its qualifiers.
Combinational logic. Slave: xfer, the permanent and transient conditions, three exclusive terminations, push/pop enables, a read multiplexer gated on ack_o. Master: qualifiers from state, and backoff_of.
Sequential logic. Slave: the FIFO array and occupancy. Master: state, latched request, backoff counter, attempt count, result flags.
Timing. All three terminations are combinational from the qualified transfer. The master spends one cycle in T_XFER per attempt and backoff_of(n) cycles in T_BACKOFF, during which CYC_O is low and the bus is available.
Qualification. Every termination contains xfer; push and pop contain ack_o, so a retried or errored transfer cannot move the FIFO.
Reset. Synchronous, active high. state_q <= T_IDLE negates both qualifiers per RULE 3.20.
Simplifications. The FIFO pops by shifting, which is clear rather than efficient — a real design uses pointers. MAX_TRIES is bounded by the 4-bit counter. The backoff function is deliberately crude; its purpose is to break symmetry, not to be optimal.
Failure modes. Section 6.
5. Waveform — Retry, Release, Succeed
RTY_I: the bus is released between attempts
9 cyclesCycles 2 to 4 are the mechanism. CYC_O is low, not just STB_O. The arbiter is free to grant the bus, and the other master's drain — visible as level falling from 4 to 3 — is only possible because of that.
A master that dropped STB_O but held CYC_O would show an identical strobe waveform and would deadlock: the grant never moves, the drain never happens, and every retry finds the FIFO still full. That is why CYC_O is the signal to check when a retry loop never succeeds.
Cycle 1 shows the choice of termination. A full FIFO is not an error — the same write will succeed after a drain. Reporting ERR_O would be correct signalling of an incorrect claim, and a well-behaved master would never try again.
6. Failure Modes and Discriminating Evidence
Symptom: a master retries forever and the condition never clears.
Candidate causes. The master holds CYC_O across its backoff, so the bus is never released and whatever would clear the condition cannot run.
Discriminating evidence. Watch CYC_O, not STB_O. Asserted continuously across the retries is conclusive — and the strobe waveform looks correct in both the working and broken cases, which is why the wrong signal gets checked first.
Likely RTL location. The cyc_o assignment — asserted for the whole transaction rather than only while a transfer is presented.
Property. P2 in Section 7.
Symptom: a system stops making progress but the bus is busy and no signal is stuck.
Candidate causes. Livelock. Two or more masters retrying resources each other holds, each releasing politely, each retrying in lock-step.
Discriminating evidence. Count completed transfers over a window rather than looking for a stuck signal. Zero acknowledges with continuous bus activity is the signature, and it is invisible to any per-transfer check because every individual transfer is legal.
Likely RTL location. The retry policy — unbounded attempts, no backoff, or identical backoff across masters.
Symptom: a transient condition is reported to software as a permanent failure.
Candidate causes. The slave asserts ERR_O where RTY_O was meant — a full FIFO, a busy port, a refresh in progress.
Discriminating evidence. Retry the same access manually after a delay. Success means the condition was transient and the termination was wrong.
Likely RTL location. The termination selection. The question to ask of each condition is whether the identical transfer could succeed later without anything the master controls changing.
Symptom: a retried write takes effect more than once.
Candidate causes. The slave's state change is gated on the qualified transfer rather than on the acknowledge, so each attempt applies the effect.
Discriminating evidence. Push one value into a FIFO that retries twice and check the occupancy. An increase of three for one logical write is conclusive, and this is strictly worse than the errored-write case because the master is expected to come back.
Property. P3.
Symptom: two masters make progress individually but stall when run together.
Candidate causes. Lock-step retry — identical backoff, so both retry on the same cycle every time and collide identically.
Discriminating evidence. The retries align cycle-for-cycle across masters. Changing one master's MASTER_ID and seeing the stall clear confirms it.
7. Verification
// ─────────────────────────────────────────────────────────────────────────
// wb_rty_checker — retry properties.
//
// P1 is SPECIFICATION (RULES 3.35, 3.45). P2, P3 and P4 are DESIGN
// OBLIGATIONS — the specification says when and how a cycle is retried is
// supplier-defined, so these encode THIS system's documented policy, which
// is the material RULE 2.15 requires in the datasheet.
// ─────────────────────────────────────────────────────────────────────────
module wb_rty_checker #(
parameter int unsigned MAX_TRIES = 8
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic ack_o,
input logic err_o,
input logic rty_o,
input logic [3:0] lvl_q,
input logic [3:0] tries_q
);
default disable iff (rst_i);
// P1 — SPECIFICATION. RULE 3.35: generated from the qualified transfer.
// RULE 3.45: exclusive with the other two.
property p_rty_legal;
@(posedge clk_i) rty_o |-> ((cyc_i && stb_i) && !ack_o && !err_o);
endproperty
a_rty_legal : assert property (p_rty_legal)
else $error("RTY_O unqualified or asserted with another termination");
// P2 — DESIGN OBLIGATION, and the one this chapter exists for. A retry
// must RELEASE THE BUS: cyc_i is negated the cycle after RTY_O.
// A master that keeps the tenure recreates the deadlock the retry
// was meant to break, while its strobe waveform looks correct.
property p_rty_releases_bus;
@(posedge clk_i) (cyc_i && stb_i && rty_o) |=> !cyc_i;
endproperty
a_rty_releases_bus : assert property (p_rty_releases_bus)
else $error("CYC_I still asserted the cycle after RTY_O");
// P3 — DESIGN OBLIGATION. A retried transfer changes nothing. Sharper
// than the errored case: the master WILL come back, so an effect
// applied per attempt is applied repeatedly.
property p_rty_changes_nothing;
@(posedge clk_i) $changed(lvl_q) |-> !$past(rty_o);
endproperty
a_rty_changes_nothing : assert property (p_rty_changes_nothing)
else $error("slave state changed on a transfer terminated with RTY_O");
// P4 — DESIGN OBLIGATION, as LIVENESS. Attempts are BOUNDED. Nothing at
// the signal level forbids infinite retry, and livelock produces a
// bus that looks entirely healthy — so this is the only place the
// failure is visible.
property p_tries_bounded;
@(posedge clk_i) tries_q <= 4'(MAX_TRIES);
endproperty
a_tries_bounded : assert property (p_tries_bounded)
else $error("retry attempts exceeded the documented bound");
endmoduleP4 is a liveness property in a module otherwise full of safety properties, and the distinction is worth naming. A safety property says nothing bad happens; a liveness property says something good eventually does. Livelock violates no safety property anywhere — every transfer is legal, every rule is obeyed — and is caught only by asking whether progress occurred.
This is the third time this module has reached that boundary: atomicity in Chapter 4.9, the RULE 3.55 hang in Chapter 4.10, and livelock here. All three are conformant buses attached to systems that do not work, and none is detectable from signal legality alone.
Tooling limitation. Icarus has no SVA support; reviewed by inspection only.
8. Common Mistakes
"Retry and error are both failures, so handle them together."
Wrong mental model: a termination is either success or failure.
Concrete bug: if (err_i || rty_i) fail(); — which converts every transient condition into a permanent failure, or, with the branches reversed, retries a permanent error forever.
Observable evidence: spurious failures under load, or a master looping on an unmapped address.
Correct model: they differ in exactly one respect — whether another attempt could succeed. That single bit is why there are three terminations rather than two, and a master that collapses them has discarded the information the third wire exists to carry.
"Retrying is safe, so retry until it works."
Wrong mental model: the condition will always clear.
Concrete bug: unbounded retry. If the condition never clears, the master retries forever while generating bus traffic.
Observable evidence: a system making no progress with a fully active bus and no stuck signals — much harder to recognise than a hang, because everything looks healthy.
Correct model: bound the attempts and escalate. A condition that has not cleared after a documented number of tries is a failure, and reporting it lets a caller decide.
"I dropped STB_O on the retry, so I released the bus."
Wrong mental model: the strobe is what holds the bus.
Concrete bug: dropping STB_O while holding CYC_O across the backoff. The arbiter keeps the grant, the other master never runs, the condition never clears, and the retry never succeeds.
Observable evidence: a retry loop that never succeeds, with a strobe waveform identical to the working case.
Correct model: CYC_O is the bus request (Chapter 4.9). Releasing the bus means negating it. This is the same signal that provides atomicity when held — and here holding it is exactly the bug.
9. Interview Reasoning
At CYC_O during the backoff — not at STB_O, which is where the eye goes first and which looks correct in both the working and broken cases.
The mechanism. The FIFO is drained by another master. For that master to run, the arbiter must grant it the bus. The arbiter cannot grant while the retrying master's CYC_O is asserted, because an open cycle is a live bus request — which is exactly the behaviour that provides atomicity in Chapter 4.9.
So a master that drops STB_O and holds CYC_O across its backoff has released nothing. It looks like it is waiting politely between attempts. It is holding the bus continuously, the drain never happens, every retry finds the FIFO still full, and the loop is infinite.
The one observation. CYC_O asserted continuously across several retries is conclusive. If it is genuinely negated between attempts and the FIFO still never drains, the problem is elsewhere — the other master is blocked on something else, or the arbiter is not granting it.
Why the bug is easy to write. Every other master in this module drives cyc_o and stb_o from the same signal, so separating them for a backoff requires noticing that they should differ in the opposite direction from the block-transfer case: there, CYC_O outlives STB_O deliberately; here, it must not.
The deeper point I would make. This is the same signal doing two opposite jobs. Held across transfers it provides atomicity; held across a retry it causes deadlock. CYC_O is a claim on the bus, and whether holding it is correct depends entirely on whether anything else needs the bus to make your condition clear. That question — does my unblocking depend on someone else getting this resource — is the one to ask before choosing between a wait state and a retry at all.
10. Understanding Check
11. Module 4 Complete
Every signal in the Wishbone Classic interface now has a chapter:
| Master output | Master input | ||
|---|---|---|---|
| 4.3 | ADR_O — which word | 4.5 | DAT_I — received data |
| 4.4 | DAT_O — driven data | 4.10 | ACK_I — completed |
| 4.6 | WE_O — direction | 4.11 | ERR_I — cannot |
| 4.7 | SEL_O — which bytes | 4.12 | RTY_I — not now (this chapter) |
| 4.8 | STB_O — this transfer | ||
| 4.9 | CYC_O — this tenure |
With 4.1 and 4.2 supplying the clock and reset every one of them references.
What this module deliberately did not do is teach the handshake as a sequence. Each chapter covered what a signal means, what qualifies it, and what breaks when that qualification is dropped — and each stopped short of cycle-by-cycle timing.
How do these twelve signals combine, cycle by cycle, into a transfer?
Module 5 — The Wishbone Handshake answers it, taking CYC, STB and ACK as a timing contract rather than as three definitions. Module 6 and Module 7 then walk complete read and write cycles, wait states included; neither has shipped yet, which is why they appear in bold rather than as links.
The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Need for Standardized Interconnects
An address map answers where a register lives. It says nothing about which wires carry the request, when they are valid, how the target reports completion, or what happens on an error. Three peripherals with three private interfaces produce three adapters, three verification efforts and three ways to be wrong — which is the argument for standardising the interface rather than the map.
- Related topic
SoC Communication
Six chapters built the pieces; this one assembles them into a working fabric and traces three real accesses through it. The result works, and reading the nine unwritten rules a third party would need is what makes the case for a published protocol concrete rather than theoretical.
- Related topic
Transaction Lifecycle
One Wishbone transaction from a master's decision to act through the slave's termination and back to the caller: what is fixed by the protocol, what every implementation may vary, and what a real simulation of the assembled system shows at each step.
- Related topic
RST_I
Wishbone's reset is synchronous and active high, and initialisation happens at the clock edge after assertion rather than at assertion: what RULES 3.00 and 3.20 require, and what an asynchronous reset breaks.
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.
