Skip to content
VLSI Mentor

Wishbone · Module 9

Common Bugs

Seven measured wait-state failures and the observation that identifies each — including an off-by-one that is one clock wrong at three wait states and a permanent hang at zero.

Chapter 9.4 established what a correct design preserves across any delay. Every defect in this module so far has been introduced deliberately, with the answer known in advance.

That is the opposite of how they arrive.

A transfer is hung, or completed with the wrong data, and nobody knows why. Where do you look?

1. RTL — Four More Broken Designs

Three of the seven bugs in this chapter were already built and measured in Chapter 9.2 and Chapter 9.3. These four are new, and each fails at the response boundary in a different way.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_offbyone_slave — the latency comparison, three ways. TEACHING ONLY.
//
// Everything except one expression is Chapter 9.1's reference slave. MODE
// selects which comparison computes `ready`:
//
//   MODE = 0  CORRECT      ready = (waited_q >= WAIT_CYCLES)
//   MODE = 1  ONE TOO MANY ready = (waited_q >= WAIT_CYCLES + 1)
//   MODE = 2  ONE TOO FEW  ready = (waited_q >= WAIT_CYCLES - 1)
//
// All three compile, all three elaborate, and all three look plausible in
// review. Two of them are wrong, and one of those two does something much
// worse than being wrong by a clock — Chapter 9.5 Section 4 measures it.
//
// The reason an off-by-one here is more dangerous than an off-by-one in
// ordinary arithmetic: this counter gates the ONLY mechanism the slave has
// for ending a transfer. A comparison that can never become true does not
// produce a late answer. It produces no answer at all.
// ─────────────────────────────────────────────────────────────────────────
module wb_offbyone_slave #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned WAIT_CYCLES = 3,
  parameter int unsigned MODE        = 0
) (
  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 [7:0]          waited_o
);
  localparam logic [OFF_AW-1:0] O_ID = 4'd4;
  localparam logic [DW-1:0] ID_VALUE = 32'h5742_0901;

  logic [7:0] waited_q;
  logic       xfer, mapped, ready;

  assign xfer   = cyc_i && stb_i;
  assign mapped = (adr_i == O_ID);

  // ── THE ONE EXPRESSION THAT DIFFERS ─────────────────────────────────────
  // WAIT_CYCLES is `int unsigned`, so MODE 2 at WAIT_CYCLES = 0 evaluates
  // 0 - 1 in unsigned arithmetic. That is not -1; it is 4294967295, and
  // the comparison can never be satisfied by an 8-bit counter.
  generate
    if (MODE == 1)      assign ready = (waited_q >= 8'(WAIT_CYCLES + 1));
    else if (MODE == 2) assign ready = (waited_q >= 8'(WAIT_CYCLES - 1));
    else                assign ready = (waited_q >= 8'(WAIT_CYCLES));
  endgenerate

  assign waited_o = waited_q;
  assign ack_o    = xfer &&  mapped && ready;
  assign err_o    = xfer && !mapped && ready;
  assign dat_o    = (xfer && !we_i && mapped) ? ID_VALUE : '0;

  always_ff @(posedge clk_i) begin
    if (rst_i)            waited_q <= '0;
    else if (!xfer)       waited_q <= '0;
    else if (!ready)      waited_q <= waited_q + 8'd1;
    else                  waited_q <= '0;
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Three slaves that fail in three different ways at the response boundary.
// TEACHING ONLY. None of these is a reference design.
//
// They are grouped because they share a symptom family — "the transfer did
// not end correctly" — and are told apart by different evidence.
// ─────────────────────────────────────────────────────────────────────────

// wb_stale_data_slave — BUG D. Acknowledges on time, with data that is not.
//
// The termination timing is perfect: ACK_O is asserted after exactly
// WAIT_CYCLES wait states. What is wrong is that DAT_O is REGISTERED, so
// the value on the bus at the terminating edge is the one computed a clock
// EARLIER — which for a changing source is the previous value.
//
// RULE 3.65 requires the SLAVE to qualify DAT_O() with ACK_O, ERR_O or
// RTY_O. This slave asserts ACK_O over data that is not yet the answer, so
// it violates that rule while looking entirely healthy on the control
// signals. The master is conformant and still captures the wrong value.
module wb_stale_data_slave #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned WAIT_CYCLES = 2
) (
  input  logic                clk_i, rst_i, cyc_i, stb_i, 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, err_o
);
  localparam logic [OFF_AW-1:0] O_COUNT = 4'd1;   // free-running: changes
  logic [7:0]    waited_q;
  logic [DW-1:0] count_q, dat_q;
  logic          xfer, mapped, ready;

  assign xfer   = cyc_i && stb_i;
  assign mapped = (adr_i == O_COUNT);
  assign ready  = (waited_q >= 8'(WAIT_CYCLES));

  assign ack_o = xfer &&  mapped && ready;
  assign err_o = xfer && !mapped && ready;

  // ── THE BUG: the read path is registered, so dat_o lags by one clock.
  assign dat_o = dat_q;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin waited_q <= '0; count_q <= '0; dat_q <= '0; end
    else begin
      count_q <= count_q + 1;
      dat_q   <= (xfer && !we_i && mapped) ? count_q : '0;
      if (!xfer)       waited_q <= '0;
      else if (!ready) waited_q <= waited_q + 8'd1;
      else             waited_q <= '0;
    end
  end
endmodule

// wb_stuck_ack_slave — BUG F. The response is never released.
//
// ACK_O is driven from a REGISTER that is set when the latency expires and
// is never cleared on the negation of STB_I. RULE 3.50 requires the
// termination signals to be asserted AND NEGATED in response to the
// assertion and negation of STB_I, and OBSERVATION 3.10 records that slaves
// "automatically negate ACK_O, ERR_O and RTY_O when STB_I is negated".
// This one does not, so the stale acknowledge is still asserted when the
// NEXT transfer is presented — and terminates it immediately, before the
// slave has done any work for it.
module wb_stuck_ack_slave #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned WAIT_CYCLES = 2
) (
  input  logic                clk_i, rst_i, cyc_i, stb_i, 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, err_o
);
  localparam logic [DW-1:0] ID_VALUE = 32'h5742_0901;
  logic [7:0] waited_q;
  logic       xfer, ready, ack_q;

  assign xfer  = cyc_i && stb_i;
  assign ready = (waited_q >= 8'(WAIT_CYCLES));

  // ── THE BUG: no dependence on the CURRENT transfer being presented.
  assign ack_o = ack_q;
  assign err_o = 1'b0;
  assign dat_o = (adr_i == 4'd4) ? ID_VALUE : 32'hDEAD_BEEF;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin waited_q <= '0; ack_q <= 1'b0; end
    else begin
      if (!xfer)       waited_q <= '0;
      else if (!ready) waited_q <= waited_q + 8'd1;
      else             waited_q <= '0;
      // Set on expiry and never cleared.
      if (xfer && ready) ack_q <= 1'b1;
    end
  end
endmodule

// wb_lost_request_slave — BUG E. The acknowledge never arrives.
//
// The latency counter is reset by the wrong condition. It clears whenever
// the slave is not already acknowledging, which is true on every clock
// before the acknowledge — so the counter can never climb past 1 and, for
// any WAIT_CYCLES above 1, `ready` is never satisfied.
//
// The transfer is presented forever. Nothing on the slave's interface is
// malformed: it simply never terminates.
module wb_lost_request_slave #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned WAIT_CYCLES = 3
) (
  input  logic                clk_i, rst_i, cyc_i, stb_i, 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, err_o,
  output logic [7:0]          waited_o
);
  localparam logic [DW-1:0] ID_VALUE = 32'h5742_0901;
  logic [7:0] waited_q;
  logic       xfer, ready;

  assign xfer  = cyc_i && stb_i;
  assign ready = (waited_q >= 8'(WAIT_CYCLES));

  assign ack_o    = xfer && ready;
  assign err_o    = 1'b0;
  assign dat_o    = ID_VALUE;
  assign waited_o = waited_q;

  always_ff @(posedge clk_i) begin
    if (rst_i) waited_q <= '0;
    // ── THE BUG: reset on "not acknowledging" instead of "not presented".
    else if (!ack_o) waited_q <= '0;
    else             waited_q <= waited_q + 8'd1;
  end
endmodule

Reading the group

All four elaborate cleanly and none is obviously wrong in review. That is the selection criterion — a bug that looks wrong is not interesting.

wb_offbyone_slave puts the whole defect in one expression, chosen by a parameter, so the three comparisons can be run against each other with nothing else varying.

wb_stale_data_slave gets the control signals perfectly right. Its acknowledge arrives after exactly the configured number of wait states. Only DAT_O is wrong, and only because the read path is registered — which is a normal thing to do for timing, done without moving the acknowledge to match.

wb_stuck_ack_slave drives ACK_O from a register with no dependence on the current transfer. RULE 3.50 requires the termination signals to be asserted and negated in response to the assertion and negation of STB_I, and OBSERVATION 3.10 records that slaves "automatically negate ACK_O, ERR_O and RTY_O when STB_I is negated". This one does not.

wb_lost_request_slave resets its counter on the wrong condition!ack_o instead of !xfer. Since ack_o is low on every clock before the acknowledge, the counter is cleared on every one of them and can never climb.

Note what these four have in common. Every one of them is a slave defect at the moment of response, and none of them produces a malformed signal that a simple protocol checker would flag. The stuck-acknowledge slave comes closest, and its violation is of a rule about negation, which checkers routinely omit.

Seven bugs. Each is stated as a wrong belief, because that is the form they take in a designer's head.

BUG A — "nothing has completed, so the address can still change"

Broken design. wb_passthrough_master (Chapter 9.3) drives ADR_O from its client's live output instead of from a register latched at acceptance.

Measured. The client asked for word 4 and switched its input to word 0 while a 3-wait slave was working. The master returned 0x00000001 — the contents of STATUS — when its client had asked for ID. One transfer, no error.

Root cause. RULE 3.60 qualifies ADR_O with STB_O, and STB_O was asserted for all four presented clocks.

Invariant. The transaction's identity is fixed at the presenting edge. Only its duration is still open.

Check. metadata changes while outstanding — measured 1 for the broken master and 0 for the correct one.

Debugging method. This is question 2. One counter, conclusive, and it names the master rather than the slave.

BUG B — "a write presented for four clocks is four writes"

Broken design. wb_cmd_slave_repeating (Chapter 9.2) commits on xfer && mapped && we_i, omitting the ready term.

Measured. One logical write to COMMAND at WAIT_CYCLES = 3 produced 4 side effects, with zero differences in the bus trace against the correct slave.

Root cause. The side effect is gated on presentation rather than on termination.

Invariant. One accepted transfer, one architectural effect — whatever the latency.

Check. Side-effect count against transfer count. A ratio of WAIT_CYCLES + 1 is conclusive.

Debugging method. This is question 4, and the bus cannot answer it. The evidence is internal: a strobe count, or the state the register drives.

BUG C — "the master can start preparing the next request"

Broken design. A master that advances its address, index or payload on elapsed clocks rather than on terminations.

Measured, in this course. Chapter 8.3's wb_block_master_fastadv presented the correct four addresses and acknowledged only two of them, losing WINDOW[0] and WINDOW[2] with a bus trace whose address sequence looked perfect.

Root cause. Sequencing driven by clocks instead of by completions.

Invariant. Anything that advances a sequence advances on completion.

Check. Address stability across a presented transfer, and a transfer count that matches what was requested. A master reporting fewer transfers than it asked for has usually failed this way.

Debugging method. Question 2 again — this is BUG A's shape applied to a sequence rather than to a single request. Do not look for gaps in the presented address list; look at which addresses were acknowledged.

BUG D — "the acknowledge says the data is ready"

Broken design. wb_stale_data_slave registers its read path, so DAT_O at the terminating edge is the value computed one clock earlier.

Measured. Reading the free-running COUNT register, the master captured 0x00000004 where the same read against a correct slave returned 0x00000005exactly one clock of COUNT stale.

Root cause. RULE 3.65 requires the slave to qualify DAT_O() with ACK_O, ERR_O or RTY_O. This slave asserts the acknowledge over data that is not yet the answer.

Invariant. The acknowledge and the data it qualifies must arrive together.

Check. Compare against a reference slave, or against the target's own internal state at the terminating edge. The master is conformant and still captures the wrong value, so this cannot be found by checking the master.

Debugging method. Neither the control signals nor the master are at fault, so questions 1–3 all pass. The discriminator is a value comparison against something that knows the right answer — which is why a free-running register makes a better test target than a constant here.

BUG E — "if the acknowledge never comes, the slave is broken"

Broken design. wb_lost_request_slave clears its latency counter whenever it is not acknowledging, so the counter can never climb.

Measured. The transfer was still presented after 40 clocks, the master still busy, and the slave's latency counter reading 0.

Root cause. The counter's reset condition. Not the decode, not the qualification, not the master.

Invariant. A slave that intends to answer must make progress towards answering.

Check. Is the latency counter progressing? This is question 3, and it is the whole chapter in one observation.

Debugging method. The title of this bug is the wrong belief. A missing acknowledge does not imply a broken slave — the request may never have reached it. Section 4's figure shows the two cases side by side, and the counter separates them in one glance.

BUG F — "the acknowledge is over, so it does not matter any more"

Broken design. wb_stuck_ack_slave sets ACK_O from a register on expiry and never clears it on the negation of STB_I.

Measured. The second transfer completed with 0 wait clocks, and the acknowledge was already asserted at the clock it was first presented — terminating a transfer for which the slave had done no work at all.

Root cause. RULE 3.50. The termination signals must be negated in response to the negation of STB_I.

Invariant. A termination belongs to exactly one transfer.

Check. ACK_I asserted at the first presented clock of a transfer against a slave whose latency is non-zero. Or: a transfer whose measured wait count is zero when the slave is configured not to be.

Debugging method. The symptom appears on the transfer after the guilty one, which is what makes it confusing. Look one transfer back.

BUG G — "the counter is off by one, so it is one clock wrong"

Broken design. wb_offbyone_slave with MODE = 1 or MODE = 2.

Measured. At WAIT_CYCLES = 3: >= WAIT_CYCLES gives 3 waits, >= WAIT_CYCLES + 1 gives 4, >= WAIT_CYCLES - 1 gives 2. All three complete, and two are silently wrong.

And then the same - 1 comparison at WAIT_CYCLES = 0 hangs permanently.

Root cause. WAIT_CYCLES is int unsigned, so 0 - 1 is not -1; it is 4294967295, and an 8-bit counter can never reach it.

Invariant. The parameter's semantics, stated and then measured — the discipline Chapter 9.1 §2 insisted on.

Check. Sweep the parameter and include zero. A comparison that is merely late at 3 becomes a deadlock at 0, and no amount of testing at 3 reveals it.

Debugging method. This is the bug that most rewards the measured table over the reasoned one. All three comparisons look correct in review, and the difference between "slightly slow" and "hangs forever" is one operand and one parameter value.

3. Waveform — Slow or Stuck?

The counter is the discriminator

10 cycles
Ten clock cycles comparing a healthy slave and a hung slave answering the same request. Both assert cycle and strobe from cycle two. The healthy slave's internal waited counter reads zero, one, two and three at cycles two through five, and its acknowledge rises at cycle five, after which the bus goes idle. The hung slave's waited counter reads zero at every cycle and never advances, its acknowledge never rises, and the cycle and strobe signals remain asserted through cycle nine and beyond.identical on the bus so faridentical on the bus so farhealthy terminates herehealthy terminates herecounter still 0: stuck, not slowcounter still 0: stuck, notslowCLK_Iok CYC+STBok waited0001230000ok ACK_Ibad CYC+STBbad waited0000000000bad ACK_It0t1t2t3t4t5t6t7t8t9
Figure 1 — the same request against a healthy 3-wait slave and against wb_lost_request_slave. The bus signals are indistinguishable for four clocks; the latency counter is not. Traced from simulation.

Cycles 2 and 3 are indistinguishable on the bus. Both slaves have a presented transfer and neither has acknowledged. Nothing on the Wishbone signals says which of these will finish.

The waited rows separate them immediately. The healthy slave counts 0, 1, 2, 3 and terminates at cycle 5. The hung slave reads 0 at every clock — it is not making progress towards an answer, and it never will.

This is why "is the counter progressing" is a better first question than "is ACK asserted". A counter at 2 of 3 means wait; a counter stuck at 0 means investigate. Both look identical from outside the slave.

And it distinguishes the two hang causes. A counter that is progressing but slow is a slave doing its job. A counter that is not progressing means either the slave's own logic is broken — this case — or the request never reached it: wrong decode, wrong select, CYC_I not routed. Those are different fixes in different modules, and the counter says which half to look in.

What the figure deliberately does not show is a timeout. Nothing here recovers the bus. A watchdog is a system-level robustness feature — RECOMMENDATION 3.10 suggests an INTERCON watchdog monitoring the master's STB_O, and Module 10 owns timeout handling. This chapter's job is to identify the fault, not to survive it.

4. Simulation — SIM G and SIM H

SIM G — one comparison, three ways.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM G - one comparison, three ways, WAIT_CYCLES = 3 ===
    all three slaves are identical apart from how `ready` is computed

    comparison                       waits  presented  completed
    waited_q >= WAIT_CYCLES             3         4        1
    waited_q >= WAIT_CYCLES + 1         4         5        1
    waited_q >= WAIT_CYCLES - 1         2         3        1

    expected for WAIT_CYCLES = 3:       3         4        1

Two of the three are wrong and all three completed. The +1 comparison is one clock slow; the -1 comparison is one clock early. Neither produces an error, a malformed signal, or anything a protocol checker would notice — they produce a transfer of the wrong length.

The -1 case is the dangerous one, because a slave that answers a clock early may be answering before its data is ready. That is BUG D wearing a different hat, arrived at from the counter rather than from the read path.

SIM G(b) — the same -1 comparison, at WAIT_CYCLES = 0.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM G(b) - the same -1 comparison at WAIT_CYCLES = 0 ===
    WAIT_CYCLES is `int unsigned`, so 0 - 1 is not -1.
    completed                0
    slave latency counter    60
    still presenting         1

The same line of code that was one clock wrong at WAIT_CYCLES = 3 hangs permanently at WAIT_CYCLES = 0.

The arithmetic. WAIT_CYCLES is declared int unsigned. 0 - 1 in unsigned arithmetic is 4294967295, not -1. The comparison waited_q >= 8'(4294967295) truncates to waited_q >= 8'hFF, and an 8-bit counter that resets on every non-presented clock never gets there. ready is never true; the transfer never terminates.

Why this is the module's best argument for sweeping parameters. A test suite that exercises WAIT_CYCLES = 3 finds a design that is one clock slow — annoying, probably survivable, quite possibly unnoticed. The same design at WAIT_CYCLES = 0 deadlocks the bus. The failure mode does not degrade gracefully with the parameter; it changes category.

And zero is exactly the value most likely to be omitted, because it feels like the trivial case. It is the one where the arithmetic breaks.

SIM H — three failures at the response boundary.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM H - three failures at the response boundary ===

    BUG D  slave acknowledges over data that is not ready
      completed                      1
      value the master captured      0x00000004
      value on DAT_I at termination  0x00000004
      same read, correct slave       0x00000005
      difference                     1 clock(s) of COUNT

    BUG F  slave never releases its acknowledge
      transfers the monitor counted  2
      completions the master reported 2
      second transfer: ACK already asserted when first presented  1
      wait clocks on the second transfer  0

    BUG E  acknowledge never arrives
      completed                      0
      still presenting               1
      slave latency counter          0
      master still busy              1

BUG D is the one that needs a reference to see. The master captured 0x00000004; the same read against a correct slave at the same latency returned 0x00000005. Exactly one clock of COUNT — which is the depth of the register the slave put in its read path.

Note what the second line rules out. The value on DAT_I at the terminating edge was also 0x00000004, so the master captured exactly what was presented. The master is not at fault, and questions 1 through 3 all pass. Only a comparison against something that knows the right answer finds this.

BUG F's evidence is the wait count, not the value. The second transfer completed with 0 wait clocks against a slave configured for 2, and the acknowledge was already asserted when it was first presented. A transfer that terminates faster than the slave's configured latency did not terminate on its own account — it collected somebody else's acknowledge.

And the master reported 2 completions for 2 requests, so nothing upstream noticed. The second read returned whatever the slave happened to be driving.

BUG E is a hang with its cause visible. Still presenting, master still busy, and the latency counter reading 0 after 40 clocks. The slave is not slow; it is not counting.

5. The Evidence Table

The seven bugs, arranged by the observation that identifies them.

ObservationWhat it meansBug
metadata changed while outstandingmaster violated RULE 3.60A, C
side effects > terminationsslave commits on presentationB
transfer count < transfers requestedsequencing on clocks, not completionsC
value differs from a reference slave, controls correctslave acknowledged over unready dataD
latency counter not progressingslave stuck, or request never arrivedE
wait count 0 against a non-zero-latency slavestale acknowledge from the previous transferF
wait count ≠ WAIT_CYCLESoff-by-one in the comparisonG
hang only at WAIT_CYCLES = 0unsigned underflow in the comparisonG

Read the left column as instrumentation requirements. Four of these eight observations are unavailable from the Wishbone signals alone: side-effect counts, the latency counter, and the reference comparison. A bus monitor is necessary and not sufficient, which is the thread this course has been collecting since Module 4 and which Chapter 5.8 §8 keeps.

What is cheap to add and repays itself. Expose the latency counter from every delayed slave, and count architectural side effects next to the register that produces them. Both are simulation-only outputs and both turn a multi-hour bisect into a single glance.

6. Common Mistakes

"A missing acknowledge means the slave is broken."

Wrong mental model: silence implicates the responder.

What is true: the request may never have reached it — wrong decode, wrong select, CYC_I not routed. The slave cannot answer a request it never saw.

Concrete bug: hours spent inside a slave whose address decode was never reached.

Observable evidence: the latency counter. Progressing means the slave has the request and is working. Stuck at 0 means either its own logic is broken or the request is absent — and the slave's STB_I says which.

Correct model: check whether the slave saw the request before checking what it did with it.

"An off-by-one costs a clock."

Wrong mental model: the error is proportional to the mistake.

What is true: measured, the same - 1 comparison is one clock early at WAIT_CYCLES = 3 and hangs forever at WAIT_CYCLES = 0, because unsigned 0 - 1 is 4294967295.

Concrete bug: a parameterised slave validated at its nominal latency and deployed with the fast configuration.

Observable evidence: SIM G against SIM G(b) — same RTL, one parameter apart.

Correct model: sweep the parameter, and include the endpoints. Zero is where the arithmetic breaks and the value most often skipped.

"The bus trace will show me what went wrong."

Wrong mental model: the bus is a complete record.

What is true: three of the seven bugs here are invisible on it. BUG B produced four side effects with a byte-identical trace. BUG D produced correct control signals and wrong data. BUG A produced a conformant-looking transfer to the wrong register.

Concrete bug: signing off on protocol-checker results.

Observable evidence: zero bus differences between a correct and a broken design.

Correct model: instrument the internal state that the bus does not carry — counters, commit strobes, and a reference.

"A transfer that completes quickly is fine."

Wrong mental model: fast is safe.

What is true: a transfer that terminates faster than the slave's configured latency did not terminate on its own account. BUG F's second transfer completed in 0 wait clocks against a 2-wait slave.

Concrete bug: a stale acknowledge from the previous transfer, which appears as a symptom on the transfer after the guilty one.

Observable evidence: measured wait count against configured latency.

Correct model: check that the wait count matches the parameter in both directions. Too few is as diagnostic as too many.

7. Interview Reasoning

I would work outwards from the slave's own progress, because one observation splits the problem in half.

First: is the slave's latency counter advancing? Every delayed slave I write exposes it for exactly this reason. A counter climbing towards its target means the slave has the request and is working — the transfer is slow, not stuck, and I go and look at why the target is taking so long.

A counter stuck at zero means the slave is not counting, and that has two very different causes which the next observation separates.

Second: is STB_I actually asserted at the slave? If it is, the slave has the request and its own logic is broken — I measured a case where the counter's reset condition was !ack_o instead of !xfer, so it cleared on every clock before the acknowledge and could never climb. If STB_I is not asserted at the slave, the request never arrived, and the fault is decode, select or routing — a different module's problem entirely.

Third, and only if those pass: is the address one this slave claims? An unmapped access to a slave with no default responder hangs in exactly this way, which Chapter 6.2 measured.

What I would not start with is the master. A master holding CYC_O and STB_O asserted with no termination is doing exactly what it is required to do — RULE 3.60 obliges it to hold, and there is no timeout in the protocol. It looks like the stuck component and it is the one behaving correctly.

And I would be explicit that recovery is a separate question. A watchdog belongs in the interconnect — RECOMMENDATION 3.10 suggests exactly that — but it is a system robustness feature, not part of the protocol, and adding one hides the fault rather than fixing it.

8. Understanding Check

9. What Module 9 Established

ChapterWhat it added
9.1why latency exists; WAIT_CYCLES defined and measured; identity is invariant
9.2two capture styles and what each costs; one write, one side effect
9.3what a master holds while it waits, and what a live client costs
9.4the extension matrix; invariants written as latency-free properties
9.5the same invariants as a debugging procedure (this chapter)

The module's central claim, measured on every page: a request stays presented for as long as the slave needs, and that is one transfer — not a request re-issued each clock. Latency changes duration and nothing else.

What the specification actually provides. One mechanism: the slave withholds the termination. There is no stall signal, no ready line, no credit. RULE 3.60 fixes the transaction's identity for the whole of the wait, RULE 3.65 ties the data to the termination, and RULE 3.50 ties the termination to STB_I in both directions. Everything in this module is a consequence of those three.

What Module 9 deliberately did not do. It never made a transfer fail. Every termination here was an ACK, every hang was a defect rather than a decision, and no design recovered from anything. Module 10 owns ERR_O, invalid accesses and bus watchdog timeouts; Module 11 owns retry; Module 12 address decoding; Module 13 byte lanes.

And one question this module has now raised twice without answering. BUG E hung the bus and nothing recovered it. Chapter 9.1 noted that a slave may insert any number of wait states — which means a conformant slave and a permanently stuck one are indistinguishable from the bus, at every finite moment.

What should a slave do when it cannot complete a transfer at all — and what should the system do when nothing answers?

Module 10 — Error Handling takes it up, beginning with the ERR signal. It has not shipped yet, which is why it appears in bold rather than as a link. 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.