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.
// 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// 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
endmoduleReading 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.
2. The Gallery
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 0x00000005 — exactly 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 cyclesCycles 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.
=== 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 1Two 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.
=== 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 1The 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.
=== 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 1BUG 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.
| Observation | What it means | Bug |
|---|---|---|
| metadata changed while outstanding | master violated RULE 3.60 | A, C |
| side effects > terminations | slave commits on presentation | B |
| transfer count < transfers requested | sequencing on clocks, not completions | C |
| value differs from a reference slave, controls correct | slave acknowledged over unready data | D |
| latency counter not progressing | slave stuck, or request never arrived | E |
| wait count 0 against a non-zero-latency slave | stale acknowledge from the previous transfer | F |
wait count ≠ WAIT_CYCLES | off-by-one in the comparison | G |
hang only at WAIT_CYCLES = 0 | unsigned underflow in the comparison | G |
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
| Chapter | What it added |
|---|---|
| 9.1 | why latency exists; WAIT_CYCLES defined and measured; identity is invariant |
| 9.2 | two capture styles and what each costs; one write, one side effect |
| 9.3 | what a master holds while it waits, and what a live client costs |
| 9.4 | the extension matrix; invariants written as latency-free properties |
| 9.5 | the 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
Related tutorials
- Related topic
Waveform Analysis
A nine-step method for reading an unfamiliar Wishbone read off a waveform, applied to five traces where each failure is localised from evidence rather than recognised from memory.
- Related topic
Waveform Analysis
A ten-step method for reading an unfamiliar Wishbone write off a waveform, applied to five traces — and the one question a bus trace can never answer about a write.
- Related topic
Transaction Lifecycle
One Wishbone transaction from a master's decision to act through the slave's termination and back to the caller: what is fixed by the protocol, what every implementation may vary, and what a real simulation of the assembled system shows at each step.
- Related topic
STB — Strobe
A transfer is presented for as long as the master waits and accepted in exactly one cycle. A slave that confuses the two performs its write once per waiting cycle, on a bus that stays perfectly conformant.
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.
