Wishbone · Module 5
ACK — Acknowledge
The slave's half of the handshake: a response rather than an advertised readiness, sampled as a level, with RULE 3.55 constraining the master's internal design rather than any wire it drives.
Chapter 4.10 established what ACK means and that it is the only mandatory termination. Chapter 5.3 showed it doing a second job: marking the one cycle in which a presented transfer is accepted.
How does the slave end the transfer, and what must the master do at that edge?
1. Two Ways to Build an Acknowledge
The specification permits both, and names the tradeoff itself.
Combinational — asserted in the same cycle the transfer is presented.
PERMISSION 3.30 — "The assertion of [ACK_O], [ERR_O], and [RTY_O] MAY be asynchronous to the [CLK_I] signal (i.e. there is a combinatorial logic path between [STB_I] and [ACK_O])."
OBSERVATION 3.40 — "The asynchronous assertion of [ACK_O], [ERR_O], and [RTY_O] assures that the interface can accomplish one data transfer per clock cycle."
Registered — asserted one or more cycles after.
OBSERVATION 3.45 — "The asynchronous assertion ... could proof impossible to implement. For example slave wait states are easiest implemented using a registered [ACK_O] signal."
Neither is better. The combinational form buys one transfer per clock; the registered form costs a cycle and buys timing closure. Chapter 5.7 develops the tradeoff with OBSERVATION 3.50's loopback delay, which is the reason large systems often cannot use the fast form.
What matters here is that the master cannot tell which it is talking to, and must work with both. That is the practical content of RULE 3.55.
2. Level, Not Edge
This is the chapter's central engineering point.
The master's job at every edge is to ask one question: is a termination asserted right now, while my transfer is presented? Not did the acknowledge change.
if (active_q && ack_i) begin ... end // correct: a level, per transfer
if ($rose(ack_i)) begin ... end // wrong: assumes a gapWhy the edge version looks reasonable. "Wait for the acknowledge" is how the handshake is described in words, and edge detection is the natural translation. The bug is structural, not careless — which is why the specification needed a rule for it.
3. What a Master Must Do At the Termination Edge
Four things happen at the edge where ACK_I is sampled asserted, and their order in the RTL does not matter but their presence does.
Capture read data, if this was a read. RULE 3.65 qualifies the slave's DAT_O with its termination, so this edge is the only opportunity. Chapter 4.5 traced the off-by-one that follows from missing it.
Record that this transfer completed, so the master can report it once and only once.
Release, or advance. A single-transfer master negates CYC_O and STB_O. A block master negates STB_O and moves the address on.
Do not capture on ERR_I or RTY_I. There is no defined read value on an error and nothing happened on a retry.
Chapter 5.6 takes the completion edge apart in full; this chapter needs only that the edge exists and is sampled as a level.
4. RTL — An Always-Acknowledging Slave and a Master That Survives It
// ─────────────────────────────────────────────────────────────────────────
// wb_ack_fast_slave — the PERMISSION 3.10 slave, exactly.
//
// PERMISSION 3.10 — "If the SLAVE guarantees it can keep pace with all
// MASTER interfaces and if the [ERR_I] and [RTY_I] signals are not used,
// then the SLAVE's [ACK_O] signal MAY be tied to the logical AND of the
// SLAVE's [STB_I] and [CYC_I] inputs."
//
// Both preconditions hold here: it never waits, and it has no ERR_O/RTY_O.
// Adding an error output would move it under PERMISSION 3.15 instead.
//
// Against a master that holds STB_O across transfers, this slave's ACK_O
// NEVER FALLS. That is conformant (PERMISSION 3.35) and is what RULE 3.55
// requires masters to tolerate.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00).
// ─────────────────────────────────────────────────────────────────────────
module wb_ack_fast_slave #(
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 [7:0] accepts_o // side-effect count, for QA
);
localparam int unsigned NL = DW/8;
logic [DW-1:0] mem_q [16];
logic xfer;
assign xfer = cyc_i & stb_i; // RULES 3.30 & 3.35
// PERMISSION 3.10, literally. Note this is NOT ack_o = 1'b1: a tied-high
// acknowledge would terminate transfers never presented to this slave,
// violating RULE 3.35 — the distinction Chapter 5.1 Section 1 drew.
assign ack_o = xfer;
// Accepted == presented here, because this slave never waits. The ack_o
// term is still written explicitly, for the reason Chapter 5.3 gave: it
// is redundant now and load-bearing the moment a wait state appears.
logic write_ok;
assign write_ok = xfer & we_i & ack_o;
always_ff @(posedge clk_i) begin
if (rst_i) begin
for (int unsigned i = 0; i < 16; i++) mem_q[i] <= '0;
accepts_o <= '0;
end else if (write_ok) begin
for (int unsigned n = 0; n < NL; n++) begin
if (sel_i[n]) mem_q[adr_i][n*8 +: 8] <= dat_i[n*8 +: 8];
end
accepts_o <= accepts_o + 8'd1;
end
end
always_comb begin
dat_o = '0; // RULE 3.65
if (xfer && !we_i && ack_o) dat_o = mem_q[adr_i];
end
endmodule// ─────────────────────────────────────────────────────────────────────────
// wb_ack_block_master — holds STB_O across N transfers and counts
// terminations by LEVEL. This is the RULE 3.55-compliant shape.
//
// It exists to be pointed at wb_ack_fast_slave, where ACK_I is asserted
// continuously and never falls. Counting levels gets N; counting edges
// gets 1. The two differ by one line, and Section 7 simulates both.
//
// SCOPE NOTE. Holding STB_O across several transfers is a BLOCK cycle and
// Module 8 owns it properly. It appears here only because it is the
// simplest construction that makes ACK_I stay asserted, which is what
// RULE 3.55 is about.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00, 3.20).
// ─────────────────────────────────────────────────────────────────────────
module wb_ack_block_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned CW = 8
) (
input logic clk_i,
input logic rst_i,
input logic req_i,
input logic [AW-1:0] base_i,
input logic [CW-1:0] count_i,
output logic done_o,
output logic [CW-1:0] acked_o, // transfers completed
output logic [DW-1:0] last_dat_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
);
logic active_q;
logic [AW-1:0] adr_q;
logic [CW-1:0] left_q;
assign cyc_o = active_q; // RULE 3.25: spans the block
assign stb_o = active_q; // held across all transfers
assign we_o = 1'b0; // read block
assign adr_o = adr_q;
assign dat_o = '0;
assign sel_o = '1;
always_ff @(posedge clk_i) begin
if (rst_i) begin
active_q <= 1'b0; // RULE 3.20
adr_q <= '0;
left_q <= '0;
acked_o <= '0;
last_dat_o <= '0;
done_o <= 1'b0;
end else begin
done_o <= 1'b0;
if (!active_q) begin
if (req_i && (count_i != '0)) begin
active_q <= 1'b1;
adr_q <= base_i;
left_q <= count_i;
acked_o <= '0;
end
end else if (ack_i) begin
// ── RULE 3.55 COMPLIANCE, IN ONE LINE ─────────────────────────
// ack_i is read as a LEVEL, every cycle the transfer is presented.
// Nothing here asks whether it CHANGED, so a slave holding it
// asserted across all N transfers is counted correctly.
//
// The bug this avoids: if ($rose(ack_i)) ...
// which sees one acknowledge instead of N and never finishes.
last_dat_o <= dat_i; // RULE 3.65 window
acked_o <= acked_o + CW'(1);
if (left_q == CW'(1)) begin
active_q <= 1'b0; // last transfer: release
done_o <= 1'b1;
end else begin
left_q <= left_q - CW'(1);
adr_q <= adr_q + AW'(1);
end
end else if (err_i || rty_i) begin
active_q <= 1'b0;
done_o <= 1'b1;
end
end
end
endmodule// ─────────────────────────────────────────────────────────────────────────
// wb_ack_edge_master — THE RULE 3.55 VIOLATION, isolated.
//
// NOT A REFERENCE DESIGN. Identical to wb_ack_block_master except that it
// advances on a RISING EDGE of ack_i instead of on the level. Against a
// slave that ever negates ACK_I between transfers it works perfectly.
// Against wb_ack_fast_slave it stalls after one transfer, with STB_O and
// ACK_I both asserted — a waveform that looks like success.
// ─────────────────────────────────────────────────────────────────────────
module wb_ack_edge_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned CW = 8
) (
input logic clk_i,
input logic rst_i,
input logic req_i,
input logic [AW-1:0] base_i,
input logic [CW-1:0] count_i,
output logic done_o,
output logic [CW-1:0] acked_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
);
logic active_q;
logic [AW-1:0] adr_q;
logic [CW-1:0] left_q;
logic ack_q; // previous ack_i
assign cyc_o = active_q;
assign stb_o = active_q;
assign we_o = 1'b0;
assign adr_o = adr_q;
assign dat_o = '0;
assign sel_o = '1;
// ── THE BUG ───────────────────────────────────────────────────────────
// A rising edge of ack_i. RULE 3.55 requires a master to operate normally
// when a slave HOLDS ACK_I asserted; this one cannot, because a held
// acknowledge produces exactly one rising edge no matter how many
// transfers complete.
logic ack_rise;
assign ack_rise = ack_i & ~ack_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
active_q <= 1'b0;
adr_q <= '0;
left_q <= '0;
ack_q <= 1'b0;
acked_o <= '0;
done_o <= 1'b0;
end else begin
done_o <= 1'b0;
ack_q <= ack_i;
if (!active_q) begin
if (req_i && (count_i != '0)) begin
active_q <= 1'b1;
adr_q <= base_i;
left_q <= count_i;
acked_o <= '0;
end
end else if (ack_rise) begin
acked_o <= acked_o + CW'(1);
if (left_q == CW'(1)) begin
active_q <= 1'b0;
done_o <= 1'b1;
end else begin
left_q <= left_q - CW'(1);
adr_q <= adr_q + AW'(1);
end
end else if (err_i || rty_i) begin
active_q <= 1'b0;
done_o <= 1'b1;
end
end
end
endmoduleReading the group
Purpose. The slave produces a legally-held acknowledge. One master survives it; the other does not.
Interface. The masters take a base address and a count and report how many transfers completed — acked_o is what makes the failure countable.
State. Masters: active flag, address, remaining count. The broken one adds ack_q, and that register is the bug.
Combinational behaviour. Slave: xfer, ack_o = xfer, the read multiplexer. Masters: qualifiers from active_q; the broken one also computes ack_rise.
Sequential behaviour. The good master advances whenever ack_i is asserted; the broken one only on a rising edge.
Request start. Both assert CYC_O and STB_O together at the edge following req_i, per PERMISSION 3.40 — the block holds them for its whole duration.
Waiting. Against this slave there is no waiting; every transfer terminates in the cycle it is presented.
Termination. Level-sampled in the good master. Edge-detected in the broken one, which is the RULE 3.55 violation.
Read data. last_dat_o captured in the acknowledged cycle, per RULE 3.65.
Reset. Synchronous, active high; active_q <= 0 negates both qualifiers per RULE 3.20.
Failure modes. Section 6, and wb_ack_edge_master is one.
Simplifications. Read-only blocks. The block construction itself belongs to Module 8; it appears here only as the simplest way to hold ACK_I asserted.
5. Waveform — One Rising Edge, Three Transfers
ACK held asserted: level counting works, edge counting does not
7 cyclesThere is exactly one rising edge on ACK_I and three transfers complete. That is RULE 3.55 in a single picture.
The slave is doing nothing exotic. ack_o = cyc_i & stb_i, precisely as PERMISSION 3.10 allows, and the qualified transfer never stopped being true.
The acked (bad) trace flattens at 1. In a real design that master then stalls with STB_O and ACK_I both high — and an engineer looking at the waveform sees an asserted acknowledge and concludes the transfer succeeded.
6. Failure Modes and Discriminating Evidence
Symptom: a multi-transfer operation stalls after the first transfer, with STB_O and ACK_I both asserted.
Candidate causes. The master edge-detects ACK_I — a RULE 3.55 violation — against a slave whose acknowledge does not fall.
Discriminating evidence. ACK_I asserted continuously while the master makes no progress. This is the one hang signature where the acknowledge is high; every other hang in this module shows it low. That single bit is conclusive.
Likely RTL location. The master's completion condition — $rose, an edge-detect flop, or a wait-for-negation state.
Property. P3 in Section 8.
Symptom: a block completes with fewer transfers than requested, and no error.
Candidate causes. The same edge detection, in a master whose exit condition is driven by something other than the count.
Discriminating evidence. Count the cycles where CYC & STB & ACK were all true and compare with the master's acked_o. A mismatch localises the fault to the master immediately.
Symptom: a slave acknowledges transfers addressed to other slaves.
Candidate causes. ACK_O tied high rather than tied to the qualified transfer.
Discriminating evidence. Check ACK_O while CYC_I and STB_I are low. Asserted is a direct RULE 3.35 violation.
Likely RTL location. The slave's ack_o assignment. The fix is PERMISSION 3.10's form — tied to the AND, not tied high.
Property. P1.
Symptom: the design works with one peripheral and hangs with another that is nominally equivalent.
Candidate causes. A master that happens to work against a registered-ACK slave (whose acknowledge does fall between transfers) and fails against a combinational one (whose does not).
Discriminating evidence. Compare ACK_I between the two slaves across consecutive transfers. If it falls with one and not the other, the master's edge dependence is exposed. Both slaves are conformant; the master is not.
Symptom: ACK_O remains asserted after the master drops STB_O.
Candidate causes. A registered acknowledge with no clearing path — a RULE 3.50 violation, contradicting OBSERVATION 3.10's "automatically negate".
Discriminating evidence. ACK_O high with STB_I low. In a shared fabric this corrupts the next master's transfer, so the symptom surfaces somewhere else entirely.
Property. P2.
7. Simulation — Level Versus Edge, Measured
Both masters were run against wb_ack_fast_slave requesting a three-transfer block, observed over a 40-cycle window:
level edge
transfers requested 3 3
cycles CYC&STB&ACK true 3 40
rising edges on ACK_I 1 1
TRANSFERS COUNTED 3 1
completed yes STALLED
slave still presenting? no yesRead the cycles CYC&STB&ACK true row. The compliant master saw the qualified-and-acknowledged condition three times — once per transfer — and released the bus. The edge master saw it 40 times and counted 1, because the window ended before it did; left alone it would sit there indefinitely.
That row is the stall signature in numeric form. STB_O still asserted, ACK_I still asserted, no progress. Both masters saw exactly one rising edge, which is all a held acknowledge ever offers.
The slave was byte-identical in both runs and fully conformant in both. The only difference is one line in the master — and the specification anticipated that line precisely enough to write RULE 3.55 about it.
8. Verification
// ─────────────────────────────────────────────────────────────────────────
// wb_ack_checker — acknowledge properties, both sides.
//
// P1 and P2 are SPECIFICATION (RULES 3.35 and 3.50). P3 is also
// SPECIFICATION — from RULE 3.55 — but is a LIVENESS property about the
// MASTER'S BEHAVIOUR rather than a constraint on any wire, which is why it
// needs a white-box progress signal. P4 is LOCAL POLICY.
// ─────────────────────────────────────────────────────────────────────────
module wb_ack_checker (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic ack_o,
input logic err_o,
input logic rty_o,
input logic m_progress // white-box: master advanced a transfer
);
default disable iff (rst_i);
// P1 — SPECIFICATION (RULE 3.35). The acknowledge is a RESPONSE: it may
// only be asserted when the transfer provoking it is present.
// Catches a tied-high ACK_O and one derived from STB_I alone.
property p_ack_is_a_response;
@(posedge clk_i) ack_o |-> (cyc_i && stb_i);
endproperty
a_ack_is_a_response : assert property (p_ack_is_a_response)
else $error("RULE 3.35: ACK_O asserted without CYC_I & STB_I");
// P2 — SPECIFICATION (RULE 3.50, OBSERVATION 3.10). Terminations are
// negated in response to the negation of STB_I.
property p_ack_negates_with_stb;
@(posedge clk_i) !stb_i |-> !ack_o;
endproperty
a_ack_negates_with_stb : assert property (p_ack_negates_with_stb)
else $error("RULE 3.50: ACK_O still asserted after STB_I negated");
// P3 — SPECIFICATION (RULE 3.55), as LIVENESS. If a transfer is
// presented and acknowledged, the master MUST make progress.
//
// This is the ONLY way to catch edge detection. During the failure
// every bus signal is legal — qualifiers asserted, acknowledge
// asserted, all rules satisfied — and what is wrong is that nothing
// happens. "Nothing happens" is not a signal, so the property needs
// to see inside the master.
property p_master_progresses_on_held_ack;
@(posedge clk_i) (cyc_i && stb_i && ack_o) |-> m_progress;
endproperty
a_master_progresses_on_held_ack :
assert property (p_master_progresses_on_held_ack)
else $error("RULE 3.55: master did not advance on an asserted ACK_I");
// P4 — LOCAL POLICY. The three terminations are mutually exclusive.
// RULE 3.45 makes this SPECIFICATION for any slave that supports
// ERR_O or RTY_O; wb_ack_fast_slave supports neither, so for THIS
// slave it is trivially true rather than a constraint. Kept so the
// checker can be reused against slaves that do.
property p_terminations_exclusive;
@(posedge clk_i) (int'(ack_o) + int'(err_o) + int'(rty_o)) <= 1;
endproperty
a_terminations_exclusive : assert property (p_terminations_exclusive)
else $error("RULE 3.45: more than one termination asserted");
endmoduleP3 is the one to study. Every other property here constrains a wire and a passive monitor can check it. P3 constrains an outcome, and during the RULE 3.55 failure the bus is entirely conformant.
This is the fourth time this course has reached that boundary — atomicity in Chapter 4.9, repeated side effects in Chapter 5.3, livelock in Chapter 4.12, and this. A conformance-only plan misses all four, and Chapter 5.8 turns that observation into a checklist.
Note what makes RULE 3.55 unusual. Nearly every B3 rule constrains a signal. This one says a master "MUST be designed to operate normally when..." — an obligation on the design, not the wires. When a specification phrases a rule that way, it is telling you the corresponding check needs white-box access, and that budget belongs in the verification plan rather than being discovered when the property cannot be written.
Tooling limitation. Icarus Verilog has no SVA support and cannot execute any of these. They are reviewed by inspection; the synthesizable modules are elaborated and simulated.
9. Common Mistakes
"Wait for ACK to go high, then wait for it to go low."
Wrong mental model: the acknowledge is a pulse per transfer.
Concrete bug: edge detection, which RULE 3.55 exists to forbid.
Observable evidence: a block stalling after one transfer with STB_O and ACK_I both asserted — a waveform that reads as success.
Correct model: sample ACK_I as a level in the cycle the transfer is presented. PERMISSION 3.35 lets a slave hold it; RULE 3.55 makes coping with that the master's job.
"A slave that is always ready can tie ACK_O high."
Wrong mental model: importing valid/ready's always-ready sink.
Concrete bug: terminations for transfers never presented, including other slaves'.
Observable evidence: transfers completing that were addressed elsewhere; merged read data in a shared fabric.
Correct model: PERMISSION 3.10 gives the legal form — tied to the AND of STB_I and CYC_I. That is a response; tied high is not.
"Combinational ACK means the protocol is asynchronous."
Wrong mental model: a combinational path breaks the synchronous model.
Concrete bug: none directly — but the belief leads engineers to register every termination reflexively, adding a cycle of latency to every transfer in the system without asking whether timing required it.
Observable evidence: uniformly doubled access latency with no timing-closure justification.
Correct model: PERMISSION 3.30 explicitly allows the combinatorial path from STB_I to ACK_O, and everything is still observed at rising clock edges. What changes is a timing path, not the clocking model — Chapter 5.7 quantifies it.
10. Interview Reasoning
The master is edge-detecting ACK_I, and the slave's acknowledge never falls. That is a RULE 3.55 violation in the master.
Why the acknowledge never falls. A never-busy slave computes ack_o = cyc_i & stb_i, exactly as PERMISSION 3.10 permits. The master holds both qualifiers across the whole block, so the qualified transfer is continuously true and the acknowledge has no reason to go low. PERMISSION 3.35 explicitly allows this, and the slave is entirely conformant.
What the master does wrong. It waits for a rising edge, or for a negation before advancing. Neither arrives. It stalls, holding the strobe — and the slave, still seeing a qualified transfer, keeps the acknowledge asserted in response.
Why the waveform misleads. ACK_I high normally means success, so the instinct is that the transfer completed and the problem is downstream. The tell is that STB_O is still asserted — a completed transfer would have released it. And compared with every other hang in this module, this one is distinguished by the acknowledge being high rather than low.
Why RULE 3.55 exists at all. "Wait for the acknowledge" is how the handshake is described in prose, and translating that phrase directly into RTL produces edge detection. The bug is structural rather than careless, which is why the specification spends a rule on it — and phrases it as an obligation on the master's design rather than on any signal.
The fix, and what it buys beyond this bug. Sample the level inside the state representing an outstanding transfer. The same change makes wait states work correctly for free, because a level test is indifferent to how long the acknowledge took to arrive.
How I would catch it in verification. Not with a bus monitor — the bus is legal throughout. It needs a liveness property with a white-box progress signal: if a transfer is presented and acknowledged, the master advanced.
11. Understanding Check
12. What's Next
The three handshake signals are now understood in time: a cycle that frames, a strobe that presents and persists, an acknowledge that responds and is sampled as a level.
What has been assumed throughout is that a transfer simply starts. But a master serves a local client with its own timing, and the moment a request crosses from that client onto the bus is where a whole class of corruption enters.
Exactly what must be true for a Wishbone transfer to begin, and what must the master capture before it does?
Chapter 5.5 — Transaction Initiation answers it. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
ACK_I
The only mandatory termination. What a slave promises by asserting it, how wait states work without a wait signal, and why RULE 3.55 requires a master to keep working when a slave holds it asserted.
- Related topic
Control Signals
Address and data are payload; they say what values are involved and nothing about what should happen to them. Control information is what makes a bus interpretable: a qualifier that says a request is real, a direction, lane enables, a completion and an error — each derived from a failure that occurs without it.
- Related topic
The Wishbone Mental Model
Wishbone is two levels, not one: a master opens a bus cycle and presents transfers inside it, and an addressed slave terminates each transfer with exactly one of three signals. That two-level structure is why describing Wishbone as valid/ready with renamed signals is wrong rather than merely imprecise.
- Related topic
The Slave Interface
A Wishbone slave never initiates. It observes a qualified request, interprets a local offset, and ends the transfer with exactly one of three terminations. Everything that makes it reusable comes from what it refuses to know: its own base address, the topology, and which master is asking.
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.
