Wishbone · Module 4
CYC_O
STB_O presents one transfer; CYC_O frames the tenure it belongs to. Holding it across transfers is what makes a read-modify-write atomic — and what atomicity still does not guarantee.
Chapter 4.8 deferred the second half of the qualifying conjunction. A master that reads a value, modifies it and writes it back must not have another master change that location in between — and nothing covered so far prevents it.
What holds a master's claim on the bus across several transfers, and what stops another master interleaving?
1. Single Transfers Hide the Distinction
Every master in this module so far has driven both qualifiers from one register:
assign cyc_o = active_q;
assign stb_o = active_q;That is correct, and RULE 3.25 is satisfied trivially — a master performing exactly one transfer per cycle has a tenure exactly as long as its transfer, so the two intervals coincide.
It is correct for that master, not in general. A design that generalises from it — a reviewer who concludes the two signals are redundant, an engineer who wires a slave to stb_i alone — has drawn a conclusion from a special case.
The distinction appears the moment a master performs more than one transfer as a unit:
| Cycle type | CYC_O | STB_O |
|---|---|---|
| SINGLE READ/WRITE | one transfer long | identical to CYC_O |
| BLOCK | held across all transfers | one assertion per transfer |
| RMW | held across read and write | one assertion each |
CYC_O asserted with STB_O negated is a legal and meaningful state — a master holding the bus between transfers of a block. Chapter 3.2 identified it as the combination with no valid/ready equivalent, and it is what the rest of this chapter is about.
2. The Read-Modify-Write Problem
Consider two masters and one shared word holding a bitmask. Each master wants to set one bit.
Each performs the obvious sequence: read the word, OR in its bit, write it back.
Interleaved without protection:
| Step | Master A | Master B | Word |
|---|---|---|---|
| 1 | reads 0x00 | 0x00 | |
| 2 | reads 0x00 | 0x00 | |
| 3 | writes 0x01 | 0x01 | |
| 4 | writes 0x02 | 0x02 |
Master A's bit is gone. Both masters did exactly what they were asked; the sequence is what failed. Chapter 2.6 introduced this as the general shared-resource hazard, and it is the reason CYC_O is separable from STB_O at all.
The fix is not more signals. It is holding CYC_O asserted across both transfers, so the interconnect never grants the bus to another master between the read and the write. RULE 3.25 names RMW explicitly as one of the three cycle types CYC_O must span.
3. The Slave Side: Why RULE 3.30 Exists
RULE 3.30 — "SLAVE interfaces MAY NOT respond to any SLAVE signals when [CYC_I] is negated."
Read that alongside RULE 3.35, which requires terminations to be generated from the logical AND of CYC_I and STB_I, and the pair covers a slave completely: 3.35 governs the terminations specifically, 3.30 governs everything else the slave might do.
Why a slave cannot just watch STB_I. In a shared fabric the strobe reaching a slave is the product of address decode. Whether the transfer being decoded belongs to a master that currently holds the bus is exactly what CYC_I says. A slave gating on stb_i alone works point-to-point, works with one master, and fails under arbitration in the way Chapter 4.8 §9 traced.
The one exception the rule names: a slave must always respond to SYSCON signals. RST_I is not gated by anything — a reset arriving while CYC_I is negated must still reset the interface, which is why Chapter 4.2's slave has its reset outside every qualification term.
4. RTL — A Block-Capable Master and an RMW
// ─────────────────────────────────────────────────────────────────────────
// wb_cyc_master — a master that holds CYC_O across MULTIPLE transfers.
//
// PURPOSE. Every previous master in this module drove cyc_o and stb_o from
// one register. This one separates them, which is the point: cyc_o spans
// the tenure, stb_o is asserted once per transfer within it.
//
// RULE 3.25: CYC_O asserted for the duration of the cycle, and no later
// than the rising edge that qualifies STB_O. Here cyc_o is asserted in the
// same edge as the first stb_o and held until the last transfer ends.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00, 3.20).
// ─────────────────────────────────────────────────────────────────────────
module wb_cyc_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned CW = 8 // burst-count width
) (
input logic clk_i,
input logic rst_i,
input logic go_i,
input logic [AW-1:0] base_i,
input logic [CW-1:0] count_i, // transfers in this block
output logic busy_o,
output logic done_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,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic [1:0] { S_IDLE, S_XFER, S_GAP } state_e;
state_e state_q;
logic [AW-1:0] adr_q;
logic [CW-1:0] left_q;
logic [DW-1:0] last_q;
// ── THE SEPARATION ─────────────────────────────────────────────────────
// cyc_o is asserted in S_XFER *and* S_GAP. stb_o only in S_XFER.
// S_GAP is the state that cannot exist in a single-transfer master: the
// bus is HELD but no transfer is presented. That is the legal combination
// CYC_O asserted with STB_O negated.
assign cyc_o = (state_q == S_XFER) || (state_q == S_GAP);
assign stb_o = (state_q == S_XFER);
assign we_o = 1'b0; // read block
assign adr_o = adr_q;
assign dat_o = '0;
assign busy_o = (state_q != S_IDLE);
logic terminated;
assign terminated = ack_i | err_i | rty_i;
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE; // RULE 3.20 via cyc/stb
adr_q <= '0;
left_q <= '0;
last_q <= '0;
done_o <= 1'b0;
end else begin
done_o <= 1'b0;
unique case (state_q)
S_IDLE: begin
if (go_i && (count_i != '0)) begin
state_q <= S_XFER;
adr_q <= base_i;
left_q <= count_i;
end
end
S_XFER: begin
if (terminated) begin
if (ack_i) last_q <= dat_i; // Chapter 4.5's capture
// An error or retry ABANDONS the block here. Deciding that is
// this master's policy, not a rule — Chapters 4.11 and 4.12
// examine what else a master might legitimately do.
if (!ack_i) begin
state_q <= S_IDLE;
done_o <= 1'b1;
end else if (left_q == CW'(1)) begin
state_q <= S_IDLE; // last transfer: drop CYC
done_o <= 1'b1;
end else begin
left_q <= left_q - CW'(1);
adr_q <= adr_q + AW'(1);
// Deliberately pass through S_GAP rather than presenting the
// next transfer immediately. It is not required — a master
// may hold stb_o continuously across a block — but it makes
// the held-bus-without-a-transfer state observable.
state_q <= S_GAP;
end
end
end
S_GAP: begin
state_q <= S_XFER; // CYC_O never dropped
end
default: state_q <= S_IDLE;
endcase
end
end
endmodule// ─────────────────────────────────────────────────────────────────────────
// wb_rmw_master — read-modify-write as ONE tenure.
//
// The atomicity mechanism is a single fact: cyc_o is asserted from the
// start of the read until the end of the write, and is never negated in
// between. RULE 3.25 names RMW as one of the cycle types CYC_O must span.
//
// WHAT THIS DOES NOT DO (Section 2): it does not lock the slave against a
// second port, and it depends on an arbiter that honours the request. Both
// are system properties outside this module.
// ─────────────────────────────────────────────────────────────────────────
module wb_rmw_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32
) (
input logic clk_i,
input logic rst_i,
input logic go_i,
input logic [AW-1:0] adr_i,
input logic [DW-1:0] setmask_i, // bits to OR in
output logic done_o,
output logic failed_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,
input logic [DW-1:0] dat_i,
input logic ack_i,
input logic err_i,
input logic rty_i
);
typedef enum logic [1:0] { R_IDLE, R_READ, R_WRITE } rstate_e;
rstate_e state_q;
logic [AW-1:0] adr_q;
logic [DW-1:0] mask_q, wdat_q;
// ── CYC_O SPANS BOTH TRANSFERS ─────────────────────────────────────────
// This single line is the atomicity. Compare with stb_o, which is also
// asserted in both states here only because this master presents the
// write in the cycle immediately after the read terminates. If it needed
// a cycle to compute, cyc_o would stay high and stb_o would drop — and
// the bus would still be held.
assign cyc_o = (state_q != R_IDLE);
assign stb_o = (state_q != R_IDLE);
assign we_o = (state_q == R_WRITE);
assign adr_o = adr_q;
assign dat_o = wdat_q;
logic terminated;
assign terminated = ack_i | err_i | rty_i;
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= R_IDLE;
adr_q <= '0;
mask_q <= '0;
wdat_q <= '0;
done_o <= 1'b0;
failed_o <= 1'b0;
end else begin
done_o <= 1'b0;
failed_o <= 1'b0;
unique case (state_q)
R_IDLE: begin
if (go_i) begin
state_q <= R_READ;
adr_q <= adr_i;
mask_q <= setmask_i;
end
end
R_READ: begin
if (ack_i) begin
// Modify. The read value never leaves this master, and the bus
// is still held, so no other master can have changed the word.
wdat_q <= dat_i | mask_q;
state_q <= R_WRITE;
end else if (err_i || rty_i) begin
// Abandoning here is safe: nothing has been written, so the
// location is exactly as it was. Abandoning after the read has
// succeeded but before the write is also safe for the same
// reason. There is no half-completed RMW to unwind.
state_q <= R_IDLE;
done_o <= 1'b1;
failed_o <= 1'b1;
end
end
R_WRITE: begin
if (terminated) begin
state_q <= R_IDLE;
done_o <= 1'b1;
failed_o <= ~ack_i;
end
end
default: state_q <= R_IDLE;
endcase
end
end
endmoduleReading the pair
Purpose. The block master shows CYC_O outliving STB_O; the RMW master shows why that matters.
Ownership. Both drive CYC_O, STB_O, WE_O, ADR_O and DAT_O. Neither drives any termination.
Combinational logic. Both derive their qualifiers from state. In wb_cyc_master the two expressions differ — that difference is the chapter.
Sequential logic. Block master: state, address, remaining count, last captured word. RMW master: state, address, mask, computed write data.
Timing. The block master asserts CYC_O and the first STB_O on the same edge, satisfying RULE 3.25's "no later than". It drops CYC_O only when the last transfer terminates. The RMW master presents its write in the cycle after the read terminates and never negates CYC_O between.
Qualification. No slave logic here; the slaves of the previous four chapters are the counterpart.
Reset. Synchronous, active high. state_q <= S_IDLE negates both qualifiers, satisfying RULE 3.20 through the state encoding rather than by assigning the outputs directly — worth noting, because a reviewer checking RULE 3.20 must trace through the state to confirm it.
Simplifications. The block master reads only. The RMW master handles one word. Neither retries on RTY_I — Chapter 4.12 is where that belongs.
Failure modes. Section 6.
5. Waveform — The Bus Held Between Transfers
CYC_O: one tenure, two transfers
9 cyclesCycle 3 is the state that does not exist in a single-transfer master. CYC_O is asserted, STB_O is not. No transfer is presented, no slave is being asked anything — and the bus is not available to anyone else.
That one cycle is the atomicity. Drop CYC_O there and another master may be granted the bus, read the same word, and produce the lost-update sequence from Section 2.
Note what a slave sees during cycle 3: CYC_I asserted, STB_I negated. RULE 3.30 permits it to respond to nothing, RULE 3.35 forbids it to terminate. It waits — which is the correct and complete behaviour.
6. Failure Modes and Discriminating Evidence
Symptom: concurrent updates to a shared word lose each other's changes.
Candidate causes. The RMW master drops CYC_O between the read and the write — usually because both qualifiers come from one register, the pattern every single-transfer master in this module uses.
Discriminating evidence. Watch CYC_O across the pair. A negation between the read's termination and the write's presentation is conclusive. It is one cycle wide and easy to miss at a glance, so trigger on cyc_o falling while the master's internal state says an RMW is in progress.
Likely RTL location. The cyc_o assignment. assign cyc_o = stb_o; is the bug written as a single line.
Property. P1 in Section 7.
Symptom: a master occasionally reads a value another master has already superseded, with CYC_O correctly held.
Candidate causes. Two possibilities, and they are distinguished by where the competing access came from. Either the arbiter does not honour CYC_O between transfers, or the slave has a second port.
Discriminating evidence. Check whether the competing write appeared on the bus at all. If it did, the arbiter granted during an open cycle — an integration bug in the interconnect, since Wishbone leaves arbitration policy to the integrator. If it did not, the change came through a back door and no amount of bus-level protection will fix it.
Likely RTL location. The arbiter's grant condition, or the slave's second port.
Symptom: a slave terminates a transfer during a gap in a block.
Candidate causes. The slave's termination is derived from something other than the qualified transfer — it is reacting to a held address, or to CYC_I alone.
Discriminating evidence. Look at STB_I at that slave in the terminating cycle. Negated is conclusive, and it is a RULE 3.35 violation.
Likely RTL location. The termination assignment.
Symptom: a system deadlocks when two masters both want the bus.
Candidate causes. A master holding CYC_O indefinitely — a block whose count is wrong, or a state machine that entered a hold state with no exit.
Discriminating evidence. CYC_O asserted for an unbounded period with STB_O idle. The absence of strobes is what distinguishes this from a slave that will not terminate, where the strobe stays high too.
Likely RTL location. The master's state machine exit conditions, not the arbiter.
7. Verification
// ─────────────────────────────────────────────────────────────────────────
// wb_cyc_checker — tenure properties.
//
// P2 and P3 are SPECIFICATION-derived from RULE 3.25. P1 is ATOMICITY
// POLICY for a particular master — the specification requires CYC_O to
// span an RMW cycle, but whether a given master is performing one is not
// visible on the bus, so the property needs a white-box signal.
// ─────────────────────────────────────────────────────────────────────────
module wb_cyc_checker (
input logic clk_i,
input logic rst_i,
input logic cyc_o,
input logic stb_o,
input logic ack_i,
input logic err_i,
input logic rty_i,
input logic rmw_active // white-box: master is mid read-modify-write
);
default disable iff (rst_i);
// P1 — ATOMICITY POLICY, checked white-box. CYC_O must not be negated
// while an RMW is in progress. Cannot be written from bus signals
// alone: an observer cannot tell a held tenure from two adjacent
// single cycles without knowing the master's intent.
property p_rmw_holds_cyc;
@(posedge clk_i) rmw_active |-> cyc_o;
endproperty
a_rmw_holds_cyc : assert property (p_rmw_holds_cyc)
else $error("CYC_O negated during a read-modify-write");
// P2 — RULE 3.25. CYC_O is asserted no later than the edge that
// qualifies STB_O. Stated as the simple implication: a strobe
// without a cycle is never legal.
property p_stb_implies_cyc;
@(posedge clk_i) stb_o |-> cyc_o;
endproperty
a_stb_implies_cyc : assert property (p_stb_implies_cyc)
else $error("RULE 3.25: STB_O asserted without CYC_O");
// P3 — RULE 3.25, the duration half. CYC_O may not be negated in the
// same cycle a transfer is still outstanding: the cycle lasts at
// least as long as the transfer inside it.
property p_cyc_spans_outstanding;
@(posedge clk_i)
(cyc_o && stb_o && !(ack_i || err_i || rty_i)) |=> cyc_o;
endproperty
a_cyc_spans_outstanding : assert property (p_cyc_spans_outstanding)
else $error("CYC_O negated with a transfer still outstanding");
// P4 — LOCAL POLICY. A tenure does not stay open indefinitely with no
// transfer presented. Not a Wishbone rule — the specification sets
// no limit — but an open cycle with no strobes starves every other
// master, and the bound belongs in the verification plan.
property p_no_idle_tenure;
@(posedge clk_i) (cyc_o && !stb_o) |-> ##[1:32] (stb_o || !cyc_o);
endproperty
a_no_idle_tenure : assert property (p_no_idle_tenure)
else $error("CYC_O held for 32 cycles with no transfer presented");
endmoduleP1 needs a white-box signal, and that is the interesting part. From the bus alone, a held tenure containing two transfers and two separate single-transfer cycles are distinguishable only by whether CYC_O dipped — but whether it was supposed to depends on what the master was doing, which is not on the bus.
This is a general limit of bus-level monitoring. Protocol conformance can be checked from the wires; intent cannot. A checker that only sees the bus can confirm RULE 3.25 was not violated and still miss that atomicity was silently lost.
Tooling limitation. Icarus has no SVA support; reviewed by inspection only.
8. Common Mistakes
"CYC_O and STB_O are redundant — drive them from the same signal."
Wrong mental model: generalising from the single-transfer case, where they genuinely coincide.
Concrete bug: an RMW master whose CYC_O follows STB_O, so the bus is released between the read and the write.
Observable evidence: lost updates under concurrency, with each individual transfer perfectly correct — the hardest kind of bug to see in a transfer-by-transfer review.
Correct model: they are separate claims. RULE 3.25 requires CYC_O to span the cycle; STB_O presents each transfer within it. They coincide only when the cycle contains one transfer.
"Holding CYC_O makes the access atomic."
Wrong mental model: CYC_O is a lock on the location.
Concrete bug: an RMW against a dual-port memory whose other port is written by a DMA engine. CYC_O is held perfectly and the update is still lost.
Observable evidence: atomicity failures with no bus activity from any other master — the competing write never appears on the waveform at all.
Correct model: CYC_O requests the bus. Atomicity needs the bus request honoured and no other path into the slave. It is a system property with three preconditions, and the specification only supplies one of them.
"An arbiter must honour CYC_O, so atomicity is guaranteed by the standard."
Wrong mental model: Wishbone specifies arbitration.
Concrete bug: an arbiter that re-evaluates between transfers, or a pre-emptive one built for latency. Every interface remains conformant and RMW stops working.
Observable evidence: the competing master's transfer appearing on the bus in the middle of an open cycle.
Correct model: Wishbone deliberately leaves interconnect topology and arbitration policy to the integrator. CYC_O is the request; what an arbiter does with it is a system design decision that must be specified, reviewed and tested locally.
9. Interview Reasoning
CYC_O held correctly rules out the commonest cause, so the next question is where the competing write came from — and one waveform answers it.
If the competing write appeared on the bus during the other master's open cycle, the arbiter granted while a tenure was in progress. Wishbone does not define arbitration policy, so this is an integration bug: the arbiter is conformant in the sense that no interface rule is broken, and the system is still wrong. The fix is in the arbiter's grant condition — do not re-evaluate while a grantee's CYC_O is asserted.
If the competing write never appeared on the bus at all, the location was changed through a second path — a dual-port memory, a CPU-side interface, internal hardware updating the register. No bus-level mechanism can fix this, because the bus was never involved. The options are a hardware mutual-exclusion primitive inside the slave, moving the shared state somewhere with only one access path, or a software protocol that does not require atomicity.
A third possibility worth eliminating: one master's CYC_O is held but the arbiter is not the thing granting — for example a crossbar where the two masters reach the slave by independent paths and arbitration happens at the slave port with different rules. The evidence is the same waveform, looked at from the slave's side rather than the master's.
The framing I would give: atomicity here rests on three things — a master that holds CYC_O, an arbiter that honours it, and a slave with no back door. The specification supplies the first only. Saying "we use RMW so it's atomic" is a claim about one third of the requirement, and the other two thirds are where the failures live.
10. Understanding Check
11. What's Next
The master's side is complete. Every signal a master drives — address, both data directions, byte lanes, direction, transfer and tenure — has been covered, and all of them are qualified by something.
Everything now waits on the slave. A transfer is presented and held, and until the slave says otherwise nothing moves.
How does a slave say "done", and what exactly has it promised when it does?
Chapter 4.10 — ACK_I answers it. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Shared Resources
Two initiators wired to one target is not a wiring problem with a wiring solution. A single-port target has one address input and one completion output, so access must be serialised — and the rule that matters most is not who goes first but that ownership cannot change while a transaction is in flight.
- Related topic
Read-Modify-Write Cycle
One CYC_O across a read and a related write is the whole of what the specification defines. Measured runs show the same conformant master losing mutual exclusion when only the arbiter's policy changes.
- Related topic
CPU to Peripheral Communication
A CPU reaches hardware outside itself by reading and writing addressed locations, and a peripheral is hardware it cannot execute. Everything a driver does has to be expressed as a read or a write of a location the peripheral answers for — and once more than a couple of peripherals exist, wiring each one to the core separately stops scaling. That is the problem an on-chip bus is the answer to.
- 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.
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.
