Skip to content
VLSI Mentor

Wishbone · Module 11

Retry Concepts

A retry re-issues the operation, so the master must remember what it accepted. Measured: a master reading its client live wrote 0xBBBB0002 to word 4 where the client had asked for word 9.

Chapter 11.1 established the class and left the master doing nothing about it — classify the deferral, report it upward, stop.

What does it actually take to attempt the operation again?

1. An Attempt Ends. A Request Might Not.

The single most important consequence of RTY being a termination:

The attempt is over. If the operation happens again, it happens in a different transfer.

Not a continuation, not a resumption, not the same transfer with a longer life. A second attempt has its own presentation, its own qualification under RULE 3.35, and its own termination.

This is why a master cannot "hold STB_O until the slave is ready". The slave, honouring RULE 3.50, negates its termination when STB_I negates and asserts it again while the request is presented — so holding produces a stable stream of RTY_O and no progress at all. Chapter 11.4 shows why at least one non-presented clock is structurally required.

And it is why the two-level state model from Chapter 11.1 §3 is not bookkeeping. In Section 6's measurement one client request produced three bus attempts and one completion. A system that counts only one of those numbers cannot see retry traffic; a system that conflates them will report three outcomes for one operation.

2. What Must Survive a Retry

The operation's identity. Address, direction, byte lanes, and — for a write — the payload. A retry that changes any of them is not a retry; it is a different operation wearing the same name.

Which means the master must have latched them. Chapter 9.3 established this for a single transfer under wait states: the request's identity is fixed at the presenting edge and the master must have nothing left that can move. Retry widens the window enormously.

window in which the client could interfere
zero-wait transfernone — presented and terminated in one clock
transfer with wait statesthe clocks the transfer is outstanding — Chapter 9.3
a retried requestevery clock from acceptance to final completion, including the gaps between attempts

And the gaps are the part no Wishbone rule covers. RULE 3.60 governs the master's outputs while a transfer is outstanding. Between attempts nothing is presented, so the rule has nothing to say — and a master that reconstructs its request from live inputs at the start of each attempt is not violating anything on the bus. It is simply issuing the wrong operation.

Section 7 measures exactly that, and it is the reason request identity is a module-wide invariant here rather than one chapter's example.

3. MAX_RETRIES, Defined Before It Is Used

Modules 9 and 10 both measured what an undefined latency parameter costs — an off-by-one that became a permanent hang at zero, and a drain limit whose interaction was only visible when it was exceeded. The same discipline applies to counting attempts, where the ambiguity is worse because the words themselves are contested.

The terminology, fixed for the whole module:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  attempt 0     the ORIGINAL transfer          (not a retry)
  attempt 1     the first RE-ISSUED transfer   (retry 1)
  attempt N     the Nth re-issued transfer     (retry N)

MAX_RETRIES = N permits at most N re-issues, so the maximum number of bus attempts is N + 1.

attemptsretriesmeaning
MAX_RETRIES = 010classify but never re-issue — a legal, useful policy
MAX_RETRIES = 121one second chance
MAX_RETRIES = 343

Zero is not a disabled feature. It is "report the deferral and let a higher layer decide", which is one of the four legitimate policies Chapter 11.1 §2 listed. Section 6 measures it, because a parameter whose zero case is untested is where Module 9's worst defect lived.

And there is no PARAM - 1 anywhere in the RTL. The counter counts re-issues upward from zero and the exhaustion test is retry_q == MAX_RETRIES. Module 9 measured an unsigned 0 - 1 becoming 4294967295 and hanging the bus; that pattern is avoided by construction rather than by care.

4. RTL — A Master That Remembers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_retry_master — a master that re-issues a deferred request, bounded.
//
// THE CLIENT CONTRACT, extending Chapter 10.1's:
//
//   done_o    one clock per accepted client request. Means THE CLIENT
//             REQUEST IS FINISHED — not that any one bus attempt ended.
//             A request that took four attempts produces one done_o.
//
//   ok_o      valid with done_o. The request succeeded (an attempt was
//             terminated with ACK_I).
//   err_o     valid with done_o. The request failed (ERR_I).
//   exh_o     valid with done_o. The retry policy ran out: every allowed
//             attempt was terminated with RTY_I.
//
//   rdat_o    valid ONLY with done_o && ok_o on a read.
//
// EXACTLY ONE of ok_o / err_o / exh_o accompanies each done_o.
//
// NOTE WHAT exh_o IS NOT. It is not a Wishbone termination class. No slave
// ever asserted it; it is this master reporting that ITS OWN policy is
// spent. Chapter 11.5 separates it from ERR in debugging.
//
// ── MAX_RETRIES SEMANTICS ────────────────────────────────────────────────
// Stated once and measured in Chapter 11.2 Section 6 rather than assumed:
//
//   attempt 0        the original transfer
//   attempt 1        the first RE-issued transfer  (retry 1)
//   attempt N        the Nth re-issued transfer    (retry N)
//
//   MAX_RETRIES = N  ->  at most N RE-ISSUES are permitted, so the
//                        maximum number of bus attempts is N + 1.
//
//   MAX_RETRIES = 0  ->  no re-issues. One attempt. An RTY on it is
//                        reported immediately as exhaustion. This is a
//                        legal, useful configuration — it is "classify
//                        but do not retry" — and Section 6 measures it.
//   MAX_RETRIES = 1  ->  2 attempts maximum.
//   MAX_RETRIES = 3  ->  4 attempts maximum.
//
// retry_q counts RE-ISSUES, so it runs 0..MAX_RETRIES and the exhaustion
// test is `retry_q == MAX_RETRIES` on an RTY. There is no PARAM-1
// expression anywhere: Module 9 measured an unsigned `0 - 1` turning an
// off-by-one into a permanent hang, and this module does not repeat it.
//
// ── RETRY_DELAY SEMANTICS ────────────────────────────────────────────────
// Measured in Chapter 11.4 Section 6, and stated here as measured rather
// than as intended:
//
//   non-presented clocks between two attempts  =  RETRY_DELAY + 1
//
//   RETRY_DELAY = 0  ->  1 non-presented clock
//   RETRY_DELAY = 1  ->  2 non-presented clocks
//   RETRY_DELAY = N  ->  N + 1 non-presented clocks
//
// THE +1 IS NOT AN IMPLEMENTATION ARTEFACT AND CANNOT BE REMOVED. It is
// what makes the second attempt a SEPARATE TRANSFER.
//
// If STB_O never negated between the two presentations, the slave would
// see one continuously-presented transfer rather than two. RULE 3.50
// requires the slave to negate its termination in response to the
// negation of STB_I; with no negation there is no second qualification and
// no second termination — just one request being held while the slave goes
// on asserting RTY_O. That is the "retry as a wait state" mistake, in
// hardware.
//
// So one non-presented clock is the irreducible cost of a retry, exactly
// as one turnaround clock is the irreducible cost of a second bus cycle in
// Chapter 8.6. RETRY_DELAY adds to that floor; it cannot go below it.
//
// Wishbone imposes no retry delay at all. Both parameters are LOCAL POLICY.
//
// ── REQUEST IDENTITY ─────────────────────────────────────────────────────
// Every attempt presents the metadata latched when the CLIENT REQUEST was
// accepted. The client's live inputs are never re-read. This is the
// invariant Chapter 11.2 Section 5 measures against a master that gets it
// wrong, and it is what makes "retry" mean "the same operation again".
// ─────────────────────────────────────────────────────────────────────────
module wb_retry_master #(
  parameter int unsigned AW          = 30,
  parameter int unsigned DW          = 32,
  parameter int unsigned MAX_RETRIES = 2,
  parameter int unsigned RETRY_DELAY = 0
) (
  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,
  output logic            ok_o,
  output logic            err_o,
  output logic            exh_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,

  // ── observation only ──
  output logic [7:0]      attempt_o,      // 0 = original, 1.. = retries
  output logic [7:0]      retry_o,        // re-issues so far
  output logic            waiting_o       // in the retry gap
);
  typedef enum logic [1:0] { S_IDLE, S_XFER, S_GAP } state_e;
  state_e state_q;

  // ── THE LATCHED REQUEST. Written once, at client acceptance, and read
  //    by every attempt. Nothing else writes these.
  logic [AW-1:0]   adr_q;
  logic [DW-1:0]   dat_q;
  logic [DW/8-1:0] sel_q;
  logic            we_q;

  logic [7:0]  retry_q;
  logic [15:0] gap_q;
  logic        terminated;

  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);
  assign attempt_o = retry_q;
  assign retry_o   = retry_q;
  assign waiting_o = (state_q == S_GAP);

  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;
      retry_q <= '0; gap_q <= '0;
      done_o <= 1'b0; ok_o <= 1'b0; err_o <= 1'b0; exh_o <= 1'b0;
      rdat_o <= '0;
    end else begin
      done_o <= 1'b0; ok_o <= 1'b0; err_o <= 1'b0; exh_o <= 1'b0;

      case (state_q)
        S_IDLE: if (req_i) begin
          // ── ACCEPTANCE. The only clock at which client inputs are read.
          adr_q   <= req_adr_i;
          dat_q   <= req_dat_i;
          sel_q   <= req_sel_i;
          we_q    <= req_we_i;
          retry_q <= '0;
          state_q <= S_XFER;
        end

        S_XFER: if (terminated) begin
          if (ack_i) begin
            if (!we_q) rdat_o <= dat_i;    // capture on ACK only
            ok_o    <= 1'b1;
            done_o  <= 1'b1;
            state_q <= S_IDLE;
          end else if (err_i) begin
            // An error is final for this master: retrying an ERR either
            // loops forever or converts a clean failure into a hang.
            err_o   <= 1'b1;
            done_o  <= 1'b1;
            state_q <= S_IDLE;
          end else begin
            // RTY. Either the policy has another re-issue left, or it does not.
            if (retry_q == 8'(MAX_RETRIES)) begin
              exh_o   <= 1'b1;
              done_o  <= 1'b1;
              state_q <= S_IDLE;
            end else begin
              retry_q <= retry_q + 8'd1;
              gap_q   <= 16'(RETRY_DELAY);
              state_q <= S_GAP;
            end
          end
        end

        // Nothing is presented here. The latched request is untouched, so
        // the next attempt carries the same identity as the first.
        S_GAP: begin
          if (gap_q == 16'd0) state_q <= S_XFER;
          else                gap_q   <= gap_q - 16'd1;
        end

        default: state_q <= S_IDLE;
      endcase
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_retry_master_live — THE BUG, isolated. NOT A REFERENCE DESIGN.
//
// Identical to wb_retry_master except that the Wishbone outputs are driven
// from the CLIENT'S LIVE INPUTS instead of from the registers latched at
// acceptance:
//
//     assign adr_o = adr_q;        becomes      assign adr_o = req_adr_i;
//
// The retry counting, the delay, the classification and the completion
// reporting are all unchanged and all correct. The only defect is that the
// master does not remember what it accepted.
//
// WHY IT IS NOT AN OBVIOUS MISTAKE. Against a slave that never defers, this
// master is indistinguishable from the correct one: the transfer is
// presented and terminated before the client has any opportunity to change
// anything. Chapter 9.3 measured the same structural precondition for a
// single transfer under wait states — here the window is wider still,
// because it spans the gap BETWEEN attempts as well.
//
// WHAT IS STILL CORRECT. Everything on the bus, within each attempt. Each
// presentation is properly qualified, each termination is taken, the bus is
// released between attempts. A protocol checker watching its pins passes it.
//
// It also violates RULE 3.60 within an attempt if the client moves while
// the transfer is outstanding — but against a zero-wait slave that window
// is empty, and the damage measured in Chapter 11.2 happens entirely in the
// gap between attempts, where no Wishbone rule applies at all.
// ─────────────────────────────────────────────────────────────────────────
module wb_retry_master_live #(
  parameter int unsigned AW          = 30,
  parameter int unsigned DW          = 32,
  parameter int unsigned MAX_RETRIES = 2,
  parameter int unsigned RETRY_DELAY = 0
) (
  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            ok_o,
  output logic            err_o,
  output logic            exh_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,
  output logic [7:0]      attempt_o,
  output logic [7:0]      retry_o,
  output logic            waiting_o
);
  typedef enum logic [1:0] { S_IDLE, S_XFER, S_GAP } state_e;
  state_e state_q;

  logic [7:0]  retry_q;
  logic [15:0] gap_q;
  logic        terminated;

  assign cyc_o = (state_q == S_XFER);
  assign stb_o = (state_q == S_XFER);

  // ── THE BUG. Four wires from the client straight to the bus. There is
  //    nothing to latch because nothing is remembered.
  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 attempt_o = retry_q;
  assign retry_o   = retry_q;
  assign waiting_o = (state_q == S_GAP);

  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; retry_q <= '0; gap_q <= '0;
      done_o <= 1'b0; ok_o <= 1'b0; err_o <= 1'b0; exh_o <= 1'b0; rdat_o <= '0;
    end else begin
      done_o <= 1'b0; ok_o <= 1'b0; err_o <= 1'b0; exh_o <= 1'b0;
      case (state_q)
        S_IDLE: if (req_i) begin
          retry_q <= '0;
          state_q <= S_XFER;      // nothing latched
        end
        S_XFER: if (terminated) begin
          if (ack_i) begin
            if (!req_we_i) rdat_o <= dat_i;
            ok_o <= 1'b1; done_o <= 1'b1; state_q <= S_IDLE;
          end else if (err_i) begin
            err_o <= 1'b1; done_o <= 1'b1; state_q <= S_IDLE;
          end else begin
            if (retry_q == 8'(MAX_RETRIES)) begin
              exh_o <= 1'b1; done_o <= 1'b1; state_q <= S_IDLE;
            end else begin
              retry_q <= retry_q + 8'd1;
              gap_q   <= 16'(RETRY_DELAY);
              state_q <= S_GAP;
            end
          end
        end
        S_GAP: begin
          if (gap_q == 16'd0) state_q <= S_XFER;
          else                gap_q   <= gap_q - 16'd1;
        end
        default: state_q <= S_IDLE;
      endcase
    end
  end
endmodule

Reading the pair

The correct master's guarantee is an absence, exactly as in Chapter 9.3. S_IDLE is the only state that writes adr_q, dat_q, sel_q and we_q. Nothing in S_XFER or S_GAP touches them, so there is no path by which a second attempt could present something different from the first.

S_GAP exists for two reasons and only one of them is the delay. It inserts RETRY_DELAY clocks of waiting — and it also guarantees that STB_O is negated between attempts, which is what makes the second attempt a second transfer. Chapter 11.4 measures why that separation cannot be skipped.

err_i is final for this master, and that is a policy. Retrying an error either loops forever or converts a clean, reportable failure into a hang — the reasoning Chapter 10.1 §2 set out. A different master could retry errors and be conformant; what it could not do is leave the choice unstated, which RULE 2.15 forbids.

exh_o is not a termination class. No slave ever asserted it. It is this master reporting that its own policy is spent, and Chapter 11.5 spends its debugging section separating it from ERR — they mean very different things and look identical to software that only checks for failure.

wb_retry_master_live differs by four assign statements. The state machine, the counting, the delay and the reporting are byte-for-byte the same. What it lacks is the latches — and with nothing latched, each attempt reconstructs its request from whatever the client happens to be driving.

Why that is not obviously wrong. Against a slave that never defers, the two masters are indistinguishable: the transfer is presented and terminated before anything can change. The defect needs a deferral to exist, which is the same structural precondition that hid the defects in Chapters 9.2, 9.3 and 10.2.

Timing. Both masters present CYC_O and STB_O together and release both at every termination. Reset is active high and synchronous.

Simplifications. One outstanding request; no pipelining. RTY_I is the only class that triggers a re-issue. There is no backoff schedule — RETRY_DELAY is a constant, because a deterministic delay is measurable and an exponential one is not teachable in a cycle table.

5. Waveform — One Request, Two Transfers

Attempt 0, gap, attempt 1

10 cycles
Ten clock cycles showing one client request served by two bus transfers. At cycle two the cycle and strobe signals are asserted with address word nine and the slave answers with a retry, ending the first attempt. At cycles three and four nothing is presented: the strobe is low and the master's gap indicator is high, with the retry counter reading one. At cycle five the cycle and strobe signals are asserted again with the same address word nine and the slave answers with an acknowledge. At cycle six the master reports done and ok for one clock and the slave's accept counter reads one.attempt 0 — RTY ends itattempt 0 — RTY ends itnothing presented: the gapnothing presented: the gapattempt 1 — same address, ACKattempt 1 — same address,ACKCLK_ICYC+STBADR_O--------0x9--------0x9----------------RTY_IACK_Iretry0001111111gapdone+okaccepts0000001111t0t1t2t3t4t5t6t7t8t9
Figure 1 — one client write to word 9 against a slave that defers its first attempt. MAX_RETRIES = 3, RETRY_DELAY = 1. Traced from simulation.

Count the presentations: two. Cycle 2 and cycle 5. Two separate transfers, each with its own qualification and its own termination.

Count the client completions: one. Cycle 6. The client asked once and was answered once, and never learned that the bus had been used twice.

Cycles 3 and 4 are the gap, and the figure exists to make them visible. CYC+STB is low; nothing is presented; the slave sees no transfer. RETRY_DELAY = 1 produced two non-presented clocks, and Chapter 11.4 explains why one of them is structural rather than configured.

ADR_O reads 0x9 in both attempts. That is the request-identity invariant, visible: the second attempt presents the address latched at acceptance, not whatever the client is driving now. Section 7 measures a master for which this row would read 0x9 then 0x4.

And the accepts row moves once. The slave recorded one command for one client request, at cycle 6 — the deferred attempt at cycle 2 enqueued nothing, which is what makes re-issuing safe. Chapter 11.4 measures a target that breaks this.

What the figure would look like if the master held STB_O instead. CYC+STB would stay high from cycle 2, RTY_I would stay asserted with it, and nothing would ever change. One transfer, forever — which is the "retry is a wait state" misconception rendered as hardware.

6. Simulation — SIM C and SIM D

SIM C — a slave that defers exactly twice, then accepts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM C - a retry that eventually succeeds ===
    slave defers 2 attempts then accepts; MAX_RETRIES = 3

    bus attempts presented      3
    RTY terminations            2
    ACK terminations            1
    master retry count          2
    client completions          1  (ok=1 err=0 exhausted=0)
    slave accepts               1

Three bus attempts, one client completion. That is the two-level model as data: presented = 3, RTY = 2, ACK = 1, and client completions = 1 reported as ok.

The retry counter reads 2 — two re-issues, matching the two deferrals. attempts = retries + 1, which is the arithmetic the next table proves across the range.

And slave accepts = 1. One client write produced one committed command, not three. The two deferred attempts committed nothing, which is the property that makes this slave safe to retry against and which Chapter 11.4 measures against a target that breaks it.

SIM D — a slave that never accepts, swept across MAX_RETRIES.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM D - retry exhaustion, swept ===
    slave never accepts. attempts = MAX_RETRIES + 1.

    MAX_RETRIES   attempts   RTY   retries   ok  err  exhausted
         0            1        1      0       0    0      1
         1            2        2      1       0    0      1
         3            4        4      3       0    0      1

attempts = MAX_RETRIES + 1 on every row, including both endpoints: 0 → 1, 1 → 2, 3 → 4. The definition in Section 3 is now a measurement.

MAX_RETRIES = 0 gives one attempt and reports exhaustion. No re-issue happened, and the client was told the policy was spent rather than that the operation failed. That is a complete, legal retry policy — the master classified a deferral and declined to act on it.

All three terminate. None of them loops: exhausted = 1 in every row, and the bounded design cannot do otherwise. An unbounded master against this slave would still be running — which is why bounding is treated here as a correctness requirement rather than a refinement.

And ok = 0, err = 0 throughout. Exhaustion is neither success nor error. A client contract that offered only those two would have to lie about one of these runs, and Chapter 11.5 shows what that costs in a debugging session.

7. Simulation — SIM E: The Client Moves

The experiment. The client asks to write 0xAAAA0001 to word 9. The slave defers the first attempt. While the master is waiting to re-issue, the client's inputs change to word 4 and 0xBBBB0002 — which is what a client does the instant it believes it has handed off a request.

The slave records the address and payload it actually accepted, so the question "which operation finally happened" has a measured answer rather than an inferred one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM E - the client changes its request between attempts ===
    client asks to write 0xAAAA0001 to word 9, then switches its
    inputs to word 4 / 0xBBBB0002 while attempt 0 is being retried.

    master                  attempts  accepted offset  accepted data
    wb_retry_master             2           9            0xaaaa0001
    wb_retry_master_live        2           4            0xbbbb0002

Both masters took two attempts and both reported success. From the client's side, and from a protocol checker's, the two runs are equally healthy.

The correct master committed offset 9 with 0xAAAA0001 — the operation its client asked for.

The live master committed offset 4 with 0xBBBB0002. A write intended for the command register landed on the ID register with different data, and the client was told it succeeded.

Note what makes this worse than Chapter 9.3's corruption. There, a master reading its client live during wait states returned the wrong register's value on a read. Here the wrong address is written, and the damage is to device state rather than to a returned value — a read can be repeated, a write cannot be unwound.

And nothing on the bus is malformed. Each attempt was properly presented, properly qualified and properly terminated. RULE 3.60 was not violated either, because within each attempt the metadata was stable — the change happened in the gap, where no rule applies. A protocol checker passes this rig.

The evidence that does find it is a comparison between what the client asked for and what the target recorded. That needs instrumentation at both ends, which is why the slave in this module records its accepted address and payload at all.

The fix is four registers, and the guarantee they provide is an absence: with the request latched at acceptance, there is no path by which a later attempt could differ from the first.

8. Failure Modes and Discriminating Evidence

Symptom: an operation lands on the wrong register, intermittently.

Candidate causes. A master reconstructing its request from live client inputs across a retry.

Discriminating evidence. Compare the address the client requested against the address the target recorded. A mismatch with a healthy bus trace is conclusive. The bus alone will not show it — every attempt is individually well-formed.

Why intermittent: the defect needs a deferral. Against a target that never returns RTY, the broken master is correct.

Symptom: software reports one failure but the bus shows many transfers.

Candidate causes. Normal bounded retry. This is not a bug.

Discriminating evidence. attempts against client completions. Several attempts and one completion is the retry machine working. Several completions for one client request would be the bug — a master reporting each attempt upward.

Symptom: a retrying master never completes.

Candidate causes. Unbounded retry against a resource that never clears.

Discriminating evidence. A rising attempt count with no change in the target's ready condition. Chapter 11.5 builds the debugging procedure; the immediate check is whether MAX_RETRIES is finite.

Symptom: a master hangs with STB_O asserted and RTY_I asserted alongside it.

Candidate causes. The master is treating RTY as a wait state — holding the request instead of releasing it.

Discriminating evidence. RTY_I asserted on consecutive clocks with STB_O never negating. The slave is behaving correctly under RULE 3.50; the master never ends the attempt.

Likely RTL location: a termination expression that omits rty_i.

9. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Properties for a retrying master. The split is stark here: exactly one
// is the specification's, because Wishbone says nothing about retry at all.
//
// 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 Sections 6 and 7 come from procedural checks, which Icarus
// does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_retry_master_props #(
  parameter int unsigned AW          = 30,
  parameter int unsigned DW          = 32,
  parameter int unsigned MAX_RETRIES = 2
) (
  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            req_i, busy_o,
  input logic            done_o, ok_o, err_rep_o, exh_o,
  input logic [7:0]      retry_o
);
  default clocking cb @(posedge clk_i); endclocking
  default disable iff (rst_i);

  logic terminated, accepted;
  assign terminated = cyc_o && stb_o && (ack_i || err_i || rty_i);
  assign accepted   = req_i && !busy_o;

  // M1 — SPECIFICATION (RULE 3.60). Within a presented attempt the
  //      qualified outputs are stable. This is the only property here the
  //      specification requires, and it says nothing about the gaps.
  M1_stable_in_attempt: assert property (
    (cyc_o && stb_o && !(ack_i||err_i||rty_i)) |=>
      ($stable(adr_o) && $stable(we_o) && $stable(sel_o))
  );

  // M2 — LOCAL POLICY, and the invariant this chapter is built on. The
  //      request presented by every attempt is the one latched at
  //      acceptance. Written as: the metadata does not change between
  //      acceptance and completion, INCLUDING across the gaps — which is
  //      strictly stronger than M1 and is not a Wishbone obligation.
  M2_identity_across_retries: assert property (
    (busy_o && !done_o) |=> ($stable(adr_o) && $stable(we_o) && $stable(sel_o))
  );

  // M3 — LOCAL POLICY. RTY ends the attempt: the master releases rather
  //      than holding the request. Without this, retry degenerates into a
  //      wait state and no progress is possible.
  M3_rty_releases: assert property (
    (cyc_o && stb_o && rty_i) |=> !stb_o
  );

  // M4 — LOCAL POLICY. The retry count never exceeds the configured
  //      maximum, so the bounded design cannot livelock.
  M4_bounded: assert property ( retry_o <= 8'(MAX_RETRIES) );

  // M5 — LOCAL POLICY. Exhaustion is reported only at the configured
  //      limit, so MAX_RETRIES means what Section 3 says it means.
  M5_exact_exhaustion: assert property (
    exh_o |-> $past(retry_o == 8'(MAX_RETRIES) && rty_i)
  );

  // M6 — LOCAL CONTRACT. One accepted client request produces exactly one
  //      completion, however many bus attempts occurred, and that
  //      completion carries exactly one outcome.
  M6_one_completion: assert property ( done_o |-> $onehot({ok_o, err_rep_o, exh_o}) );

  // M7 — LOCAL CONTRACT. Success is never reported for a deferral.
  M7_no_success_on_rty: assert property ( ok_o |-> $past(ack_i) );
endmodule

M2 is the chapter's property and it is emphatically not a Wishbone rule. RULE 3.60 governs the master's outputs while a transfer is outstanding; M2 extends that across the gaps between attempts, where the specification has nothing to say. It is the formal statement of "a retry re-issues the same operation", and it is the property wb_retry_master_live fails.

M1 and M2 differ in exactly the window that matters. A master can satisfy M1 perfectly and fail M2 — which is what the broken master does, and why a checker built only from Wishbone rules passes it.

M4 and M5 are the liveness pair. M4 says the count is bounded; M5 says the bound is the one that was configured. Together they make "this request will terminate" a checkable claim rather than an assumption about the target's behaviour.

10. Common Mistakes

"A retry continues the same transfer."

Wrong mental model: the operation is suspended and resumed.

What is true: the attempt terminated. A second attempt is a second transfer with its own presentation and its own termination — two presentations in Figure 1, one client completion.

Concrete bug: a master that holds STB_O after an RTY, producing a stable stream of RTY_O from a slave that is behaving perfectly.

Observable evidence: RTY_I asserted on consecutive clocks with STB_O never negating.

Correct model: release, wait, present again. Chapter 11.4 shows the separation is structurally required.

"The master can re-read the client when it retries."

Wrong mental model: the client's request is still sitting there.

What is true: the client was told nothing and has moved on. Measured: the live master wrote 0xBBBB0002 to word 4 where the client had asked to write 0xAAAA0001 to word 9 — and reported success.

Observable evidence: the target's recorded address against the client's requested address. Nothing on the bus.

Correct model: latch at acceptance. The guarantee is then the absence of any path by which the value could change.

"MAX_RETRIES = 3 means three attempts."

Wrong mental model: the parameter counts attempts.

What is true: it counts re-issues, so three retries plus the original is four attempts — measured. Either convention is defensible; only one can be implemented, and leaving it implicit is how a test and an RTL end up disagreeing.

Correct model: state it, then measure it. attempts = MAX_RETRIES + 1.

"MAX_RETRIES = 0 disables retry, so it is a degenerate setting."

Wrong mental model: zero means the feature is off.

What is true: it means one attempt, and a deferral reported as exhaustion — which is a complete and useful policy: classify the deferral and let a higher layer decide. Measured: 1 attempt, 1 RTY, exhausted.

Why it matters that it was measured: zero is where counter arithmetic breaks, and Module 9 measured an unsigned 0 - 1 turning an off-by-one into a permanent hang.

"Several bus attempts mean several failures to report."

Wrong mental model: the client should hear about each attempt.

What is true: one accepted request produces one completion. Measured: three attempts, one done_o.

Concrete bug: a master that pulses done_o per attempt, so a client counting completions sees three operations where it issued one.

Correct model: attempts are the master's business; the client hears the outcome once.

11. Interview Reasoning

The operation's identity — address, direction, byte lanes and write payload — and it is harder because the window is much wider than the one Wishbone governs.

The rule that does exist is RULE 3.60: the master's qualified outputs are stable while a transfer is outstanding. That covers each attempt individually and is easy to satisfy.

What it does not cover is the gap between attempts. Nothing is presented there, so no Wishbone obligation applies — and a master that reconstructs its request from live client inputs at the start of each attempt violates no rule on the bus. It simply issues a different operation.

I measured it. A client asked to write 0xAAAA0001 to word 9; the slave deferred; the client's inputs then moved to word 4 and 0xBBBB0002, which is what a client does the moment it thinks it has handed off. The correct master committed word 9 with the original data. The broken one committed word 4 with the new data — and reported success.

Both took two attempts, both looked healthy, and a protocol checker passes both. The evidence that separates them is a comparison between what the client requested and what the target recorded, which needs instrumentation at both ends.

Why this is worse than the equivalent wait-state bug from Chapter 9.3: there, the corruption produced the wrong returned value on a read. Here a write lands on the wrong register, and device state is not something you can re-read to check.

The fix is four registers loaded at acceptance, and what makes it robust is that the guarantee becomes an absence — there is no path by which a later attempt could differ, rather than a discipline that has to be maintained.

12. Understanding Check

13. What's Next

The machine is built: identity latched at acceptance, attempts counted, exhaustion bounded and reported as its own outcome.

What has not been asked is whether a target should be deferring at all. The slave in this chapter returns RTY on a full queue because it was written to — and the same condition could just as legitimately have been answered with wait states.

When is a deferral the right answer, and what does it cost compared with simply waiting?

Chapter 11.3 — Temporary Resource Unavailability builds both slaves against the same resource condition and measures bus occupancy, attempt count and completion latency for each. 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.