Wishbone · Module 10
Error Responses
A master that folds ACK or ERR into success reports a failed write as successful over a byte-identical bus. A slave that asserts both classes at once is caught by a checker in one clock.
Chapter 10.1 settled the taxonomy and gave one master and one slave that each handled it correctly. Neither was tested under pressure.
How should RTL generate an error, and what does it cost when a master consumes one carelessly?
1. The Slave Side — Deciding, Once
Chapter 10.1's slave computes ack_o and err_o as complements of one illegal term. That is worth stating as a design rule rather than an implementation detail.
Everything that depends on the decision must be derived from the decision. In wb_reg_slave_err three things do: the acknowledge, the error, and the write commit. They cannot disagree, because there is only one of them.
The alternative is what Section 3 builds. A slave that grows error reporting by adding an err_o expression beside an existing ack_o expression has two decisions where it needs one — and nothing forces them to be complementary.
What Wishbone actually requires of the slave here is narrow:
RULE 3.35 — the termination is generated in response to the logical AND of CYC_I and STB_I. An error is qualified exactly like an acknowledge.
RULE 3.45 — a slave that supports ERR_O or RTY_O must not assert more than one of the three at any time.
RULE 3.50 — the termination signals are asserted and negated in response to the assertion and negation of STB_I, which Chapter 9.5 measured the consequence of violating.
What Wishbone does not require is which conditions produce an error. That is the supplier's, and RULE 2.15 makes publishing it mandatory. This module's slave errors on an unimplemented offset and on a write to a read-only register; a different peripheral could acknowledge both and be equally conformant.
And the obligation that is not a numbered rule at all: an errored transfer must change nothing. It follows from what the termination communicates rather than from any clause, which is why Chapter 4.11 §2 states it as a design obligation and this module's slave enforces it structurally.
2. The Master Side — Classifying, Not Just Detecting
A master needs two different answers from the same three wires, and the expressions for them are not the same.
has this transfer ended? ack_i || err_i || rty_i ← correct
did this operation succeed? ack_i ← correct
did this operation succeed? ack_i || err_i || rty_i ← the bugThe first expression is right, and every master in Modules 5 through 9 used it — for termination detection, which is what it is for. The defect is reusing it one line later for a question it does not answer.
Why that is an easy mistake to make. The termination expression is already sitting in the module, already correct, already named something like terminated. Reaching for it again reads as reuse rather than as error.
And the bus gives no feedback. A master that misclassifies still presents correctly, holds its metadata stable, releases at the right edge and issues one completion per request. Everything a protocol checker examines is fine.
3. RTL — Two Broken Designs and a Checker
// wb_master_err_as_ack — THE BUG, isolated. NOT A REFERENCE DESIGN.
//
// Identical to wb_error_aware_master except that the outcome is computed
// from the wrong expression:
//
// if (ack_i) ok_o <= 1'b1; becomes ok_o <= 1'b1;
//
// applied unconditionally at the terminating edge. The master has folded
// "the transfer terminated" into "the operation succeeded" — using the
// expression that is correct for the first question to answer the second.
//
// WHY THIS IS NOT AN OBVIOUS MISTAKE. `ack_i || err_i || rty_i` is exactly
// the right expression for "has this transfer ended", and every master in
// Modules 5 to 9 used it for precisely that. The defect is reusing it one
// line later for a different question.
//
// WHAT IS STILL CORRECT. Everything on the bus. The master presents, holds
// its metadata stable, releases at the termination, and issues exactly one
// completion per request. A protocol checker watching its pins passes it,
// because nothing it does on the bus is wrong.
//
// It also captures read data on any termination, so the client receives
// whatever the slave happened to be driving when it reported failure.
// ─────────────────────────────────────────────────────────────────────────
module wb_master_err_as_ack #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32
) (
input logic clk_i,
input logic rst_i,
input logic req_i,
input logic req_we_i,
input logic [AW-1:0] req_adr_i,
input logic [DW-1:0] req_dat_i,
input logic [DW/8-1:0] req_sel_i,
output logic busy_o,
output logic done_o,
output logic ok_o,
output logic err_o,
output logic rty_o,
output logic [DW-1:0] rdat_o,
output logic cyc_o,
output logic stb_o,
output logic we_o,
output logic [AW-1:0] adr_o,
output logic [DW-1:0] dat_o,
output logic [DW/8-1:0] sel_o,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic { S_IDLE, S_XFER } state_e;
state_e state_q;
logic [AW-1:0] adr_q;
logic [DW-1:0] dat_q;
logic [DW/8-1:0] sel_q;
logic we_q;
logic terminated;
assign cyc_o = (state_q == S_XFER);
assign stb_o = (state_q == S_XFER);
assign adr_o = adr_q;
assign dat_o = dat_q;
assign sel_o = sel_q;
assign we_o = we_q;
assign busy_o = (state_q != S_IDLE);
assign terminated = cyc_o && stb_o && (ack_i || err_i || rty_i);
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE;
adr_q <= '0; dat_q <= '0; sel_q <= '0; we_q <= 1'b0;
done_o <= 1'b0; ok_o <= 1'b0; err_o <= 1'b0; rty_o <= 1'b0; rdat_o <= '0;
end else begin
done_o <= 1'b0; ok_o <= 1'b0; err_o <= 1'b0; rty_o <= 1'b0;
case (state_q)
S_IDLE: if (req_i) begin
adr_q <= req_adr_i; dat_q <= req_dat_i;
sel_q <= req_sel_i; we_q <= req_we_i;
state_q <= S_XFER;
end
S_XFER: if (terminated) begin
// ── THE BUG: the termination class is never examined. ───────────
ok_o <= 1'b1;
if (!we_q) rdat_o <= dat_i; // ... and the data is taken too
done_o <= 1'b1;
state_q <= S_IDLE;
end
default: state_q <= S_IDLE;
endcase
end
end
endmodule// wb_slave_double_term — THE BUG, isolated. NOT A REFERENCE DESIGN.
//
// A slave that asserts ACK_O and ERR_O together on an illegal access.
//
// The mistake it models is a plausible one: the designer added error
// reporting to a working slave by adding an err_o expression, and left the
// existing ack_o expression unchanged. Each line reads correctly on its
// own; nobody checked that they are mutually exclusive.
//
// This violates RULE 3.45 — a SLAVE that supports ERR_O or RTY_O MUST NOT
// assert more than one of ACK_O / ERR_O / RTY_O at any time.
//
// WHY THIS ONE IS DIFFERENT FROM MODULE 9's BUGS. Every defect in Module 9
// was invisible on the bus: the repeating-commit slave produced a
// byte-identical trace to the correct one. This defect IS on the bus, on
// two wires, at one edge. A protocol checker catches it in one clock.
// ─────────────────────────────────────────────────────────────────────────
module wb_slave_double_term #(
parameter int unsigned OFF_AW = 4,
parameter int unsigned DW = 32
) (
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
);
localparam logic [OFF_AW-1:0] O_ID = 4'd4;
localparam logic [DW-1:0] ID_VALUE = 32'h5742_1001;
logic xfer, mapped;
assign xfer = cyc_i && stb_i;
assign mapped = (adr_i == O_ID);
// ── THE BUG: ack_o does not exclude the illegal case, so on an illegal
// access BOTH are asserted at the same edge.
assign ack_o = xfer;
assign err_o = xfer && !mapped;
assign dat_o = (xfer && !we_i && mapped) ? ID_VALUE : 32'hBAD0_BAD0;
endmodule
// wb_term_checker — a bus-level protocol checker for termination legality.
//
// SIMULATION ONLY. Not synthesisable, not part of any interface.
//
// It watches one slave port and counts violations of the two rules that
// govern how a termination may appear. Unlike the internal instrumentation
// Module 9 needed, everything here is visible on the wires — which is the
// point of the contrast.
// ─────────────────────────────────────────────────────────────────────────
module wb_term_checker (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic ack_i,
input logic err_i,
input logic rty_i,
output int unsigned terminations_o, // clocks with any class asserted
output int unsigned multi_class_o, // RULE 3.45 violations
output int unsigned unqualified_o // RULE 3.35 violations
);
logic xfer, any_term;
assign xfer = cyc_i && stb_i;
assign any_term = ack_i || err_i || rty_i;
always_ff @(posedge clk_i) begin
if (rst_i) begin
terminations_o <= 0; multi_class_o <= 0; unqualified_o <= 0;
end else begin
if (xfer && any_term) terminations_o <= terminations_o + 1;
// RULE 3.45: at most one class at a time. Counted whenever more than
// one is asserted, qualified or not.
if ((ack_i + err_i + rty_i) > 2'd1) multi_class_o <= multi_class_o + 1;
// RULE 3.35: a termination is generated in response to the AND of
// CYC_I and STB_I. A class asserted without a presented transfer is
// a termination for a request that was never made.
if (any_term && !xfer) unqualified_o <= unqualified_o + 1;
end
end
endmoduleReading the group
wb_master_err_as_ack differs from the correct master by removing an if. The classification arm becomes unconditional: ok_o <= 1'b1 at every termination, and rdat_o captured at every termination too. Nothing else changed — same state machine, same latched metadata, same release, same single completion.
wb_slave_double_term differs by one missing term. assign ack_o = xfer; should have excluded the illegal case. The error expression beside it is correct on its own. Two expressions, neither obviously wrong, not complementary.
wb_term_checker counts two things and both are computable from the wires. multi_class_o counts clocks with more than one class asserted — RULE 3.45. unqualified_o counts a class asserted without CYC_I && STB_I — RULE 3.35.
Note what the checker deliberately does not check. Whether the class was the right class. No bus-level checker can: which of ACK and ERR a legal access deserves is the slave's policy, and the policy lives in a datasheet rather than on a wire.
Note also that the master cannot defend itself against the slave. wb_error_aware_master tests ack_i first, so against a double-terminating slave it takes the optimistic reading. That is a choice, and Section 6 measures its consequence — but no ordering of those tests is safe. Testing err_i first would report failure for an access that may have succeeded. The only correct response to a RULE 3.45 violation is to find it and fix it, which is what the checker is for.
4. Waveform — Two Classes at One Edge
A RULE 3.45 violation, and what it costs
9 cyclesCycle 2 is the violation, and it is one clock wide. ACK_I and ERR_I are both asserted, both qualified by CYC_O and STB_O. RULE 3.45 forbids exactly this, and there is nothing subtle about the evidence — two signals, one edge.
Cycle 3 is the damage. The master took the acknowledge, reported done and ok, and captured 0xBAD0BAD0 — the placeholder this slave drives for an unmapped offset. A conformant master delivered a garbage value to its client and called it a success.
The master is not at fault and cannot fix this. It examined the class; the class was ambiguous. Whichever signal it tested first, some access would be misreported.
The violations row is the point of the figure. It moves at cycle 3, one clock after the violation, because the checker registers its count. A bus-level checker catches this in a single transfer, without knowing anything about the peripheral's policy, the address map, or what the client expected.
Contrast that with Chapter 9.2's repeating-commit slave, which produced a byte-identical trace to the correct design across every clock. Same module, same kind of instrument, opposite result — and the difference is whether the defect appears on a wire.
5. Simulation — SIM B and SIM C
SIM B — one failed access, two masters. Same slave, same stimulus: a write to the read-only STATUS register, which this slave's policy answers with ERR.
=== SIM B - one failed access, two masters ===
write word 0 (STATUS, read-only) - this slave's policy is ERR
master ERR on bus client ok client err
wb_error_aware_master 1 0 1
wb_master_err_as_ack 1 1 0
bus-signal differences between the two rigs 0
RULE 3.45 violations seen by checker 0 / 0Both masters saw the same ERR on the bus. Both rigs report ERR on bus = 1, and bus-signal differences between the two rigs = 0 — the testbench compares CYC_O, STB_O, WE_O, ADR_O, ACK_I and ERR_I on every clock and finds no disagreement.
The client verdicts are opposite. The correct master reports err = 1, ok = 0. The broken one reports ok = 1, err = 0. A write that the slave refused was reported upstream as having succeeded.
RULE 3.45 violations = 0 / 0. Neither bus is malformed. There is nothing for a protocol checker to find, because the protocol was not violated — the master simply told its client something the bus never said.
And the register did not change, in either rig. The slave suppressed the write, correctly. So the system is now in a state where software believes a configuration write landed and the hardware disagrees, with no error logged anywhere and no trace evidence that anything went wrong.
That is the whole cost of the missing if, and it is worth naming precisely: the defect is not on the bus, it is at the boundary between the bus and the client. A checker watches the first; only the client contract covers the second.
SIM C — a slave asserting two termination classes.
=== SIM C - a slave asserting two termination classes ===
read word 7 from wb_slave_double_term
terminations observed 1
RULE 3.45 violations (ACK+ERR) 1
RULE 3.35 violations 0
correct master reported ok=1 err=0
value delivered to client 0xbad0bad0One termination observed, and one RULE 3.45 violation. The checker counted the malformed edge without knowing anything about the peripheral.
The master reported ok = 1 and delivered 0xbad0bad0 — the slave's placeholder for an unmapped offset, handed to the client as a successful read result.
RULE 3.35 violations = 0. The malformed termination was correctly qualified: CYC_I and STB_I were both asserted. The slave got the qualification right and the exclusivity wrong, which is why counting the two separately is worth the extra register.
The contrast, stated plainly
| SIM B — master misclassifies | SIM C — slave double-terminates | |
|---|---|---|
| Bus conformant? | yes, entirely | no — RULE 3.45 |
| Protocol checker finds it? | no | yes, in one clock |
| Client told the truth? | no | no |
| Who can fix it? | the master | only the slave |
| Evidence needed | client contract instrumentation | the two wires |
Both produce the same top-level symptom — software believes a failed access succeeded. The evidence required to find them is completely different, and that is the practical reason error handling needs both kinds of check.
The generalisation this module adds to the course's running thread. Chapter 5.8 §8 has been collecting defects that sit on fully conformant buses since Module 4. SIM C is the first one in nine modules that a bus-level checker actually catches — and it is worth knowing that the category exists, because it means "the checker is green" and "the checker is useless" are both wrong as general claims.
6. Failure Modes and Discriminating Evidence
Symptom: software reports success and the hardware disagrees.
Candidate causes. Two, and one observation separates them.
Discriminating evidence. Count client successes against ACK terminations on the bus. If successes exceed acknowledges, the master is misclassifying — SIM B. If they match but the operation still did not happen, look inside the slave: it acknowledged and did nothing, which is Chapter 9.2's territory.
Likely RTL location. The outcome assignment, using the termination expression instead of ack_i.
Symptom: a read returns a recognisable placeholder value.
Candidate causes. Data captured from an errored termination.
Discriminating evidence. The value matches whatever the slave drives on error — 0xBAD0BAD0 here, all-zeros in the correct slave. A value that is obviously a marker rather than data means the capture happened on the wrong class.
Why the marker choice matters: a slave that drives a plausible value on error makes this undetectable. Driving a deliberately implausible one is a debugging courtesy, not a requirement.
Symptom: a protocol checker reports exclusivity violations.
Candidate causes. A slave with independent ack_o and err_o expressions.
Discriminating evidence. Conclusive on its own. RULE 3.45 is a slave obligation and the count names the port. No further triage is needed — this is one of the rare cases where the checker output is the diagnosis.
Likely RTL location. An ack_o expression that was never narrowed when error reporting was added.
Symptom: an access fails intermittently and the master reports success.
Candidate causes. A double-terminating slave whose illegal condition is data-dependent, combined with a master that tests ack_i first.
Discriminating evidence. The checker's violation count against the failure rate. They should track. A master-side fix will not help, and attempting one produces the mirror-image bug.
7. Verification
// Properties for termination legality and for the client contract. The
// split between them is the subject of this chapter: the first two can be
// checked from the bus, the last three cannot.
//
// 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 Section 5 come from procedural checks, which Icarus does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_error_response_props (
input logic clk_i, rst_i,
input logic cyc_i, stb_i,
input logic ack_i, err_i, rty_i,
input logic done_i, ok_i, err_rep_i,
input logic ack_seen_i // an ACK terminated the transfer
);
default clocking cb @(posedge clk_i); endclocking
default disable iff (rst_i);
logic xfer;
assign xfer = cyc_i && stb_i;
// R1 — SPECIFICATION (RULE 3.45). At most one termination class at a
// time. Checkable from the bus alone; this is the property SIM C's
// slave fails and the checker counts.
R1_exclusive: assert property ( $onehot0({ack_i, err_i, rty_i}) );
// R2 — SPECIFICATION (RULE 3.35). A termination is generated in response
// to the AND of CYC_I and STB_I. Also checkable from the bus alone.
R2_qualified: assert property ( (ack_i || err_i || rty_i) |-> xfer );
// R3 — LOCAL CONTRACT. Client success is reported only when an ACK
// terminated the transfer. This is the property SIM B's master
// fails, and NO bus-level checker can express it: `ok_i` is not a
// Wishbone signal and the correct value depends on a class the
// master may simply have ignored.
R3_success_needs_ack: assert property ( ok_i |-> ack_seen_i );
// R4 — LOCAL CONTRACT. Failure is reported for a failed transfer, so a
// misclassification cannot hide by reporting neither outcome.
R4_failure_reported: assert property ( done_i |-> (ok_i ^ err_rep_i) );
// R5 — LOCAL CONTRACT. Exactly one completion per terminated transfer.
R5_one_completion: assert property (
done_i |-> $past(xfer && (ack_i || err_i || rty_i))
);
endmoduleR1 and R2 are the specification's, and they are the reason a checker exists. Both are functions of three wires and a qualification; neither needs to know the address map, the peripheral's policy or the client's expectations.
R3 is the chapter's central property and it is emphatically local. ok_i is not a Wishbone signal. A bus monitor cannot write this property, because the thing it constrains does not appear on the bus — which is precisely why SIM B's defect survives a green checker.
R4 exists to close an escape route. A master could satisfy R3 by reporting neither outcome, leaving the client to infer. R4 forbids the silence, and together the two force every completion to carry exactly one honest verdict.
What none of these can establish is whether the slave chose the right class for a given access. That is policy, it lives in a datasheet under RULE 2.15, and checking it requires a model of the peripheral rather than a model of the bus.
8. Common Mistakes
"ack_i || err_i is a fine success condition — some termination happened."
Wrong mental model: termination and success are the same event.
What is true: the expression is correct for "has this ended" and wrong for "did this work". Every master in Modules 5 to 9 used it legitimately for the first question.
Concrete bug: wb_master_err_as_ack. Measured: a write the slave refused, reported upstream as ok = 1, over a bus with zero differences from the correct rig's.
Observable evidence: client successes exceeding bus ACK count. Nothing on the bus.
Correct model: two questions, two expressions. Detect with all three classes; classify with ack_i.
"A protocol checker would have caught it."
Wrong mental model: conformance implies correctness.
What is true: it catches SIM C and cannot catch SIM B. The second defect leaves a perfectly legal bus.
Concrete bug: signing off error handling on checker results.
Observable evidence: RULE 3.45 violations = 0 on a rig that reports failures as successes.
Correct model: bus checkers cover bus obligations. The client contract needs its own properties, written against signals the bus does not carry.
"The master should decide sensibly if a slave asserts two classes."
Wrong mental model: the master can compensate.
What is true: no ordering is safe. Testing ack_i first reports success for a failed access — measured, with 0xbad0bad0 delivered as a result. Testing err_i first reports failure for an access that may have succeeded.
Concrete bug: "hardening" a master against a slave that violates RULE 3.45, which hides the violation and trades one wrong answer for another.
Observable evidence: the checker's violation count, which does not move when the master changes.
Correct model: fix the slave. The master's job is to notice, not to guess.
"An errored access can still update the register — the master knows it failed."
Wrong mental model: the error report is enough.
What is true: the master learns the transfer failed and has no way to discover that state changed anyway. The slave has told it something untrue.
Concrete bug: independent err_o and commit conditions that disagree on some access.
Observable evidence: register state changing on a transfer whose termination was ERR — an internal check, not a bus one.
Correct model: derive the error and the commit from one term, as wb_reg_slave_err does with illegal.
9. Interview Reasoning
Because it answers "did the transfer end" and gets reused to answer "did the operation succeed", and those have different answers.
The first question is about the handshake. All three classes end a transfer, so ack_i || err_i || rty_i is exactly right for deciding when to release the bus, capture nothing further, and let the client issue its next request. Every master I have written uses it for that.
The second question is about the outcome, and only ack_i answers it.
Why the mistake is easy. The termination expression is already in the module, already correct, already named terminated. Reaching for it a second time reads as reuse, not as a bug — and there is no compiler or linter that objects.
What it costs, measured. I ran the same failed write — to a read-only register, which that slave's policy answers with ERR — through a correct master and one that folds the classes. The buses were byte-identical: zero differences across every clock. The correct master reported failure; the broken one reported success. The register was not written in either case, so software now believes a configuration write landed and the hardware disagrees, with nothing logged.
And the part I would emphasise in review: a protocol checker passes the broken rig. RULE 3.45 violations = 0, RULE 3.35 violations = 0 — nothing about the bus is wrong. The defect lives at the boundary between the bus and the client, which is a place bus checkers do not look.
So the design rule I would state. Detect termination with all three classes; classify the outcome with one. And write a property for the client contract, because that is the only thing that covers this — ok is not a Wishbone signal and no bus monitor can constrain it.
10. Understanding Check
11. What's Next
Generation and consumption are both covered, and both failure modes measured — one that a checker finds, one that only a client contract does.
Every error so far came from the same place: the selected peripheral decided it could not serve the access. That is one source among several, and the others behave differently enough to be worth separating.
What happens when the address does not belong to that peripheral — or to any peripheral at all?
Chapter 10.3 — Invalid Accesses distinguishes a local invalid offset from a globally unmapped address, and builds the responder that decides what the second one means. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Need for Standardized Interconnects
An address map answers where a register lives. It says nothing about which wires carry the request, when they are valid, how the target reports completion, or what happens on an error. Three peripherals with three private interfaces produce three adapters, three verification efforts and three ways to be wrong — which is the argument for standardising the interface rather than the map.
- Related topic
SoC Communication
Six chapters built the pieces; this one assembles them into a working fabric and traces three real accesses through it. The result works, and reading the nine unwritten rules a third party would need is what makes the case for a published protocol concrete rather than theoretical.
- 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
RST_I
Wishbone's reset is synchronous and active high, and initialisation happens at the clock edge after assertion rather than at assertion: what RULES 3.00 and 3.20 require, and what an asynchronous reset breaks.
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.
