Skip to content
VLSI Mentor

Wishbone · Module 10

Timeout Handling

Wishbone defines no timeout. A measured watchdog: exact parameter semantics, a boundary edge where the real answer wins, and a late response that terminates the wrong transfer unless the guard drains it.

Chapter 10.3 fixed the case where the address is wrong. SIM F's transfer is still outstanding, and a default responder does nothing for it — the address was fine and the target simply stopped answering.

If a selected target never responds, when does "still waiting" become "failure", and who decides?

1. What a Timeout Actually Is

A timeout is a decision to stop believing. The system picks a maximum latency it is prepared to tolerate and treats anything beyond it as failure — not because the transfer has failed, but because waiting longer has stopped being useful.

Three consequences follow, and all three are uncomfortable.

It can be wrong. A target that would have answered at clock 50 is declared failed at clock 40. The work was real and is now discarded.

It manufactures information. The ERR a watchdog generates did not come from a target that detected a fault. It is the system's own conclusion, wearing the same signal a genuine failure would use — which is why Chapter 10.5 has to work to tell them apart.

It does not stop the target. This is the one that produces real bugs, and Section 7 measures it. Wishbone has no cancel. The transfer upstream ends; the operation downstream continues.

Timeout is therefore not a protocol feature and not an error class. It is a policy that produces an error class, and keeping those two ideas separate is most of the chapter.

Timeout is not retry

The three termination classes are ACK, ERR and RTY. A timeout produces the second, by system policy.

It is not RTY. Retry means a target asking to be asked again — a distinct class with supplier-defined semantics, and Module 11's subject. A watchdog is not a target and is not asking for anything.

And a timed-out access should not be retried automatically unless the system knows why it timed out. Re-issuing into a target that is still working on the previous request is how the hazard in Section 7 becomes a loop.

2. TIMEOUT_CYCLES, Defined Before It Is Used

Chapter 9.1 §2 insisted that a latency parameter's meaning be written down and then measured, and Chapter 9.5 measured what happens when it is not — an off-by-one that was one clock wrong at one setting and a permanent hang at zero.

The same discipline, with the same shape as WAIT_CYCLES:

TIMEOUT_CYCLES = N means the guard tolerates N wait clocks. If no downstream termination has arrived by the (N+1)th clock at which the upstream transfer is presented, the guard times out at that clock.

Wait clocks toleratedTIMEOUT_CYCLES
Presented clocks before the timeout firesTIMEOUT_CYCLES + 1
TIMEOUT_CYCLES = 0degenerate but legal — times out at the first presented clock, so no target can ever answer

Zero is measured rather than forbidden. It is the value most likely to be skipped and the one where arithmetic breaks, and a parameter whose zero case is untested is exactly where Module 9's worst defect lived. Section 6 runs it.

The boundary policy

If a downstream termination and the threshold fall on the same clock edge, something has to win, and the specification says nothing about it.

This guard's policy: the downstream termination wins. LOCAL POLICY, not a requirement.

The reasoning is that a real answer is strictly better information than a manufactured one, and the alternative discards work that actually completed. A different system could choose the opposite — a hard real-time deadline might prefer a predictable timeout to a late-but-correct answer — and would be equally conformant.

It is one expression in the RTL, and Section 6 measures it rather than asserting it.

3. RTL — A Watchdog That Drains

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_timeout_guard — a bus watchdog, placed between a master and the fabric.
//
// WHAT THE SPECIFICATION SAYS ABOUT THIS, IN FULL:
//   RECOMMENDATION 3.10 — "Design INTERCON modules to prevent deadlock. One
//   solution is a watchdog timer function that monitors the MASTER's STB_O
//   signal."
// That is the entirety of it. A RECOMMENDATION, addressed to INTERCON,
// aimed at deadlock, suggesting one mechanism. Wishbone defines NO timeout:
// not a duration, not a response, not a recovery. Everything below is
// LOCAL SYSTEM POLICY, and it is written down because RULE 2.15 requires a
// core that generates ERR_O to document the conditions that produce it.
//
// ── TIMEOUT_CYCLES SEMANTICS ─────────────────────────────────────────────
// Stated once, and measured in Chapter 10.4 Section 6 rather than assumed.
// The shape deliberately matches Module 9's WAIT_CYCLES:
//
//   TIMEOUT_CYCLES = N  ->  the guard TOLERATES N wait clocks. If no
//                           downstream termination has arrived by the
//                           (N+1)th clock at which the upstream transfer
//                           is presented, the guard times out AT that
//                           clock.
//
//   TIMEOUT_CYCLES = 0  ->  degenerate but legal: zero wait clocks are
//                           tolerated, so the guard times out at the FIRST
//                           presented clock and no slave can ever answer.
//                           Section 6 measures it rather than forbidding
//                           it, because a parameter whose zero case is
//                           untested is where Module 9's off-by-one lived.
//
//   TIMEOUT_CYCLES = 1  ->  one wait clock tolerated; a slave with
//                           WAIT_CYCLES = 1 terminates at its 2nd presented
//                           clock, which is exactly the boundary.
//
// ── BOUNDARY POLICY ──────────────────────────────────────────────────────
// If a downstream termination and the timeout threshold fall on the SAME
// clock edge, THE DOWNSTREAM TERMINATION WINS. This is LOCAL POLICY, not a
// specification requirement. It is chosen because a real answer is strictly
// better information than a manufactured one, and because the alternative
// discards work that actually completed.
//
// ── THE LATE-RESPONSE HAZARD, AND WHY DE-PRESENTING IS NOT ENOUGH ───────
// When the guard times out it reports ERR upstream and releases the master.
// THE DOWNSTREAM TARGET IS NOT CANCELLED. Wishbone provides no cancel.
//
// The obvious response — stop presenting downstream and wait a while — does
// NOT work, and the reason is worth stating because it is easy to get wrong.
// RULE 3.50 makes a conformant target negate its termination when STB_I
// negates, so de-presenting does clear the wires. It does NOT clear the
// target's internal state. A target that finishes its operation while
// de-presented may simply HOLD the result until something is presented
// again — at which point it terminates the NEXT request with the OLD
// answer. Waiting longer does not help: the result is not decaying, it is
// parked.
//
// So this guard DRAINS instead of waiting. On timeout it latches the
// outstanding request and KEEPS PRESENTING IT downstream on behalf of a
// master that has already been released. When the target finally
// terminates, the guard ABSORBS that termination — it is not forwarded
// anywhere — and only then accepts a new upstream request.
//
// The stale answer is therefore consumed by the transfer it belongs to,
// which is the only way to be sure it cannot terminate a different one.
//
// DRAIN_LIMIT bounds the drain so that a target which never answers cannot
// stall the guard forever. If the limit is reached the guard gives up and
// reopens, and drain_abandoned_o counts it. THAT COUNTER IS THE RESIDUAL
// RISK, exposed rather than hidden: a target that answers after the limit
// can still leak, and no wrapper can prevent it. A system needing a real
// guarantee needs a target that can be reset or cancelled out of band,
// which is an architectural requirement on the target.
// ─────────────────────────────────────────────────────────────────────────
module wb_timeout_guard #(
  parameter int unsigned AW               = 30,
  parameter int unsigned DW               = 32,
  parameter int unsigned TIMEOUT_CYCLES   = 4,
  parameter int unsigned DRAIN_LIMIT      = 32
) (
  input  logic            clk_i,
  input  logic            rst_i,

  // ── upstream: the master ──
  input  logic            m_cyc_i,
  input  logic            m_stb_i,
  input  logic            m_we_i,
  input  logic [AW-1:0]   m_adr_i,
  input  logic [DW-1:0]   m_dat_i,
  input  logic [DW/8-1:0] m_sel_i,
  output logic [DW-1:0]   m_dat_o,
  output logic            m_ack_o,
  output logic            m_err_o,

  // ── downstream: the fabric or slave ──
  output logic            s_cyc_o,
  output logic            s_stb_o,
  output logic            s_we_o,
  output logic [AW-1:0]   s_adr_o,
  output logic [DW-1:0]   s_dat_o,
  output logic [DW/8-1:0] s_sel_o,
  input  logic [DW-1:0]   s_dat_i,
  input  logic            s_ack_i,
  input  logic            s_err_i,

  // ── observation only ──
  output logic [15:0]     count_o,
  output logic            expired_o,
  output logic            draining_o,
  output int unsigned     timeouts_o,
  output int unsigned     late_absorbed_o,
  output int unsigned     drain_abandoned_o
);
  typedef enum logic [1:0] { G_PASS, G_DRAIN } state_e;
  state_e state_q;

  logic [15:0] count_q;
  logic [15:0] drain_q;
  logic        presented, down_term, at_threshold;

  // The timed-out request, latched so the guard can keep presenting it
  // after the master has been released and moved on.
  logic            hold_we_q;
  logic [AW-1:0]   hold_adr_q;
  logic [DW-1:0]   hold_dat_q;
  logic [DW/8-1:0] hold_sel_q;

  assign presented = m_cyc_i && m_stb_i && (state_q == G_PASS);
  assign down_term = s_ack_i || s_err_i;

  // The threshold clock is the (TIMEOUT_CYCLES+1)th presented clock, which
  // is reached when the counter — which starts at 0 — has counted that many
  // tolerated waits.
  assign at_threshold = presented && (count_q >= 16'(TIMEOUT_CYCLES));

  // ── BOUNDARY POLICY, in one expression. `expired` requires the threshold
  //    AND the absence of a real termination, so a downstream answer on the
  //    same edge suppresses the timeout.
  assign expired_o = at_threshold && !down_term;

  // ── DOWNSTREAM PRESENTATION. In G_PASS the master's request is
  //    forwarded. In G_DRAIN the LATCHED request is presented instead, so
  //    the target still sees the transfer it is working on and its eventual
  //    termination belongs to that transfer rather than to a new one.
  assign s_cyc_o = (state_q == G_PASS) ? m_cyc_i : 1'b1;
  assign s_stb_o = (state_q == G_PASS) ? m_stb_i : 1'b1;
  assign s_we_o  = (state_q == G_PASS) ? m_we_i  : hold_we_q;
  assign s_adr_o = (state_q == G_PASS) ? m_adr_i : hold_adr_q;
  assign s_dat_o = (state_q == G_PASS) ? m_dat_i : hold_dat_q;
  assign s_sel_o = (state_q == G_PASS) ? m_sel_i : hold_sel_q;

  // ── UPSTREAM RETURN. A real termination is forwarded unchanged; a
  //    timeout is reported as ERR. RULE 3.45 holds because the two cannot
  //    coincide: expired_o excludes down_term by construction.
  assign m_ack_o = (state_q == G_PASS) ? s_ack_i : 1'b0;
  assign m_err_o = (state_q == G_PASS) ? (s_err_i || expired_o) : 1'b0;
  assign m_dat_o = s_dat_i;

  assign count_o    = count_q;
  assign draining_o = (state_q == G_DRAIN);

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      state_q <= G_PASS; count_q <= '0; drain_q <= '0;
      timeouts_o <= '0; late_absorbed_o <= '0; drain_abandoned_o <= '0;
      hold_we_q <= 1'b0; hold_adr_q <= '0; hold_dat_q <= '0; hold_sel_q <= '0;
    end else begin
      case (state_q)
        G_PASS: begin
          if (!presented) begin
            count_q <= '0;
          end else if (down_term) begin
            // A real answer. The boundary policy lives here too: this arm
            // is taken even on the threshold clock.
            count_q <= '0;
          end else if (expired_o) begin
            timeouts_o <= timeouts_o + 1;
            count_q    <= '0;
            // Latch the request so it can go on being presented downstream.
            hold_we_q  <= m_we_i;
            hold_adr_q <= m_adr_i;
            hold_dat_q <= m_dat_i;
            hold_sel_q <= m_sel_i;
            drain_q    <= 16'(DRAIN_LIMIT);
            state_q    <= G_DRAIN;
          end else begin
            count_q <= count_q + 16'd1;
          end
        end

        G_DRAIN: begin
          // The timed-out request is still presented downstream. When the
          // target answers, that answer is ABSORBED here — m_ack_o and
          // m_err_o are forced low outside G_PASS — and the guard reopens.
          if (down_term) begin
            late_absorbed_o <= late_absorbed_o + 1;
            state_q         <= G_PASS;
          end else if (drain_q == 16'd0) begin
            // The target never answered within the bound. Reopening now
            // reintroduces the leak risk; the counter records that it was
            // taken knowingly.
            drain_abandoned_o <= drain_abandoned_o + 1;
            state_q           <= G_PASS;
          end else begin
            drain_q <= drain_q - 16'd1;
          end
        end

        default: state_q <= G_PASS;
      endcase
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_stubborn_slave — a target whose internal operation CANNOT BE CANCELLED.
//
// TEACHING MODEL. It stands in for the common real case: a bridge that has
// forwarded a request to a far side and cannot recall it, a controller with
// a multi-clock sequence already in flight, an engine with no abort input.
//
// Behaviour:
//   - accepts a request at the first presented clock and starts an internal
//     operation lasting LATENCY clocks
//   - THE OPERATION RUNS TO COMPLETION REGARDLESS OF STB_I. De-presenting
//     the transfer does not stop it, because nothing in Wishbone can.
//   - when the operation finishes it holds a result, and asserts ACK_O at
//     whatever transfer is presented at that moment
//
// THAT LAST LINE IS THE HAZARD, and it is worth being precise about whose
// defect it is. Asserting a termination carrying an OLD operation's result
// for a NEW request is this slave's bug — a target should not answer a
// request it has not performed. But it is a bug a timeout wrapper must
// assume may exist, because the wrapper cannot inspect the target and has
// no way to cancel it.
//
// RULE 3.50 is honoured throughout: ack_o is gated on the transfer being
// presented, so it negates when STB_I negates. The slave never asserts a
// termination into an empty bus. That is exactly why the hazard is subtle —
// the slave is conformant at the signal level and still wrong.
// ─────────────────────────────────────────────────────────────────────────
module wb_stubborn_slave #(
  parameter int unsigned OFF_AW  = 4,
  parameter int unsigned DW      = 32,
  parameter int unsigned LATENCY = 8       // internal operation, in clocks
) (
  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,

  // observation only
  output logic                busy_o,
  output logic                result_pending_o,
  output logic [OFF_AW-1:0]   op_adr_o
);
  typedef enum logic [1:0] { S_IDLE, S_WORK, S_HOLD } state_e;
  state_e state_q;

  logic [15:0]       cnt_q;
  logic [OFF_AW-1:0] adr_q;
  logic [DW-1:0]     result_q;
  logic              xfer;

  assign xfer = cyc_i && stb_i;

  // RULE 3.50: the termination is gated on the presented transfer, so it
  // negates when STB_I negates. RULE 3.35: qualified by CYC_I AND STB_I.
  assign ack_o = xfer && (state_q == S_HOLD);
  assign err_o = 1'b0;
  assign dat_o = (state_q == S_HOLD) ? result_q : '0;

  assign busy_o           = (state_q == S_WORK);
  assign result_pending_o = (state_q == S_HOLD);
  assign op_adr_o         = adr_q;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      state_q <= S_IDLE; cnt_q <= '0; adr_q <= '0; result_q <= '0;
    end else begin
      case (state_q)
        S_IDLE: if (xfer) begin
          adr_q   <= adr_i;
          cnt_q   <= '0;
          state_q <= S_WORK;
        end

        // ── THE OPERATION IS NOT GATED ON xfer. It continues whether or
        //    not the transfer is still presented, which is the whole point.
        S_WORK: begin
          cnt_q <= cnt_q + 16'd1;
          if (cnt_q + 16'd1 >= 16'(LATENCY)) begin
            // A result tagged with the address it was computed for, so a
            // testbench can prove which request a late answer belongs to.
            result_q <= {28'h0BADD, adr_q};
            state_q  <= S_HOLD;
          end
        end

        // Holding a result. It will be delivered to whatever transfer is
        // presented next — including one it does not belong to.
        S_HOLD: if (xfer) state_q <= S_IDLE;

        default: state_q <= S_IDLE;
      endcase
    end
  end
endmodule

Reading the pair

The guard has two states and the second one is the chapter. G_PASS forwards in both directions. G_DRAIN is where a timed-out request goes to finish.

expired_o = at_threshold && !down_term is the boundary policy, written as one expression so it cannot be inconsistent with itself. A real termination on the threshold clock suppresses the timeout — and because m_err_o ORs s_err_i with expired_o, and the two cannot coincide, RULE 3.45 holds upstream by construction.

The drain is the part worth dwelling on. On timeout the guard latches the request and keeps presenting it downstream, on behalf of a master that has already been released. When the target finally answers, the guard absorbs that answer — m_ack_o and m_err_o are forced low outside G_PASS, so nothing is forwarded anywhere.

Why de-presenting instead would not work, and this is the design mistake the chapter exists to correct. RULE 3.50 makes a conformant target negate its termination when STB_I negates, so dropping the request does clear the wires. It does not clear the target's internal state. A target that finishes while de-presented may simply hold the result until something is presented again — and then terminate the next request with the old answer.

Waiting longer does not help, because the stale result is not decaying. It is parked. Only presenting the transfer it belongs to consumes it, which is what draining does.

DRAIN_LIMIT bounds the drain and drain_abandoned_o exposes the residual risk. A target that never answers cannot be allowed to stall the guard forever; a target that answers after the limit can still leak. That counter is the honest statement of what a wrapper cannot do — Wishbone provides no cancel, and a system needing a real guarantee needs a target that can be reset out of band, which is a requirement on the target rather than something a wrapper can supply.

wb_stubborn_slave is a teaching model and its own behaviour is a defect. Answering a request it has not performed is wrong; a good target would discard a result whose transfer went away. The point is that a watchdog cannot assume targets are good, because it cannot inspect them.

And note what the stubborn slave gets right. ack_o is gated on xfer, so it honours RULE 3.50 and never asserts a termination into an empty bus. It is conformant at the signal level and still produces the hazard — which is why this is an architecture problem rather than a protocol one.

Timing. The counter starts at zero on the first presented clock, so the threshold is reached on the (TIMEOUT_CYCLES+1)th. Reset is active high and synchronous throughout.

Simplifications. One outstanding transfer. No RTY_I path — Module 11. The guard sits directly in the master's path; a production interconnect would have one per master port or one per target, which is Module 17's territory.

4. Waveform — Timeout, Drain, Absorb

The upstream transfer ends; the downstream one does not

10 cycles
Ten clock cycles showing a timeout and the drain that follows. The master presents from cycle two and the guard's counter reads zero, one, then two at cycles two, three and four. At cycle four the expired signal asserts and an error is returned upstream. At cycle five the master has released and reports a client error, while the guard enters its draining state and continues asserting the downstream strobe. The target remains busy until cycle eight, when it asserts its acknowledge. At cycle nine that late acknowledge has been absorbed, the absorbed counter reads one, and the downstream strobe finally drops.threshold: timeout firesthreshold: timeout firesmaster freed; drain beginsmaster freed; drain beginslate answer, absorbedlate answer, absorbedCLK_Im CYC+STBcount0001200000expiredclient errs STB_Odrainings ACK_Iabsorbed0000000001t0t1t2t3t4t5t6t7t8t9
Figure 1 — TIMEOUT_CYCLES = 2 against a target whose operation takes 5 clocks and cannot be cancelled. The upstream transfer fails at cycle 4; the downstream answer arrives at cycle 8 and is absorbed. Traced from a run at these parameters; Section 6's measurements use the larger values stated there.

Count up to the threshold. count reads 0 at cycle 2, 1 at cycle 3, 2 at cycle 4. TIMEOUT_CYCLES = 2, so cycle 4 is the (N+1)th presented clock and expired asserts there.

Cycle 5 is where the two halves separate, and it is the whole point of the figure. m CYC+STB has gone low — the master received its error and released. client err is asserted. From the master's point of view the transfer is over.

But s STB_O is still high, and stays high through cycle 8. The guard is presenting the timed-out request downstream on behalf of a master that has left. draining marks the state.

Cycle 8 is the late answer. The target's operation completes and it asserts ACK. That acknowledge belongs to the transfer the master abandoned four clocks earlier, and it is arriving now.

Cycle 9 is the resolution. absorbed increments and s STB_O finally drops. The stale answer was consumed by the transfer it belonged to and never reached the master's port.

What the figure would look like without the drain. s STB_O would drop at cycle 5 with draining never asserting. The target would finish at cycle 8 holding its result, and the next upstream request would collect it — which Section 7 measures.

5. Simulation — SIM G, H and I

Four rigs, one clock, one request each. The guard's TIMEOUT_CYCLES and the slave's WAIT_CYCLES differ per rig so that the answer arrives before, on, or after the threshold.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM G/H/I - the watchdog, three outcomes ===
    TIMEOUT_CYCLES = N tolerates N wait clocks and times out
    at the (N+1)th presented clock. Boundary policy: a real
    downstream termination on that clock WINS.

    case                        TO  slave  presented  ok  err  timeouts
    G  answer before threshold   4     1        2      1    0      0
    H  answer ON the threshold   1     1        2      1    0      0
    I  no answer at all          4    99        5      0    1      1
    Z  TIMEOUT_CYCLES = 0        0     1        1      0    1      1

    presented clocks at timeout = TIMEOUT_CYCLES + 1:
      TIMEOUT_CYCLES=4 -> 5      TIMEOUT_CYCLES=0 -> 1

Case G — the answer arrives first. The slave terminates at its 2nd presented clock; the threshold is the 5th. ok = 1, timeouts = 0 — the guard is transparent when nothing goes wrong, which is the property it must have to be deployable at all.

Case H — the answer arrives exactly on the threshold. TIMEOUT_CYCLES = 1 puts the threshold at the 2nd presented clock; a slave with WAIT_CYCLES = 1 terminates at its 2nd presented clock. Same edge.

Measured: ok = 1, timeouts = 0. The slave won. That is the documented boundary policy, and it is now a measurement rather than a claim. A different system could choose the opposite and be equally conformant — what it could not do is leave the choice undefined, because the two outcomes differ in the success or failure reported to software.

Case I — nothing answers in time. presented = 5 against TIMEOUT_CYCLES = 4. That is N + 1 exactly, which is the parameter's stated meaning confirmed at a non-trivial value.

Case Z — TIMEOUT_CYCLES = 0. presented = 1, one timeout, reported as err. The transfer times out at its first presented clock, so no target can ever answer, however fast.

Zero is degenerate and legal, and it is measured for a reason. Chapter 9.5 found an off-by-one that was one clock wrong at WAIT_CYCLES = 3 and hung the bus permanently at 0, because unsigned 0 - 1 is not -1. The zero case is where latency-parameter arithmetic breaks, and it is the value people skip because it feels trivial.

The two identities hold at both ends of the range:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  presented clocks at timeout  =  TIMEOUT_CYCLES + 1
     TIMEOUT_CYCLES = 4  ->  5
     TIMEOUT_CYCLES = 0  ->  1

6. The Late Response — SIM J

This is the section that justifies the drain, and the experiment is built so that a stale answer is identifiable rather than merely suspected.

The setup. The target's operation takes 9 clocks and cannot be cancelled; the guard tolerates 3. Request 1 reads word 3 and times out. The master then issues request 2, to word 4. The target finishes request 1's operation afterwards, and tags its result with the offset it was computed for — 0x000badd3 for word 3.

So a value of 0x000badd3 appearing as request 2's result is proof, not inference.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM J - a late response, and what the guard does with it ===
    TIMEOUT_CYCLES=3, target operation takes 9 clocks and cannot
    be cancelled. Request 1 reads word 3 and times out. Request 2
    reads word 4. The target's result is tagged 0x0BADD_<offset>,
    so an answer belonging to request 1 is identifiable.

    guard              timeouts  absorbed  abandoned  req2 ok  req2 err  req2 data
    DRAIN_LIMIT=32        2         2         0         0        1      0x00000000
    DRAIN_LIMIT=0         1         0         1         1        0      0x000badd3

    request 1's answer is tagged 0x000badd3 (offset 3). Seeing it as
    request 2's result means a stale response terminated the wrong
    transfer.

Read the bottom row first, because it is the failure. With DRAIN_LIMIT = 0 the guard gives up immediately — the "de-present and hope" design. Request 2 was reported ok = 1 with data 0x000badd3.

That is request 1's answer, delivered as request 2's result. The tag says so: offset 3, when request 2 asked for word 4. A read of one register returned another register's value, reported as a success, with no error anywhere in the system.

And notice timeouts = 1, not 2. Request 2 did not time out — the leaked acknowledge terminated it. The leak did not merely corrupt one result; it suppressed the failure report that would have exposed the problem.

abandoned = 1 records that the guard reopened without draining. That counter is the warning, and in this configuration it is the only evidence available.

Now the top row. With DRAIN_LIMIT = 32 the guard drains: absorbed = 2, abandoned = 0, and request 2 was reported as an error with no data. No leak.

Two timeouts, not one, and that is correct. The target takes 9 clocks and the guard tolerates 3, so both requests time out. The drained rig reports two honest failures; the undrained one reports one failure and one false success.

Which is the comparison worth carrying: the broken guard looks better on a naive metric. Fewer errors reported. The system that reports more failures is the one telling the truth.

What the drain does and does not guarantee

It guarantees that a late answer arriving within DRAIN_LIMIT is consumed by its own transfer. Measured: absorbed = 2, no leak.

It does not guarantee anything about a target that answers later. drain_abandoned_o counts those cases, and a target that finishes after the limit can still terminate a subsequent request.

No wrapper can do better, and the reason is structural. Wishbone provides no cancellation. The only lever a guard has is whether it presents a transfer, and a target that parks a result until something is presented cannot be flushed by any sequence of presentations the guard can make on its own.

A real guarantee requires something the bus does not have — a reset, an abort, or a tag that lets a response be matched to its request. Those are requirements on the target or on a different protocol, and inventing them here would be inventing semantics Wishbone does not provide.

So the honest architectural statement is: the drain converts an unbounded correctness hazard into a bounded one, exposes the residual risk as a counter, and makes DRAIN_LIMIT a number somebody has to justify against the slowest target in the system.

7. Failure Modes and Discriminating Evidence

Symptom: a read returns another register's value, and no error is reported.

Candidate causes. A stale downstream response terminating a later request.

Discriminating evidence. Compare the returned value against what the previous access asked for. Measured here as 0x000badd3 returned to a request for word 4 — the tag named the wrong offset. Without such a tag, compare against the previous request's expected result.

Corroborating evidence. A drain_abandoned count greater than zero, or a timeout count lower than the number of accesses that should have timed out.

Likely location: the timeout wrapper, not the target.

Symptom: a system times out on a target that is known to be working.

Candidate causes. TIMEOUT_CYCLES set below the target's genuine worst-case latency.

Discriminating evidence. The guard's count at expiry against the target's own latency counter. If the target was still progressing when the guard fired, the threshold is too tight and nothing is broken.

Note the asymmetry: this failure is safe and loud. The opposite — a threshold so generous that a dead target is never detected — is silent.

Symptom: timeouts appear only under load.

Candidate causes. Arbitration delay counted against the target's budget.

Discriminating evidence. Where the guard sits. A guard upstream of an arbiter measures grant wait plus service time; one downstream measures only service time. Chapter 8.6 measured a competing master's wait going from 2 clocks to 35 — enough to cross a threshold set from service time alone.

Scope note: arbitration is Module 17's.

Symptom: a timed-out access is retried and the system gets worse.

Candidate causes. Re-issuing into a target still working on the previous request.

Discriminating evidence. A rising late_absorbed or drain_abandoned count under retry. Each retry meets a target that is already busy, and a leak becomes a loop.

Correct model: timeout is not retry. Retry has its own termination class and Module 11 owns it.

8. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Properties for the timeout guard. Note how few are the specification's:
// Wishbone defines no timeout, so almost everything here is policy, and
// the labels are load-bearing rather than decorative.
//
// 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 5 and 6 come from procedural checks, which Icarus
// does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_timeout_guard_props #(
  parameter int unsigned TIMEOUT_CYCLES = 4
) (
  input logic        clk_i, rst_i,
  input logic        m_cyc_i, m_stb_i, m_ack_o, m_err_o,
  input logic        s_ack_i, s_err_i,
  input logic        expired_i, draining_i,
  input logic [15:0] count_i
);
  default clocking cb @(posedge clk_i); endclocking
  default disable iff (rst_i);

  logic up_presented, down_term;
  assign up_presented = m_cyc_i && m_stb_i;
  assign down_term    = s_ack_i || s_err_i;

  // T1 — SPECIFICATION (RULE 3.45), preserved by the guard. The upstream
  //      master must never see two classes, and a guard that ORed a
  //      manufactured error onto a real acknowledge would break this.
  T1_one_class_upstream: assert property ( !(m_ack_o && m_err_o) );

  // T2 — LOCAL POLICY: the boundary rule, as a property. A timeout is
  //      never declared on a clock where a real termination arrived.
  T2_real_answer_wins: assert property ( down_term |-> !expired_i );

  // T3 — LOCAL POLICY: the parameter's semantics. A timeout fires only
  //      once the tolerated wait clocks have elapsed.
  T3_threshold_honoured: assert property (
    expired_i |-> (count_i >= 16'(TIMEOUT_CYCLES))
  );

  // T4 — LOCAL POLICY: a timeout is reported upstream as an error, so the
  //      master is released rather than left waiting.
  T4_timeout_reports_error: assert property ( expired_i |-> m_err_o );

  // T5 — LOCAL POLICY, and the one that matters most. No downstream
  //      termination reaches the master while draining. This is the
  //      property the DRAIN_LIMIT = 0 configuration fails, and the reason
  //      SIM J's leak is a leak.
  T5_no_late_leak: assert property ( draining_i |-> (!m_ack_o && !m_err_o) );

  // T6 — LOCAL POLICY. Draining is entered only from a timeout, so the
  //      guard cannot stall a healthy transfer.
  T6_drain_from_timeout: assert property (
    $rose(draining_i) |-> $past(expired_i)
  );
endmodule

T1 is the only specification property, and placing it on the guard's upstream port is the point. RULE 3.45 binds slaves; a guard is not a slave, but it presents terminations to a master and can construct a violation by combining a real one with a manufactured one. The property belongs where the classes are combined.

T5 is the chapter's central property and it is worth reading twice. It does not say late responses do not happen — they do, and late_absorbed_o counts them. It says they do not reach the master. That is the difference between a hazard that is managed and one that is denied.

And T5 is checkable, which the informal claim is not. "The drain prevents leaks" is an assertion about a design; "no termination is forwarded while draining" is a statement about two signals, and it fails immediately in the DRAIN_LIMIT = 0 configuration.

What no property here can establish is whether TIMEOUT_CYCLES is set correctly. That is a system-integration question — it depends on the slowest legitimate target, on arbitration delay, and on what the software can tolerate — and no property written from these pins can reach it.

9. Common Mistakes

"Wishbone times out automatically."

Wrong mental model: the bus protects you from a dead target.

What is true: it does not. There is no timeout in the specification — no duration, no response, no recovery. The closest thing is RECOMMENDATION 3.10, which advises designing INTERCON to prevent deadlock and suggests a watchdog as one solution.

Concrete bug: shipping a system with no watchdog and no default responder, on the assumption that something will eventually give up. Chapter 10.3's SIM F measured 40 clocks of nothing, with no component behaving incorrectly.

Correct model: bounded failure is a feature somebody builds. Check whether yours has one and where it sits.

"A timeout and an ERR are the same thing."

Wrong mental model: both mean failure, so both are one concept.

What is true: ERR is a termination class on the bus. A timeout is a policy that decides to emit one. A genuine ERR comes from a component that detected a fault; a watchdog's ERR is the system's own conclusion that waiting has stopped being useful.

Concrete bug: a debug procedure that treats every ERR as a target fault, and therefore investigates a peripheral that never reported anything.

Observable evidence: which component asserted the error — Chapter 10.5 is about recovering exactly this.

Correct model: timeout produces an error class; it is not one.

"Once the master has timed out, a late ACK cannot matter."

Wrong mental model: the transfer is over, so the target's answer is harmless.

What is true: the target was never told. Wishbone has no cancel, and a target holding a finished result will deliver it to whatever transfer is presented next.

Concrete bug: measured — request 2 was reported ok with request 1's answer, 0x000badd3, tagged with the wrong offset. And the leak suppressed request 2's own timeout, so the system reported fewer errors than it should have.

Observable evidence: a returned value belonging to a previous request; a drain_abandoned count above zero.

Correct model: the upstream transfer ended; the downstream one did not. Drain it.

"De-presenting the request cancels it."

Wrong mental model: dropping STB_O undoes the operation.

What is true: it clears the wires — RULE 3.50 makes a conformant target negate its termination when STB_I negates. It does not clear the target's internal state, and a parked result does not decay.

Concrete bug: a "quarantine" wrapper that de-presents and waits N clocks. Waiting longer never helps, because the stale answer is waiting too.

Correct model: present the transfer it belongs to until it is answered, and absorb that answer.

"Set the timeout generously and the problem goes away."

Wrong mental model: a large threshold is a safe threshold.

What is true: it trades one failure for another. Too tight and healthy targets are declared dead — loud and safe. Too generous and a dead target is never detected — silent, and the system hangs for as long as the threshold lasts.

Correct model: derive it from the slowest legitimate target plus arbitration delay, and treat it as a number requiring justification rather than a default.

10. Interview Reasoning

No, it does not — and the specification's only contribution is one sentence of advice about where such a thing belongs.

What the specification says. RECOMMENDATION 3.10: design INTERCON modules to prevent deadlock, one solution being a watchdog timer function that monitors the master's STB_O. That is a recommendation, addressed to the interconnect, aimed at deadlock, suggesting one mechanism. It defines no duration, no response and no recovery.

Why the protocol cannot define one. A slave may insert any number of wait states, so "still waiting" never expires. At any finite clock, a slow target and a dead one are indistinguishable on the bus — I measured a healthy slave and a stuck one in Module 9 whose traces matched for as long as anyone watched. Elapsed time carries no information, so any threshold is a system's choice about tolerable latency.

Where it should live. The recommendation says the interconnect, and I would agree for a specific reason: the interconnect is the component that knows a transfer is outstanding and has no stake in it. A master watchdogging itself is checking its own homework, and a target cannot watchdog a request it never received.

One placement subtlety I would raise. Upstream or downstream of arbitration changes what is being measured. Upstream, the budget includes grant wait — which Chapter 8.6 measured going from 2 clocks to 35 for a competing master under a block transfer. Downstream it measures service time only. Getting that wrong produces timeouts that appear only under load.

And the number itself needs justifying. Too tight and healthy targets are declared dead, which is loud and safe. Too generous and a dead target is never detected, which is silent. I would derive it from the slowest legitimate target plus worst-case arbitration, and write down where the figure came from.

11. Understanding Check

12. What's Next

Errors now come from three places, and all three arrive at the master on the same wire: a peripheral that refused, a decoder that owned nothing, and a watchdog that stopped waiting.

They are produced by different components, for different reasons, with different evidence — and at the client contract they are one bit.

A transfer failed. Which of them was it, and how do you find out from a trace?

Chapter 10.5 — Debug Strategies turns this module's mechanisms into an evidence-driven procedure, against failures whose source is not announced in advance. 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.