Wishbone · Module 11
Temporary Resource Unavailability
A full queue can be answered with wait states or with RTY. Measured against the same condition: 11 clocks of bus occupancy and one attempt, against 4 clocks and four attempts.
Chapter 11.2 built the retry machine and never asked whether the target should be deferring at all. The slave returns RTY on a full queue because it was written to — and the same condition could equally have been answered by withholding the termination.
When is a deferral the right answer, and what does it cost compared with simply waiting?
1. The Case Where There Is No Choice
One situation settles itself, and Chapter 4.12 §1 worked it in full, so it is recalled here rather than re-derived.
If the resource can only be released by another master getting the bus, waiting deadlocks the system. A mailbox FIFO written by master A and drained by master B: A waits on a full queue, holding CYC_O; the arbiter cannot grant B; B cannot drain; space never appears.
Every component behaves correctly and the system is dead — the arbiter is honouring an open cycle exactly as Chapter 8.4 required it to.
The principle generalises well beyond Wishbone: a resource must not block while holding the thing its unblocking depends on.
So the decision procedure starts with one question:
Can this resource become available while the bus is held?
No → the target must defer. Waiting is a deadlock. Yes → both work, and Section 5's measurement decides.
Note that this is a property of the system, not of the slave. The same FIFO is safe to wait on with one master and deadlocks with two. The RTL is identical and correct in only one of them — which is exactly the kind of thing RULE 2.15's datasheet exists to record.
2. What Each Choice Actually Costs
When both are legal, the trade is between who holds the bus and how much traffic is generated.
| WAIT | RTY | |
|---|---|---|
| Bus held while unavailable | yes, continuously | no |
| Transfers generated | one | one per attempt |
| State required in the master | none | counter, latched request, delay |
| Client semantics | simple: one transfer, one answer | two-level: attempts and requests |
| Failure mode if misused | deadlock | livelock |
| Other masters | blocked | can use the bus between attempts |
The last row is usually the deciding one, and it is the reason the question is a system question. In a single-master system there is nobody to block, so waiting costs nothing anybody can observe and the retry machinery is pure overhead.
Both failure modes are total, which is worth noticing. A deadlocked wait and a livelocked retry both mean the system stops making progress; they differ in whether the bus looks idle or busy while it happens. Chapter 11.5 is largely about telling them apart.
3. RTL — The Same Resource, Answered by Waiting
The slave below is Chapter 11.1's with one decision changed.
// wb_wait_fifo_slave — the SAME resource condition, answered with wait
// states instead of a deferral.
//
// Identical to wb_cmd_fifo_slave except for one decision: when the queue
// is full, this slave WITHHOLDS its termination instead of asserting
// RTY_O. The transfer stays outstanding until space appears.
//
// COMMAND write, space -> ACK_O
// COMMAND write, FULL -> (no termination; the transfer waits)
// offset not implemented -> ERR_O
//
// BOTH ARE CONFORMANT. Nothing in Wishbone says a busy target must defer
// rather than wait, and nothing says the reverse. The choice changes what
// the system does, not whether it is legal, and Chapter 11.3 measures the
// difference rather than asserting it.
//
// This slave has NO RTY_O port at all, which is legal: PERMISSION 3.25
// makes support optional. A master with RTY_I connected to nothing simply
// never sees that class.
//
// WHERE THIS DESIGN IS DANGEROUS, and Chapter 4.12 worked it in full: if
// the queue can only drain when ANOTHER master gets the bus, waiting here
// holds the bus that the draining master needs. Every component behaves
// correctly and the system deadlocks. That hazard is structural and is the
// reason RTY_O exists; it is not something this slave's RTL can fix.
// ─────────────────────────────────────────────────────────────────────────
module wb_wait_fifo_slave #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32,
parameter int unsigned DEPTH = 2
) (
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,
input logic drain_i,
output logic [3:0] level_o,
output logic full_o,
output int unsigned pushes_o,
output logic [DW-1:0] last_cmd_o
);
localparam logic [OFF_AW-1:0] O_STATUS = 4'd0;
localparam logic [OFF_AW-1:0] O_ID = 4'd4;
localparam logic [OFF_AW-1:0] O_CMD = 4'd9;
localparam logic [DW-1:0] ID_VALUE = 32'h5742_1101;
logic [3:0] level_q;
logic [DW-1:0] last_q;
logic xfer, mapped, is_cmd, full, accept;
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_STATUS) || (adr_i == O_ID) || (adr_i == O_CMD);
assign is_cmd = (adr_i == O_CMD) && we_i;
assign full = (level_q >= 4'(DEPTH));
assign accept = xfer && mapped && is_cmd && !full;
// ── THE ONE DIFFERENCE. A full queue produces NO termination, so the
// master's transfer stays outstanding rather than ending.
assign ack_o = xfer && mapped && (!is_cmd || !full);
assign err_o = xfer && !mapped;
always_comb begin
dat_o = '0;
if (xfer && !we_i && mapped) begin
case (adr_i)
O_STATUS: dat_o = {27'd0, full, level_q[2:0]};
O_ID: dat_o = ID_VALUE;
default: dat_o = '0;
endcase
end
end
assign level_o = level_q;
assign full_o = full;
assign last_cmd_o = last_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
level_q <= '0; last_q <= '0; pushes_o <= '0;
end else begin
case ({accept, (drain_i && (level_q != 4'd0))})
2'b10: level_q <= level_q + 4'd1;
2'b01: level_q <= level_q - 4'd1;
default: ;
endcase
if (accept) begin
last_q <= dat_i;
pushes_o <= pushes_o + 1;
end
end
end
endmoduleReading it against the deferring slave
The difference is one line. wb_cmd_fifo_slave computes rty_o = xfer && mapped && is_cmd && full; this slave computes nothing for that case at all. A full queue produces no termination, so the transfer stays outstanding.
Everything else is identical, including the accept term that gates both the acknowledge and the push. The side-effect contract is unchanged — a waiting slave has not committed anything either, it simply has not answered yet.
This slave has no RTY_O port, which is legal: PERMISSION 3.25 makes support optional, and a master whose RTY_I is tied low never sees the class. Two conformant interfaces, and the pairing works because neither expects the other to support it.
What would make this slave dangerous is not visible in its RTL at all. If the queue drains only when another master is granted the bus, this design deadlocks — and the identical source would be perfectly safe in a single-master system. Section 1's question is the one that decides it.
4. Waveform — Holding Versus Releasing
One long transfer, or several short ones
10 cyclesThe WAIT CYC+STB row never drops. One transfer, presented continuously, with no termination in sight. The bus belongs to this master for the entire time the queue is full.
The RTY CYC+STB row is three short pulses, each one a complete transfer terminated by RTY_I. Between them the master is presenting nothing.
The bus free row is the architectural consequence, and it is the whole reason to prefer a deferral. In the waiting case it is low throughout; in the retrying case it is high for most clocks — and those are the clocks another master could be using.
Neither master is making progress. The queue is full in both columns and nothing has been enqueued. The difference is entirely in what else the system can do meanwhile.
This figure is schematic in one respect and the caption says so: the retry cadence here is drawn at a fixed spacing to make the shape legible. Section 5's measurement is the authority for the actual numbers, and it was run with RETRY_DELAY = 2.
5. Simulation — SIM F
One DEPTH = 1 queue with a command already in it, so the measured write meets a full resource. The drain arrives partway through. Both rigs get the same queue, the same drain, and the same request.
=== SIM F - one resource condition, two slave policies ===
DEPTH=1 queue, a command already queued, drained mid-run.
Both masters are trying to write COMMAND.
policy bus attempts clocks holding CYC clocks to completion ok
WAIT 1 11 11 1
RTY 4 4 13 1Both succeeded. ok = 1 on both rows — the resource cleared and the command was enqueued either way. The policies differ in cost, not in outcome.
Bus occupancy: 11 clocks against 4. The waiting master held CYC_O for every clock from presentation to completion. The retrying master held it for four clocks total across four attempts — one clock each — and left it free for the other seven.
That is a 2.75× difference in bus occupancy for the same operation, and it is the number the decision turns on. In a single-master system it buys nothing; in a system where something else needs the bus, it is the difference between that something else running and not running.
Completion latency: 11 clocks against 13. The retrying master took two clocks longer to finish. That is the price, and it comes from the retry cadence: with RETRY_DELAY = 2, an attempt that could have succeeded at clock 11 was not made until clock 13.
A shorter delay would narrow the latency gap and raise the attempt count. That is the tuning axis, and it is a genuine trade rather than a free improvement: more attempts is more bus traffic, which is the thing the deferral was supposed to save.
Attempt count: 1 against 4. The retrying rig generated four transfers where the waiting rig generated one. Each is a full presentation and termination — real bus activity that a performance model must account for and that a naive attempt counter will report as four operations.
What the numbers do not settle
They do not say which policy is correct, and it would be a misreading to take 4 < 11 as a verdict.
If nothing else wants the bus, the waiting master's 11 clocks of occupancy cost nothing observable and its 2-clock latency advantage is free. The retry machinery — counter, latched request, delay, exhaustion path — is pure complexity.
If something else does want the bus, those 7 free clocks are the entire point, and 2 clocks of added latency is a small price.
And if the resource can only clear because something else gets the bus, the waiting rig does not take 11 clocks. It never finishes, and Section 1's question is the one that matters rather than anything in this table.
6. Failure Modes and Discriminating Evidence
Symptom: a system deadlocks under load and works in isolation.
Candidate causes. A waiting slave whose resource is released by another master.
Discriminating evidence. One master holding CYC_O indefinitely, with the resource's producer or consumer starved of grants. The held transfer is the cause, not a symptom.
Correct model: Section 1's question. This is not tunable — no wait-state budget fixes it, because the wait is structurally unsatisfiable.
Symptom: the bus is saturated with short transfers that achieve nothing.
Candidate causes. A retry cadence far shorter than the resource's recovery time.
Discriminating evidence. Attempts per successful operation, and the fraction terminated with RTY. Four attempts for one success is the measured figure here; forty would mean the delay is badly tuned.
The fix is RETRY_DELAY, not the policy. Deferring was still the right choice; the cadence is wrong.
Symptom: an operation is reported as failed when the resource was merely busy.
Candidate causes. A slave answering a transient condition with ERR_O.
Discriminating evidence. The same request succeeding moments later. A genuine ERR condition is a property of the request; a transient one is not.
Correct model: ERR for "never", RTY for "not now" — and that mapping is the slave's documented policy, not something the protocol assigns.
Symptom: a retrying system is slower than the waiting design it replaced.
Candidate causes. Expected, and measured here: 13 clocks against 11.
Discriminating evidence. Bus occupancy, not latency. If nothing else was contending for the bus, the change bought nothing and cost two clocks. The justification for deferring is what other masters can do with the free clocks — if there are none, there is no justification.
7. Verification
// Properties for the waiting slave. Note how few there are: a slave that
// answers by NOT answering has almost nothing to assert, which is part of
// its appeal and part of its danger.
//
// 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 5 come from procedural checks, which Icarus does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_wait_slave_props (
input logic clk_i, rst_i,
input logic cyc_i, stb_i, we_i,
input logic [3:0] adr_i,
input logic ack_o, err_o,
input logic full_i,
input int unsigned pushes_i
);
default clocking cb @(posedge clk_i); endclocking
default disable iff (rst_i);
logic xfer, cmd_write;
assign xfer = cyc_i && stb_i;
assign cmd_write = xfer && we_i && (adr_i == 4'd9);
// W1 — SPECIFICATION (RULE 3.35). Terminations are qualified.
W1_qualified: assert property ( (ack_o || err_o) |-> xfer );
// W2 — SPECIFICATION (RULE 3.45). At most one class. Trivially true
// here because RTY_O does not exist, which is itself the point:
// PERMISSION 3.25 makes the signal optional.
W2_exclusive: assert property ( !(ack_o && err_o) );
// W3 — LOCAL POLICY. This slave's documented behaviour: a COMMAND write
// to a full queue receives NO termination. The property is written
// as an absence, which is the only way to state "it waits".
W3_waits_when_full: assert property ( (cmd_write && full_i) |-> !ack_o );
// W4 — LOCAL CONTRACT. Nothing is enqueued while the queue is full, so
// a waiting transfer has committed nothing either. This matches
// the deferring slave's contract exactly; the difference between
// the two designs is in the termination, not in the side effect.
W4_no_push_when_full: assert property ( (cmd_write && full_i) |=> $stable(pushes_i) );
// W5 — LOCAL POLICY, and deliberately absent. There is NO liveness
// property here, because this slave cannot offer one: whether the
// transfer ever terminates depends on whether something drains the
// queue, which is a system question. A waiting slave in a system
// that cannot drain is a deadlock, and no property written from
// these pins can distinguish that from a slow drain.
endmoduleW5 is a comment rather than a property, and that is the honest outcome. A deferring slave can be shown to terminate every attempt; a waiting slave cannot promise anything about termination at all. Its liveness is a property of the system around it.
That asymmetry is the strongest argument in the chapter. The retry design is checkable — bounded attempts, bounded exhaustion, a guaranteed outcome. The waiting design's correctness lives outside the module, in facts about arbitration and about who drains the queue, and a reviewer looking only at the RTL cannot see it.
W4 is worth stating even though it looks trivial. It makes explicit that the two policies share a side-effect contract: neither commits anything before it answers. The difference between them is entirely in the termination.
8. Common Mistakes
"RTY is the modern way; wait states are legacy."
Wrong mental model: one mechanism supersedes the other.
What is true: they solve different problems. Measured, waiting completed the same operation in 11 clocks against the retrying design's 13 — and used 11 clocks of bus occupancy against 4.
Concrete bug: replacing wait states with retries in a single-master system, adding a counter, a latch, a delay and an exhaustion path to free up bus clocks nobody wanted.
Correct model: deferring buys bus availability. If nothing else wants the bus, it buys nothing.
"Waiting is simpler, so prefer it when in doubt."
Wrong mental model: simplicity is the tiebreaker.
What is true: if the resource is released by another master, waiting deadlocks. Not slowly — structurally, with every component behaving correctly.
Observable evidence: one master holding CYC_O while the master that would unblock it is starved of grants.
Correct model: ask Section 1's question first. Simplicity only decides the cases where both are safe.
"A busy resource should return ERR so software knows something is wrong."
Wrong mental model: any non-success is worth reporting as a fault.
What is true: nothing is wrong. The request is valid and will succeed shortly. ERR says something about the request; a full queue says something about the target.
Concrete bug: software treating a transient shortage as a device failure and disabling the peripheral.
Correct model: ERR for never, RTY for not now — and document it, per RULE 2.15.
"More retries with a shorter delay will finish sooner."
Wrong mental model: attempts are free.
What is true: each attempt is a full transfer. Four attempts produced one success here; a much shorter delay would produce more attempts and more bus traffic — the thing deferring was supposed to save.
Observable evidence: attempts per successful operation.
Correct model: the delay trades latency against traffic, and both sides of that trade are real.
9. Interview Reasoning
I would ask one question first, because it can settle the matter outright: can the queue drain while this master is holding the bus?
If it cannot — if the consumer is another master that needs a grant — then waiting deadlocks. The master holds CYC_O, the arbiter honours the open cycle, the drainer never runs, space never appears. Every component behaves correctly and the system is dead. That is not tunable; no wait-state budget fixes an unsatisfiable wait.
If it can drain independently, both work, and then it is a measured trade. I built both against the same full queue: the waiting slave completed in 11 clocks and held the bus for all 11. The deferring slave completed in 13 and held the bus for 4, across four attempts.
So deferring cost two clocks of latency and saved seven clocks of occupancy. Whether that is a good trade depends entirely on whether anything else wanted those seven clocks. In a single-master system it buys nothing and the retry machinery — counter, latched request, delay, exhaustion path — is pure complexity.
The asymmetry I would raise in review is about verifiability. A bounded retry design is checkable: attempts are bounded, exhaustion is exact, the request is guaranteed to reach an outcome. A waiting slave cannot promise termination at all — its liveness depends on arbitration and on who drains the queue, facts that live outside the module. A reviewer reading the RTL cannot see whether it is safe.
And both failure modes are total. A deadlocked wait and a livelocked retry both stop the system; they differ only in whether the bus looks idle or busy while it happens.
10. Understanding Check
11. What's Next
The choice is now a decision procedure rather than a preference, and the cost of each side is measured.
What Chapter 11.2 defined and did not examine is the restart itself. RETRY_DELAY was declared, the gap appeared in a waveform, and nothing checked whether the parameter means what the RTL does — or what happens if a target commits before deferring.
Exactly how does a second attempt get onto the bus, and what makes it safe to issue at all?
Chapter 11.4 — Transaction Restart measures the retry gap across the parameter range, shows why one non-presented clock is structurally irreducible, and builds the target whose duplicate commits make retrying unsafe. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
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.
- Related topic
Intermediate Questions
Three flawed slaves, three waveform predictions, and the measurement that shows one write committing four times while every Wishbone rule is obeyed.
- 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.
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.
