Skip to content
VLSI Mentor

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.

WAITRTY
Bus held while unavailableyes, continuouslyno
Transfers generatedoneone per attempt
State required in the masternonecounter, latched request, delay
Client semanticssimple: one transfer, one answertwo-level: attempts and requests
Failure mode if misuseddeadlocklivelock
Other mastersblockedcan 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

Reading 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 cycles
Ten clock cycles comparing two masters against the same full queue. The waiting master asserts cycle and strobe continuously from cycle one through cycle nine with no termination arriving, so the bus is held for the whole interval. The retrying master asserts cycle and strobe at cycles one, four and seven only, receiving a retry each time, and leaves the bus unasserted in between. The total number of clocks with the bus held is much larger for the waiting master.both present; queue is fullboth present; queue is fullattempt 2 — bus was free betweenattempt 2 — bus was freebetweenwaiting master still holdingwaiting master stillholdingCLK_IWAIT CYC+STBWAIT ACK_IRTY CYC+STBRTY RTY_Ibus freet0t1t2t3t4t5t6t7t8t9
Figure 1 — the same full queue answered two ways. The waiting master holds one transfer open; the retrying master makes four separate attempts and leaves the bus free between them. Traced from the simulation in Section 5.

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === 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          1

Both 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.
endmodule

W5 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

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.