Wishbone · Module 11
Transaction Restart
A retry needs at least one non-presented clock or it is not a second transfer at all. Measured across RETRY_DELAY, and against a target whose early commit turned one write into three.
Chapter 11.3 settled whether to defer. Chapter 11.2 declared a RETRY_DELAY parameter, showed a gap in a waveform, and never checked that the two agreed.
Exactly how does a second attempt reach the bus, and what makes it safe to issue at all?
1. Why the Gap Is Structural
Suppose a master could re-present immediately, with STB_O never negating between attempts. What would the slave see?
One continuously presented transfer. CYC_I and STB_I asserted from the first clock to the last, with no interruption.
And the slave's behaviour follows from two rules it is already obeying. RULE 3.35 generates the termination from the AND of CYC_I and STB_I — still true, so it asserts RTY_O. RULE 3.50 requires the termination to be asserted and negated in response to the assertion and negation of STB_I — and STB_I never negates, so RTY_O never negates either.
The result is a stable state, not a sequence of attempts. STB_O high, RTY_O high, forever. Nothing counts, nothing progresses, and no component is misbehaving.
The non-presented clock is what makes the second qualification a second one. Without it there is one transfer that is being refused continuously, not a series of attempts.
This is the same structure Chapter 8.6 measured for bus cycles, where two cycles require CYC_O to be low at some rising edge in between and the turnaround clock is therefore irreducible. Here the same argument applies to STB_O and to attempts.
So RETRY_DELAY adds to a floor of one; it cannot reach zero. Section 6 measures the floor and the sum.
2. RETRY_DELAY, Defined As Measured
Modules 9 and 10 both established that a latency parameter's meaning must be written down and then checked. This one was written down in Chapter 11.2's RTL and the first measurement disagreed with it — the header said RETRY_DELAY = N gives N non-presented clocks, and the trace showed N + 1.
The corrected definition, which Section 6 measures across the range:
Non-presented clocks between two attempts =
RETRY_DELAY+ 1.
RETRY_DELAY | non-presented clocks | composition |
|---|---|---|
| 0 | 1 | the structural release clock alone |
| 1 | 2 | release + 1 delay clock |
| 3 | 4 | release + 3 delay clocks |
RETRY_DELAY = 0 is the fastest legal retry, not a disabled delay. It re-presents at the earliest clock at which a second transfer can exist.
Wishbone imposes no retry delay at all. No minimum gap, no backoff, no maximum rate — the specification says only that when and how a cycle is retried is defined by the IP core supplier. Everything in this section is local policy, and the floor is arithmetic rather than regulation.
3. What Must Be True Within and Between Attempts
Two different obligations, and only one of them is Wishbone's.
Within a presented attempt: RULE 3.60. ADR_O, DAT_O(), SEL_O(), WE_O and the tags are qualified by STB_O and must be stable while the transfer is outstanding. This is ordinary, and every attempt is an ordinary transfer.
Between attempts: nothing. No transfer is presented, so no rule applies. The specification has no opinion about what a master does in the gap — which is precisely the window Chapter 11.2 §7 measured a master corrupting its own request in.
So the module-wide invariant is stronger than the rule, and is labelled accordingly:
| Scope | Source | |
|---|---|---|
| metadata stable while outstanding | within an attempt | RULE 3.60 |
| every attempt presents the accepted request | acceptance → completion | LOCAL POLICY |
4. Waveform — The Gap, at Two Settings
One clock is the floor
10 cyclesCycle 3 is low in both rows, and that clock is the subject of the figure. With RETRY_DELAY = 0 it is the only gap clock; with RETRY_DELAY = 1 it is the first of two. No setting removes it.
The D0 rows show the fastest legal retry. Attempt 0 at cycle 2, release at cycle 3, attempt 1 at cycle 4. Two presentations, one clock apart, and the slave sees two separate qualifications.
The D1 rows show one added delay clock. Attempt 1 arrives at cycle 5 instead of 4.
Both attempts in both rows present the same address, latched at acceptance — not shown here to keep the figure to seven rows, and measured in Chapter 11.2 §7.
What the figure would look like with no gap at all. STB_O high from cycle 2 onwards and RTY_I high beside it, unchanging. One transfer being refused continuously — and the reason Section 1 calls the gap structural rather than configured.
5. RTL — The Target That Commits Too Early
Section 7's experiment needs a target that breaks the side-effect contract, and the mistake it models is a realistic one: adding deferral to a working slave and leaving the commit block alone.
// wb_defer_slave — defers a fixed number of attempts, then accepts.
//
// A deterministic stand-in for a resource that clears on its own after a
// known number of tries. Nothing here is random: the Nth attempt succeeds,
// every time, in every run.
//
// attempt 1 .. DEFER_COUNT -> RTY_O not ready yet
// attempt DEFER_COUNT+1 .. -> ACK_O accepted, and recorded
// offset not implemented -> ERR_O never
//
// DEFER_COUNT = 0 accepts immediately and is the no-deferral control.
//
// THE RECORDING IS THE POINT. On acceptance the slave latches the address
// and data it was actually presented, so a testbench can prove WHICH
// request finally committed. Chapter 11.2 uses that to show a master
// retrying something other than what its client asked for.
//
// SIDE-EFFECT CONTRACT: a deferred attempt records nothing and counts
// nothing. Only an accepted attempt does.
// ─────────────────────────────────────────────────────────────────────────
module wb_defer_slave #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32,
parameter int unsigned DEFER_COUNT = 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,
output logic rty_o,
// observation only
output logic [7:0] seen_o, // qualified attempts observed
output int unsigned accepts_o, // attempts actually accepted
output logic [OFF_AW-1:0] acc_adr_o, // the offset that was accepted
output logic [DW-1:0] acc_dat_o // the payload that was accepted
);
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 [7:0] seen_q;
logic xfer, mapped, defer;
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_ID) || (adr_i == O_CMD);
// The attempt currently presented is the (seen_q+1)th this slave has
// observed. It is deferred while that index is within DEFER_COUNT.
assign defer = (seen_q < 8'(DEFER_COUNT));
assign rty_o = xfer && mapped && defer;
assign ack_o = xfer && mapped && !defer;
assign err_o = xfer && !mapped;
// No read result on a deferral: the interface is "not ready to send data".
assign dat_o = (xfer && !we_i && mapped && !defer) ? ID_VALUE : '0;
assign seen_o = seen_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
seen_q <= '0; accepts_o <= '0; acc_adr_o <= '0; acc_dat_o <= '0;
end else begin
// Count each qualified attempt once, at its terminating edge. All
// three classes terminate in the presenting clock here.
if (xfer && mapped) seen_q <= seen_q + 8'd1;
// ── COMMIT, only on acceptance. A deferred attempt records nothing.
if (xfer && mapped && !defer) begin
accepts_o <= accepts_o + 1;
acc_adr_o <= adr_i;
acc_dat_o <= dat_i;
end
end
end
endmodule// wb_defer_slave_commit — THE BUG, isolated. NOT A REFERENCE DESIGN.
//
// Identical to wb_defer_slave except that the commit is no longer gated on
// acceptance:
//
// if (xfer && mapped && !defer) begin ... commit ... end
// becomes
// if (xfer && mapped) begin ... commit ... end
//
// So the slave RECORDS THE COMMAND AND THEN RETURNS RTY_O. The termination
// says "not now"; the internal state says the operation happened.
//
// WHY THIS IS NOT AN OBVIOUS MISTAKE. The mistake it models is adding
// deferral to a working slave: the designer inserts an rty_o expression
// and narrows ack_o, and leaves the commit block alone because it "already
// worked". Each line reads correctly; nobody checks that the commit and
// the termination still agree.
//
// WHAT IS STILL CORRECT. Everything on the bus. The terminations are
// qualified (RULE 3.35), exclusive (RULE 3.45) and negated with STB_I
// (RULE 3.50). A protocol checker passes it, and a correctly-written
// master retrying against it will duplicate the command — through no
// fault of its own and with no way to detect it.
//
// NOTE WHAT WISHBONE DOES AND DOES NOT SAY HERE. There is no rule that a
// deferring target must leave its state unchanged. "No side effect on RTY"
// is a CONTRACT A TARGET OFFERS so that it is safe to retry against, not
// a protocol requirement — which is precisely why it has to be documented
// under RULE 2.15 and checked by an assertion rather than assumed.
// ─────────────────────────────────────────────────────────────────────────
module wb_defer_slave_commit #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32,
parameter int unsigned DEFER_COUNT = 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,
output logic rty_o,
output logic [7:0] seen_o,
output int unsigned accepts_o,
output logic [OFF_AW-1:0] acc_adr_o,
output logic [DW-1:0] acc_dat_o
);
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 [7:0] seen_q;
logic xfer, mapped, defer;
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_ID) || (adr_i == O_CMD);
assign defer = (seen_q < 8'(DEFER_COUNT));
// The bus interface is correct and unchanged. That is the point.
assign rty_o = xfer && mapped && defer;
assign ack_o = xfer && mapped && !defer;
assign err_o = xfer && !mapped;
assign dat_o = (xfer && !we_i && mapped && !defer) ? ID_VALUE : '0;
assign seen_o = seen_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
seen_q <= '0; accepts_o <= '0; acc_adr_o <= '0; acc_dat_o <= '0;
end else begin
if (xfer && mapped) seen_q <= seen_q + 8'd1;
// ── THE BUG: no `!defer` term. Every attempt commits, including the
// ones this slave is about to defer.
if (xfer && mapped) begin
accepts_o <= accepts_o + 1;
acc_adr_o <= adr_i;
acc_dat_o <= dat_i;
end
end
end
endmoduleReading the pair
wb_defer_slave gates its commit on !defer, so only an accepted attempt records anything. wb_defer_slave_commit drops that term, so every observed attempt commits — including the ones it is about to defer.
The bus interfaces are identical and both are conformant. Terminations qualified (RULE 3.35), exclusive (RULE 3.45), negated with STB_I (RULE 3.50). A protocol checker passes the broken one, and a correctly-written master retrying against it duplicates the command through no fault of its own.
Note precisely what Wishbone does and does not say here, because it is the sharpest protocol-versus-policy boundary in the module. There is no rule that a deferring target must leave its state unchanged. "No side effect on RTY" is a contract a target offers so that it is safe to retry against — not an obligation the specification imposes.
Which has a consequence worth stating plainly: a master cannot verify it. There is nothing on the bus that distinguishes a target that committed from one that did not, so the property has to be documented under RULE 2.15 and checked by an assertion inside the target.
And the failure is silent in the worst way. The client asked once, the master retried correctly, the bus was well-formed, and the device did the work three times.
6. Simulation — SIM G: The Retry Gap
One deferral, so there is exactly one gap between attempt 0 and attempt 1. The measurement counts clocks in which the master is busy but presenting nothing.
=== SIM G - RETRY_DELAY, measured ===
slave defers once, so there is exactly one gap between attempts.
RETRY_DELAY non-presented clocks between attempts RD + 1
0 1 1
1 2 2
3 4 4gap = RETRY_DELAY + 1 on every row, including zero.
The zero row is the one worth dwelling on. RETRY_DELAY = 0 produced one non-presented clock, not none. That clock is the release — the structural separation Section 1 argued is required for a second transfer to exist at all.
And this table is a correction, not a confirmation. The RTL header originally claimed RETRY_DELAY = N gives N non-presented clocks. The first trace showed two clocks at N = 1, and the definition was rewritten to match the hardware rather than the hardware adjusted to match the prose. The parameter's published meaning is now the measured one.
Why the discrepancy was easy to introduce. The delay counter genuinely does insert exactly RETRY_DELAY clocks. The extra clock comes from the state transition itself — leaving the presenting state, which is where STB_O negates — and it is invisible if you reason about the counter alone.
Which is the recurring lesson from Modules 9 and 10 in a third form. Chapter 9.1's WAIT_CYCLES, Chapter 10.4's TIMEOUT_CYCLES and this RETRY_DELAY all have an off-by-one between the counter's value and the observable behaviour. In every case the fix was to measure the parameter and publish the measurement.
7. Simulation — SIM H: Side-Effect Safety
One client write, against a target that defers twice before accepting. The client asked for one command to be enqueued.
=== SIM H - side-effect safety across a retry ===
slave defers 2 attempts then accepts; one client write.
target attempts seen commits recorded client ok
wb_defer_slave 3 1 1
wb_defer_slave_commit 3 3 1
one client write should produce exactly one commit.Both targets saw three attempts. Same master, same retry policy, same bus traffic — the trace is identical.
The correct target recorded one commit. The broken one recorded three.
One client write became three enqueued commands. For a command queue that is three operations performed where one was asked for: three packets transmitted, three motor steps, three DMA descriptors queued.
And the client was told it succeeded, once, in both rigs. There is nothing in the completion to distinguish them — the master behaved identically because the bus behaved identically.
Note that the master is blameless and powerless here. It latched its request, released on each RTY, waited, re-presented the same operation, and reported one outcome. Every property in Chapter 11.2 §9 passes. The defect is entirely inside the target, and no master-side change can detect or prevent it.
The evidence is the comparison the table makes: the target's commit count against the number of ACK terminations. They must be equal. Three commits against one acknowledge is conclusive, and it requires instrumentation inside the target because the bus shows one success either way.
Which makes this the clearest instance in the module of a boundary worth keeping sharp. The specification defines a termination class that means not now. It does not define what a target may do before saying it — so "safe to retry against" is a property a target has or lacks, must publish under RULE 2.15, and cannot be inferred from conformance.
8. Failure Modes and Discriminating Evidence
Symptom: an operation is performed more times than it was requested.
Candidate causes. A target that commits on attempts it defers, combined with a master that retries correctly.
Discriminating evidence. The target's side-effect count against its ACK count. Measured here as 3 against 1. The bus shows one success in both the correct and broken rigs, so this cannot be found from a trace.
Likely RTL location: separate conditions for the commit and the termination. The correct slave computes both from one term.
Symptom: a retrying master makes no progress and the bus looks continuously busy.
Candidate causes. STB_O never negating between attempts — so there are no attempts, only one refused transfer.
Discriminating evidence. Count STB_O rising edges, not RTY_I assertions. One rise with RTY_I held is a master that has not released; several rises are genuine attempts.
Correct model: the gap is structural. Section 1.
Symptom: retries arrive at a different cadence than the parameter suggests.
Candidate causes. The usual off-by-one between a delay counter and the observable gap.
Discriminating evidence. Measure non-presented clocks between two presentations and compare against the parameter. Here the relationship is RETRY_DELAY + 1, and it was published only after being measured.
Symptom: a design works against one deferring target and duplicates work against another.
Candidate causes. The two targets differ in whether they commit before deferring, and nothing in either datasheet says so.
Discriminating evidence. Instrument the target, not the bus. And read the datasheet — RULE 2.15 requires the conditions generating RTY_O to be documented, and a target whose deferral has side effects is one whose documentation should say so.
9. Verification
// Properties for restart timing and for the side-effect contract. Exactly
// one of these is the specification's; the rest are the local policy that
// makes retry work.
//
// 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_restart_props #(
parameter int unsigned RETRY_DELAY = 0
) (
input logic clk_i, rst_i,
input logic cyc_o, stb_o,
input logic ack_i, err_i, rty_i,
input logic busy_o,
input int unsigned commits_i, // the TARGET's side-effect count
input int unsigned acks_i // ACK terminations observed
);
default clocking cb @(posedge clk_i); endclocking
default disable iff (rst_i);
logic deferred;
assign deferred = cyc_o && stb_o && rty_i;
// R1 — SPECIFICATION (RULE 3.50, from the master's side). A deferred
// attempt is released: STB_O negates. Without this the slave's
// termination never negates either and there is no second attempt.
R1_release_after_rty: assert property ( deferred |=> !stb_o );
// R2 — LOCAL POLICY. The gap is at least one clock. This is the
// structural floor from Section 1, stated as a property: a
// presentation never immediately follows a deferral.
R2_gap_floor: assert property ( deferred |=> !stb_o );
// R3 — LOCAL POLICY. The configured delay is honoured: after a
// deferral, STB_O stays low for RETRY_DELAY+1 clocks before the
// next presentation. Written for the general case; at
// RETRY_DELAY = 0 it degenerates to R2.
R3_gap_exact: assert property (
deferred |=> (!stb_o [* (RETRY_DELAY + 1)] ##1 (stb_o || !busy_o))
);
// R4 — LOCAL CONTRACT, and the property this chapter exists for. The
// TARGET's side-effect count never exceeds the number of accepted
// transfers. Wishbone does not require this; it is what makes a
// target safe to retry against.
R4_no_commit_on_defer: assert property ( commits_i <= acks_i );
// R5 — LOCAL CONTRACT, the incremental form of R4. It fails on the
// FIRST duplicate rather than at the end of the run, which is a
// materially better debugging signal.
R5_commit_only_on_ack: assert property (
(commits_i != $past(commits_i)) |-> $past(cyc_o && stb_o && ack_i)
);
endmoduleR1 and R2 are the same expression and different claims, which is worth being explicit about rather than deduplicating. R1 is what RULE 3.50 forces from the master's side — the termination cannot negate unless STB_O does. R2 is the design statement that the gap exists at all. They coincide here because the floor is one clock; in a design with a longer mandatory gap they would not.
R4 and R5 are the pair that matter. R4 is a global invariant and fails at the end; R5 fails on the first duplicate commit and names the clock. Both are checked inside the target, because neither is visible on the bus.
And none of these is enforceable by a master. A master can satisfy every property in Chapter 11.2 §9 and still duplicate work against a target that breaks R4. The contract has to be held by the component that owns the side effect.
10. Common Mistakes
"RETRY_DELAY = 0 means the retry is immediate, with no gap."
Wrong mental model: the parameter controls the whole separation.
What is true: there is always at least one non-presented clock. Measured: RETRY_DELAY = 0 gives 1, = 1 gives 2, = 3 gives 4.
Concrete bug: a timing budget that assumes back-to-back attempts, or a testbench expecting a presentation on the clock after a deferral.
Correct model: gap = RETRY_DELAY + 1, and the 1 is structural.
"The master can just keep STB_O asserted and wait for the slave to relent."
Wrong mental model: holding the request is a cheaper retry.
What is true: RULE 3.50 means the slave's RTY_O never negates while STB_I is held, so the state is stable and nothing progresses. One transfer being refused, not a series of attempts.
Observable evidence: one STB_O rising edge with RTY_I asserted across many clocks.
Correct model: release, then re-present. That is what makes a second transfer exist.
"A target can perform the operation and then return RTY — the master will retry anyway."
Wrong mental model: the retry re-does something harmless.
What is true: measured — one client write became three enqueued commands. The master was correct throughout and had no way to detect it.
Observable evidence: the target's commit count against its ACK count. Nothing on the bus.
Correct model: one condition drives the termination and the side effect. And document it: RULE 2.15 requires the conditions generating RTY_O to be published, and a deferral with side effects is a condition worth publishing.
"Wishbone guarantees a deferred transfer changed nothing."
Wrong mental model: the protocol protects retry safety.
What is true: there is no such rule. The specification defines a class meaning not now; it says nothing about what a target may do before saying it.
Correct model: it is a contract, offered by the target, verifiable only from inside it. A target that does not offer it cannot be retried against, whatever the master does.
"If the bus trace is clean, the retry is safe."
Wrong mental model: conformance covers this.
What is true: the correct and the duplicating targets produce identical traces — three attempts, one acknowledge, one completion.
Correct model: retry safety is a target-internal property, and the instrument that checks it is a side-effect counter.
11. Interview Reasoning
Present, terminate, release, wait, present again — and the release is the part people leave out.
Attempt N is presented with CYC_O and STB_O asserted and the metadata latched at client acceptance. The slave answers RTY_O at a terminating edge, qualified by CYC_I && STB_I like any other class. That attempt is over.
The master negates STB_O. This is not optional and not merely tidy. RULE 3.50 requires the slave's termination to be asserted and negated in response to the assertion and negation of STB_I — so if STB_O never negates, RTY_O never negates, and the state is stable forever. One transfer being refused continuously, not a sequence of attempts.
So at least one non-presented clock is structural. I measured the gap across the parameter: RETRY_DELAY = 0 gives one non-presented clock, 1 gives two, 3 gives four. gap = RETRY_DELAY + 1, and the 1 cannot be configured away.
Then attempt N+1 is presented with the same metadata. Same address, same direction, same byte lanes, same payload — from the latches, never re-read from the client. A new transfer carrying the same operation.
The check I would apply to a waveform is to count STB_O rising edges rather than RTY_I assertions. Two rises means two attempts; one rise with RTY_I held means a master that has not released.
And I would mention that the published definition of RETRY_DELAY in my own design was wrong at first. The header claimed N non-presented clocks; the trace showed N+1. I corrected the documentation to the hardware rather than the other way round — which is the third parameter in three modules to have an off-by-one between its counter and its observable behaviour.
12. Understanding Check
13. What's Next
The mechanism is complete: a class meaning not now, a master that re-issues the operation it accepted, a bounded count, an exact gap, and a target contract that makes re-issuing safe.
Every scenario so far has been engineered to isolate one property. Real systems present all of them at once, mixed with conditions where retrying is the wrong answer entirely.
Given a failing or stalled access, which of these is it — and which of them should retry at all?
Chapter 11.5 — Real Examples classifies a set of realistic conditions, measures which respond to retry and which do not, and separates a retry loop from a long wait and from an exhausted policy in a trace. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Address Space
An address is a number until a decoder turns it into a target selection and a local offset. Range comparison and mask comparison are different engineering choices with different costs; exhaustive, mutually exclusive decode is a property to be proved rather than assumed; and a flat decoder's critical path is what eventually forces a hierarchy.
- 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
Timing Relationships
A combinational ACK gives one transfer per clock and creates a master-to-slave-and-back path in a single cycle; a registered ACK closes timing and costs a cycle. The specification describes both and prefers neither.
- Related topic
ACK Generation
A read slave's acknowledge promises that the accompanying data is the right value for the right request. Building it combinationally or from a register is a timing decision with a correctness cost.
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.
