Wishbone · Module 16
Bus Ownership
Ownership has two directions, not one. Three targeted interconnect defects measured against one stimulus — and a conformance monitor that finds every one of them faultless.
Chapter 16.2 counted the clocks a master spends asking, waiting and owning. The thing doing the granting was taken on trust.
What exactly does it mean to own a shared Wishbone path?
1. Ownership, Defined by What It Routes
Ownership is not a property of a master. It is a property of the interconnect, and its entire content is which wires are connected to which.
The forward direction is the one everyone builds first, and the specification makes it complete rather than merely tidy. RULE 3.30:
SLAVE interfaces MAY NOT respond to any SLAVE signals when
[CYC_I]is negated.
So if the interconnect gates CYC by ownership, a non-owner does not merely lose an argument about the address lines — it reaches no slave at all. The prose above RULE 3.25 says the same thing from the master's side: "When [CYC_O] is negated, all other MASTER signals are invalid." Gating CYC is what makes the rest of the gating meaningful.
The return direction has no rule behind it whatsoever. RULE 3.45 constrains the slave — "MUST NOT assert more than one of [ACK_O], [ERR_O] or [RTY_O]" — and says nothing about how many masters the interconnect hands that one termination to. Routing it to the wrong master, or to every master, breaks no rule in B3. It is a design obligation the specification never writes down, which is exactly why it is worth measuring.
Two messages in that diagram are the ones worth arguing about. "nothing" — the interconnect actively delivering silence to the non-owner — and "one request context", which is one message and not four. Sections 5 and 6 delete one of each.
2. RTL — The Interconnect
This module is the subject of the chapter. Its defect parameters are all off by default, and each one turns it into a targeted broken interconnect differing by exactly the named term.
// ─────────────────────────────────────────────────────────────────────────
// wb_owner_mux2 — two MASTERs, one shared Wishbone path.
//
// The specification calls this module an INTERCON: "A WISHBONE module that
// interconnects MASTER and SLAVE interfaces." It does not say how one
// should choose between masters. The only numbered identifiers in B3 that
// mention arbitration at all are advisory:
//
// RECOMMENDATION 3.05 "Arbitration logic often uses [CYC_I] to select
// between MASTER interfaces."
// OBSERVATION 3.40 "...simplifies the design of arbiters in
// multi-MASTER applications."
//
// and the Features list settles ownership of the question outright:
// "Arbitration methodology is defined by the end user (priority arbiter,
// round-robin arbiter, etc.)."
//
// So everything below marked LOCAL TEACHING POLICY is a choice this module
// makes, not a rule it obeys. Priority schemes, round-robin, fairness and
// starvation avoidance are Module 17's subject and are not designed here.
//
// ── LOCAL TEACHING POLICY ───────────────────────────────────────────────
// * Owner NONE: CPU wins a simultaneous acquisition. This is a tie-break,
// not a priority scheme - it decides nothing once someone owns.
// * An owner is retained while its own CYC_O stays asserted, and released
// the clock after it negates. The BLOCK section describes exactly this
// arbiter: "an arbiter for that memory can determine when one MASTER is
// done with it so that another can gain access to the memory."
// * On release, a waiting master is granted immediately.
//
// ── WHAT THIS POLICY MEANS FOR LOCK_O ───────────────────────────────────
// LOCK_O requires that "the INTERCON does not grant the bus to any other
// MASTER, until the current MASTER negates [LOCK_O] or [CYC_O]". Retaining
// an owner for the whole of its CYC_O already satisfies that, so this
// interconnect honours LOCK_O structurally rather than by a lock term. Set
// RELEASE_ON_IDLE to reach the policy where the lock term is load-bearing -
// that is Chapter 15.4's interconnect, and HONOUR_LOCK then matters.
//
// ── THE THREE DEFECTS ───────────────────────────────────────────────────
// PREEMPT, MIX_CONTEXT and BROADCAST_RETURN are OFF by default. Each turns
// this module into a targeted broken interconnect for one experiment, so
// the correct and broken versions differ by exactly the named term and
// nothing else. None of them violates a numbered rule: all three are
// interconnect design defects, which is precisely why no protocol checker
// on the shared bus can see them.
// ─────────────────────────────────────────────────────────────────────────
module wb_owner_mux2 #(
parameter int unsigned AW = 12,
parameter int unsigned DW = 32,
parameter int unsigned SW = DW/8,
parameter bit RELEASE_ON_IDLE = 1'b0,
parameter bit HONOUR_LOCK = 1'b1,
parameter bit PREEMPT = 1'b0,
parameter bit MIX_CONTEXT = 1'b0,
parameter bit BROADCAST_RETURN = 1'b0
) (
input logic clk_i,
input logic rst_i,
// -- CPU master port --
input logic cpu_cyc_i,
input logic cpu_stb_i,
input logic cpu_lock_i,
input logic cpu_we_i,
input logic [AW-1:0] cpu_adr_i,
input logic [DW-1:0] cpu_dat_i,
input logic [SW-1:0] cpu_sel_i,
output logic [DW-1:0] cpu_dat_o,
output logic cpu_ack_o,
output logic cpu_err_o,
output logic cpu_rty_o,
// -- DMA master port --
input logic dma_cyc_i,
input logic dma_stb_i,
input logic dma_lock_i,
input logic dma_we_i,
input logic [AW-1:0] dma_adr_i,
input logic [DW-1:0] dma_dat_i,
input logic [SW-1:0] dma_sel_i,
output logic [DW-1:0] dma_dat_o,
output logic dma_ack_o,
output logic dma_err_o,
output logic dma_rty_o,
// -- shared downstream Wishbone path --
output logic s_cyc_o,
output logic s_stb_o,
output logic s_lock_o,
output logic s_we_o,
output logic [AW-1:0] s_adr_o,
output logic [DW-1:0] s_dat_o,
output logic [SW-1:0] s_sel_o,
input logic [DW-1:0] s_dat_i,
input logic s_ack_i,
input logic s_err_i,
input logic s_rty_i,
// -- observation: 0 = NONE, 1 = CPU, 2 = DMA --
output logic [1:0] owner_o
);
localparam logic [1:0] OWN_NONE = 2'd0;
localparam logic [1:0] OWN_CPU = 2'd1;
localparam logic [1:0] OWN_DMA = 2'd2;
logic [1:0] owner_q, owner_n;
logic sel_cpu, sel_dma;
assign sel_cpu = (owner_q == OWN_CPU);
assign sel_dma = (owner_q == OWN_DMA);
logic cur_cyc, cur_stb, cur_lock, other_cyc;
assign cur_cyc = sel_cpu ? cpu_cyc_i : (sel_dma ? dma_cyc_i : 1'b0);
assign cur_stb = sel_cpu ? cpu_stb_i : (sel_dma ? dma_stb_i : 1'b0);
assign cur_lock = sel_cpu ? cpu_lock_i : (sel_dma ? dma_lock_i : 1'b0);
assign other_cyc = sel_cpu ? dma_cyc_i : (sel_dma ? cpu_cyc_i : 1'b0);
logic [1:0] other_id;
assign other_id = sel_cpu ? OWN_DMA : OWN_CPU;
always_comb begin
owner_n = owner_q;
if (PREEMPT) begin
// DEFECT: ownership recomputed from the request lines every clock,
// with no memory of a cycle already in progress. This is the shape a
// priority selector takes when it is written as pure combinational
// logic, and it is invisible until a slave inserts a wait state.
if (cpu_cyc_i) owner_n = OWN_CPU;
else if (dma_cyc_i) owner_n = OWN_DMA;
else owner_n = OWN_NONE;
end else if (owner_q == OWN_NONE) begin
if (cpu_cyc_i) owner_n = OWN_CPU; // tie-break, not priority
else if (dma_cyc_i) owner_n = OWN_DMA;
end else if (!cur_cyc) begin
// The owner has negated CYC_O: it is done with the resource.
if (other_cyc) owner_n = other_id;
else owner_n = OWN_NONE;
end else if (RELEASE_ON_IDLE && !(HONOUR_LOCK && cur_lock)
&& !cur_stb && other_cyc) begin
owner_n = other_id; // Chapter 15.4's policy
end
end
always_ff @(posedge clk_i) begin
if (rst_i) owner_q <= OWN_NONE;
else owner_q <= owner_n;
end
assign owner_o = owner_q;
// ── FORWARD ROUTING: owner -> slave ────────────────────────────────────
// CYC_O and STB_O are gated by ownership, so a non-owner reaches no
// slave at all. RULE 3.30 is what makes that complete: "SLAVE interfaces
// MAY NOT respond to any SLAVE signals when [CYC_I] is negated."
logic mix_active;
assign mix_active = MIX_CONTEXT && cpu_cyc_i && dma_cyc_i;
assign s_cyc_o = (sel_cpu && cpu_cyc_i) || (sel_dma && dma_cyc_i);
assign s_stb_o = (sel_cpu && cpu_stb_i) || (sel_dma && dma_stb_i);
assign s_lock_o = (sel_cpu && cpu_lock_i) || (sel_dma && dma_lock_i);
// DEFECT: each field selected independently instead of as one request
// context. RULE 3.60 names the set that MUST be qualified together by
// [STB_O] - [ADR_O], [DAT_O()], [SEL_O()], [WE_O] - and splitting that
// set across two masters produces a transfer neither master issued.
always_comb begin
if (mix_active) begin
s_adr_o = cpu_adr_i;
s_we_o = cpu_we_i;
s_dat_o = dma_dat_i;
s_sel_o = dma_sel_i;
end else if (sel_cpu) begin
s_adr_o = cpu_adr_i; s_we_o = cpu_we_i;
s_dat_o = cpu_dat_i; s_sel_o = cpu_sel_i;
end else if (sel_dma) begin
s_adr_o = dma_adr_i; s_we_o = dma_we_i;
s_dat_o = dma_dat_i; s_sel_o = dma_sel_i;
end else begin
s_adr_o = '0; s_we_o = 1'b0; s_dat_o = '0; s_sel_o = '0;
end
end
// ── RETURN ROUTING: slave -> owner ─────────────────────────────────────
// A termination is an answer to one transfer, and exactly one master
// issued that transfer. RULE 3.45 guarantees the slave asserts at most
// one of [ACK_O], [ERR_O], [RTY_O] at a time; routing it to the wrong
// master is an interconnect defect, not a protocol violation, which is
// why nothing on the shared bus can detect it.
assign cpu_ack_o = (BROADCAST_RETURN || sel_cpu) && s_ack_i;
assign cpu_err_o = (BROADCAST_RETURN || sel_cpu) && s_err_i;
assign cpu_rty_o = (BROADCAST_RETURN || sel_cpu) && s_rty_i;
assign dma_ack_o = (BROADCAST_RETURN || sel_dma) && s_ack_i;
assign dma_err_o = (BROADCAST_RETURN || sel_dma) && s_err_i;
assign dma_rty_o = (BROADCAST_RETURN || sel_dma) && s_rty_i;
// The read-data path is deliberately NOT gated. There is one shared read
// bus and both masters see it every clock, exactly as in a real mux.
// That is harmless on its own because read data is meaningful only on the
// clock a termination qualifies it - which is what makes the termination
// gating above the load-bearing part of this module.
assign cpu_dat_o = s_dat_i;
assign dma_dat_o = s_dat_i;
endmoduleReading it
The ownership state machine is four branches and they are in precedence order.
owner_q == OWN_NONE — grant to whoever is asking, CPU first. This is a tie-break on an idle bus and it is not reached once anyone owns, which is the whole difference between it and a priority scheme.
!cur_cyc — the owner has negated CYC_O. It is done; hand over to a waiting master or drop to NONE. This is the release rule, and the BLOCK section describes exactly this arbiter: "an arbiter for that memory can determine when one MASTER is done with it so that another can gain access to the memory."
RELEASE_ON_IDLE — off by default. Set, it reaches Chapter 15.4's policy, where an unlocked owner that is not presenting yields to a waiting master. That is the policy in which HONOUR_LOCK is load-bearing, and Section 9 says what that means here.
PREEMPT — a defect. Ownership recomputed from the request lines every clock with no memory of a cycle in progress. It is the shape a priority selector takes when it is written as pure combinational logic, and Section 7 shows it is invisible until a slave inserts a wait state.
The forward path is six assignments and one always_comb.
s_cyc_o and s_stb_o are ANDed with ownership. That is what makes a non-owner harmless: with CYC_I negated, RULE 3.30 forbids every slave downstream from responding to it. The non-owner is not outvoted; it is disconnected.
The payload fields are selected as one block, in one always_comb, with one condition. RULE 3.60 names the set that a master must qualify together with STB_O — ADR_O, DAT_O(), SEL_O(), WE_O — and the interconnect's obligation is to keep that set together. MIX_CONTEXT splits it and Section 5 measures the result.
The return path is six lines and they are the ones people leave out.
assign cpu_ack_o = (BROADCAST_RETURN || sel_cpu) && s_ack_i;With BROADCAST_RETURN cleared this reads: the CPU sees the acknowledgement only if the CPU is the owner. Set, the sel_cpu term is bypassed and both masters see it. One ||.
The read-data path is deliberately not gated, and that is a design statement rather than an oversight. There is one shared read bus and both masters see it every clock, exactly as in a real mux. That is harmless on its own, because read data means nothing except on the clock a termination qualifies it — which is precisely what makes the six termination assignments the load-bearing part of the module. Section 6 confirms it the hard way: with the terminations gated, the CPU reads the right word even though the DMA's data was on the shared bus the whole time.
3. Simulation — SIM E: The Ownership Timeline
The shared RAM inserts two wait states on every phase, so each tenure is three clocks long and there is room to see inside it. The CPU's request is timed to arrive while the DMA is waiting for its acknowledgement.
=== SIM E - ownership across wait states ===
the shared RAM inserts 2 wait states. The CPU's request is
timed to arrive while the DMA is waiting for its ACK.
clk c_cyc c_stb d_cyc d_stb owner s_stb adr s_ack c_ack d_ack
0 0 0 0 0 - 0 - 0 0 0
1 0 0 0 0 - 0 - 0 0 0
2 1 1 0 0 - 0 - 0 0 0
3 1 1 0 0 CPU 1 0x803 1 1 0
4 0 0 1 1 CPU 0 - 0 0 0
5 0 0 1 1 DMA 1 0x010 0 0 0
6 1 1 1 1 DMA 1 0x010 0 0 0
7 1 1 1 1 DMA 1 0x010 1 0 1
8 1 1 0 0 DMA 0 - 0 0 0
9 1 1 1 1 CPU 1 0x00c 0 0 0
10 1 1 1 1 CPU 1 0x00c 0 0 0
11 1 1 1 1 CPU 1 0x00c 1 1 0
12 0 0 1 1 CPU 0 - 0 0 0
13 0 0 1 1 DMA 1 0x030 0 0 0
14 0 0 1 1 DMA 1 0x030 0 0 0
15 0 0 1 1 DMA 1 0x030 1 0 1
16 0 0 0 0 DMA 0 - 0 0 0
17 0 0 1 1 - 0 - 0 0 0
18 0 0 1 1 DMA 1 0x011 0 0 0
19 0 0 1 1 DMA 1 0x011 0 0 0
20 0 0 1 1 DMA 1 0x011 1 0 1
21 0 0 0 0 DMA 0 - 0 0 0
22 0 0 1 1 - 0 - 0 0 0
23 0 0 1 1 DMA 1 0x031 0 0 0
24 0 0 1 1 DMA 1 0x031 0 0 0
25 0 0 1 1 DMA 1 0x031 1 0 1
26 0 0 0 0 DMA 0 - 0 0 0
27 0 0 0 0 - 0 - 0 0 0
28 0 0 0 0 - 0 - 0 0 0
29 0 0 0 0 - 0 - 0 0 0
longest wait inside a phase (presented, unanswered) 2
clocks the presented address moved inside a phase 0
owner changes 15 acquisitions CPU 5 DMA 4
owner changed while a phase was unanswered 0
a master saw a termination it did not own 0
transfers presented with no owner 0
shared request context split between masters 0
CPU read 0xaaaa000c DMA words 2Reading it
Clocks 5, 6 and 7 are one tenure and they answer the wait-state question completely.
Clock 5 — owner DMA, s_stb 1, address 0x010, no acknowledgement. Clock 6 — the CPU's c_cyc rises. The owner is still DMA. Clock 7 — the acknowledgement arrives and d_ack is 1 while c_ack stays 0.
Three things held across those three clocks, and all three are required.
The owner did not move — owner changed while a phase was unanswered: 0. A slave inserting wait states has no relationship whatsoever with ownership. The slave is not party to the arbitration and cannot see it, which is Chapter 9.1's transaction extension arriving in a new place.
The presented address did not move — clocks the presented address moved inside a phase: 0. The audit walks the recorded timeline and compares the address on every clock of every held phase against the address on that phase's first clock. A phase whose address changes mid-flight is a different transfer, and nothing downstream would know.
The non-owner saw nothing — c_ack is 0 on clock 7. The CPU was asserting CYC_O and STB_O throughout and received no termination, because it was not the owner.
Now clock 8, which is the more interesting one. The DMA is in its gap clock: d_cyc is 0, the owner is still DMA, and the CPU is asking. At clock 9 the owner is CPU. The DMA lost the bus not to a wait state but to its own one-clock gap — and then waited out the CPU's full three-clock tenure at clocks 9, 10, 11 before reacquiring at 13.
That is the release policy's price, stated plainly. Retaining an owner only while its CYC_O is asserted means a master that pauses gives up the bus. A different policy would hold it across the pause and make the CPU wait instead. Neither is required by anything; both are choices with a cost, and the cost lands on a different master.
The handover the timeline names
10 cyclesCompare the two acknowledgement rows against the shared one. There are two shared acknowledgements in this window and each appears in exactly one master's row. Add the two master rows together and you get the shared row back, which is the invariant the whole chapter turns on. Section 6 breaks it.
And read the owner row against DMA: CYC_O. The owner is DMA at cycle 4 while DMA: CYC_O is 0 — the release takes effect on the following clock, not the same one. Ownership is registered, and the one-clock skew is the same one Chapter 16.2 measured as the cost of every acquisition.
4. Simulation — SIM F: A Request Assembled From Two Masters
The defect is one always_comb condition: when both masters assert CYC_O, take ADR and WE from the CPU and DAT and SEL from the DMA. Everything else is identical, including the stimulus.
Both masters are writing. The CPU writes 0xC0FFEE01 to 0x040; the DMA writes the word it read from 0x010 to 0x030.
=== SIM F - request fields selected independently ===
both masters are writing. The CPU writes 0xC0FFEE01 to 0x040;
the DMA writes the word it read from 0x010 to 0x030.
The broken interconnect takes ADR and WE from the CPU and
DAT and SEL from the DMA whenever both assert CYC_O.
address correct field-mixed
0x030 0xaaaa0010 0xaaaa0040
0x031 0xaaaa0011 0xaaaa0011
0x040 0xc0ffee01 0xaaaa0040
the CPU's client was told its write succeeded in BOTH rigs
shared request context split between masters: correct 0 mixed 5
write phases on the shared path (config included): correct 7 mixed 8
words actually changed in the shared RAM: correct 3 mixed 4
-> WE came from the CPU while the DMA owned, so one of the
DMA's READ phases was performed as a WRITE.Reading it
Three locations, and in the broken rig all three are wrong in different ways.
0x040 should hold 0xC0FFEE01 and holds 0xAAAA0040. The CPU's write data never reached memory. It was replaced by the DMA's.
0x030 should hold 0xAAAA0010 — the word copied from 0x010 — and holds 0xAAAA0040. The DMA copied the wrong word.
And the reason it copied the wrong word is the line that should make this defect frightening. WE came from the CPU while the DMA owned the bus, so one of the DMA's READ phases was performed as a WRITE. The engine presented a read of 0x010, the interconnect presented a write to 0x040, the memory performed the write, and the DMA captured mem[0x040] as though it were the word it had asked for. A read turned into a write, and the engine had no way to know.
The counters confirm the shape of it. Write phases on the shared path: correct 7, mixed 8. The mixed rig performed one more write than any master issued. Words actually changed in the shared RAM: correct 3, mixed 4. A location was written that no master targeted with that value.
And the line that matters most in the whole simulation: the CPU's client was told its write succeeded in BOTH rigs. The master saw ACK_I and reported success. It had no way to learn that the data on the shared bus was not the data it presented, because a master does not read back its own request.
This is why the fields are one context and not four. RULE 3.60 names ADR_O, DAT_O(), SEL_O() and WE_O as the set a master qualifies together with STB_O. It is a rule about a master. Nothing tells an interconnect to keep the set together — and a mux written field by field, with four independent selects, is exactly how the set comes apart. The defect is one condition evaluated four times instead of once.
5. Simulation — SIM G: An Acknowledgement Delivered Twice
One || in the return path. The CPU asks to read 0x00C while the DMA owns the bus and is reading 0x010, so there is one transfer on the shared path and two masters waiting on it.
=== SIM G - a termination returned to both masters ===
the CPU asks to read 0x00C while the DMA owns the bus and
is reading 0x010. Only one transfer is on the shared path.
rig CPU read value CPU completions DMA phase ACKs shared ACKs
correct 0xaaaa000c 5 4 9
broadcast 0xaaaa0010 5 8 8
a master saw a termination it did not own: correct 0 broadcast 8
client completions vs bus terminations: correct 9/9 broadcast 13/8
-> in the broadcast rig the CPU's read completed without ever
being presented on the shared path.
response isolation, all three termination classes (correct rig)
class raised by owner CPU sees DMA sees
ACK shared RAM answers the DMA DMA 0 1
ERR DMA registers refuse a write CPU 1 0
RTY shared RAM reports it is busy CPU 1 0
terminations on the shared path: ACK 13 ERR 1 RTY 1
a master saw a termination it did not own: 0Reading it
The correct rig returns 0xAAAA000C. The broadcast rig returns 0xAAAA0010.
That second value is the word the DMA read. The CPU asked for 0x00C and was handed the contents of 0x010 — a location it never named, by a transfer it never issued.
And it is worse than wrong data. Look at the accounting: client completions vs bus terminations — correct 9/9, broadcast 13/8. Thirteen completions from eight terminations. In the broadcast rig the CPU's read completed without ever being presented on the shared path at all. There is no clock on which s_stb was high with the CPU's address on it. The transfer did not happen; only the report did.
That accounting identity is the cheapest checker in this module. Every termination on the shared path belongs to exactly one master, so the sum of client completions must equal the number of bus terminations. It needs no ownership state, no address comparison and no knowledge of the arbitration policy — just two counters. In the correct rig it is 9 and 9.
The non-owner count is 8, and those eight are not all the same shape. Some are the CPU seeing the DMA's acknowledgement; some are the DMA seeing the CPU's configuration writes being acknowledged. The DMA's own phase-acknowledgement count doubled from 4 to 8 — it counted every acknowledgement on the bus as its own.
Now the isolation table, which covers the other two termination classes.
| class | raised by | owner | CPU sees | DMA sees |
|---|---|---|---|---|
| ACK | the shared RAM answering the DMA | DMA | 0 | 1 |
| ERR | the DMA's registers refusing a write | CPU | 1 | 0 |
| RTY | the shared RAM reporting itself busy | CPU | 1 | 0 |
All three classes route to the owner and only the owner. That is not three separate mechanisms — it is one sel_cpu/sel_dma term applied three times — but it has to be applied three times, and an interconnect that gates ACK while broadcasting ERR is a real and common shape.
The RTY row deserves a note, because it is the specification's own example rather than a contrivance. The shared RAM asserts RTY_O when its busy_i input is high, and B3 describes exactly that use:
This signal is generally used for shared memory and bus bridges. In these cases SLAVE circuitry asserts
[RTY_I]if the local resource is busy.
A shared memory answering RTY is what the signal is for. And the ERR row is equally ordinary: the DMA's configuration slave refuses a write while its engine is running, which is Chapter 11.2's test for ERR rather than RTY — the request is wrong for the device's state, not deferred.
What a non-owner must never do is now a list rather than a principle. It must not drive shared request metadata; it must not take another owner's ACK, ERR or RTY as its own; it must not advance its transfer state because another master's phase terminated; and it must not report completion to its client. It may hold its request, and it may do anything at all that does not touch the shared path.
6. The Negative-Control Gate
A checker that has only ever passed has not been shown to check anything. Four checkers, four interconnects, one stimulus. Each broken interconnect differs from the correct one by exactly one term in wb_owner_mux2.
=== NEGATIVE-CONTROL GATE ===
one stimulus, four interconnects. Columns are the DUT;
rows are the checker. Each broken DUT differs from the
correct one by exactly one term in wb_owner_mux2.
checker correct mix-ctx bcast preempt
REQUEST CONTEXT COHERENCE PASS FAIL PASS PASS
NON-OWNER RESPONSE ISOLATION PASS PASS FAIL PASS
OWNER STABLE WHILE UNANSWERED PASS PASS PASS FAIL
COPY INTEGRITY (read from RAM) PASS FAIL PASS PASS
raw counts context non-owner term owner change mid-phase
correct 0 0 0
mix-context 5 0 0
broadcast 0 8 0
preempt 0 0 1
destination words after the copy, read out of the RAM
correct 0xaaaa0010 0xaaaa0011
mix-context 0xaaaa0040 0xaaaa0011
broadcast 0xaaaa0010 0xaaaa0011
preempt 0xaaaa0010 0xaaaa0011
specification conformance monitor (wb_rule_mon), all rigs
rig STB w/o CYC term w/o CYC multi-term lock moved
correct 0 0 0 0
mix-context 0 0 0 0
broadcast 0 0 0 0
preempt 0 0 0 0
clocks LOCK_O was asserted on the shared path: 3
-> every rig is conformant. RULE 3.25, RULE 3.30 and
RULE 3.45 are satisfied by all four interconnects,
including the three broken ones.
NEGATIVE-CONTROL CHECKERS REQUIRED: >= 3
CHECKERS PASS CORRECT DUT: 4/4
CHECKERS FAIL TARGET BROKEN DUT: 4/4Reading it — the diagonal, and the two rows that are not on it
The matrix is diagonal, and that is the result. Each structural checker fails on its own defect and passes on the other two. A checker that fired on everything would be an alarm, not an instrument.
Two entries are worth more than the diagonal itself.
COPY INTEGRITY fails on mix-context and passes on preempt. The preempt rig changed owners in the middle of an unanswered phase — a genuine, serious structural defect — and the data came out correct. The copy finished, the destination words are right, and an end-to-end test would have reported success.
That is the honest case for structural checkers. The functional checker is the one that matters to a user and it saw nothing. The structural checker saw one event and can name the clock it happened on. A system tested only end to end would have shipped the preempt interconnect, and the defect would have surfaced later, on a different slave, with a different wait-state profile.
And COPY INTEGRITY passes on broadcast too, even though the broadcast rig handed the CPU another master's data. The DMA's copy was unaffected; what broke was a different master's read. A data-integrity checker sees only the data it was pointed at.
One detail about how COPY INTEGRITY reads the memory. It reads mem[] directly by hierarchical reference rather than issuing a bus read. A data-integrity checker must not depend on the path it is checking — in the broadcast rig, a read issued over the bus is exactly the thing that cannot be trusted.
The raw counts show each defect is small and local. Five context splits, eight leaked terminations, one owner change mid-phase. The preempt defect fired once in the whole run and that once was enough to make the interconnect unsound.
7. The Conformance Monitor, and What It Finds
Everything so far has been a policy checker. The obvious next question is what a specification checker would have caught.
// ─────────────────────────────────────────────────────────────────────────
// wb_rule_mon — a conformance monitor for the four things Module 16 is
// entitled to check against the specification rather than against a local
// policy. Everything else this module could check is a design choice.
//
// RULE 3.25 "[CYC_O] MUST be asserted no later than the rising [CLK_I]
// edge that qualifies the assertion of [STB_O]" - so a
// presented STB_O without CYC_O is a master defect.
// RULE 3.30 "SLAVE interfaces MAY NOT respond to any SLAVE signals when
// [CYC_I] is negated" - a termination with CYC negated is a
// slave defect.
// RULE 3.45 a slave "MUST NOT assert more than one of [ACK_O], [ERR_O]
// or [RTY_O]" at any time.
// DESC LOCK_O "the INTERCON does not grant the bus to any other MASTER,
// until the current MASTER negates [LOCK_O] or [CYC_O]" - a
// signal description, not a numbered rule, and the only place
// B3 constrains a grant decision at all.
//
// Nothing here checks arbitration. There is no rule to check it against.
// ─────────────────────────────────────────────────────────────────────────
module wb_rule_mon (
input logic clk_i,
input logic rst_i,
input logic cpu_cyc_i,
input logic cpu_stb_i,
input logic dma_cyc_i,
input logic dma_stb_i,
input logic s_cyc_i,
input logic s_stb_i,
input logic s_lock_i,
input logic s_ack_i,
input logic s_err_i,
input logic s_rty_i,
input logic [1:0] owner_i,
output int unsigned v_stb_wo_cyc_o, // RULE 3.25
output int unsigned v_term_wo_cyc_o, // RULE 3.30
output int unsigned v_multi_term_o, // RULE 3.45
output int unsigned v_lock_moved_o // DESC LOCK_O
);
logic [1:0] prev_owner_q;
logic prev_locked_q;
logic [2:0] term3;
assign term3 = {s_ack_i, s_err_i, s_rty_i};
always_ff @(posedge clk_i) begin
if (rst_i) begin
v_stb_wo_cyc_o <= 0; v_term_wo_cyc_o <= 0;
v_multi_term_o <= 0; v_lock_moved_o <= 0;
prev_owner_q <= 2'd0; prev_locked_q <= 1'b0;
end else begin
v_stb_wo_cyc_o <= v_stb_wo_cyc_o
+ ((cpu_stb_i && !cpu_cyc_i) ? 1 : 0)
+ ((dma_stb_i && !dma_cyc_i) ? 1 : 0)
+ ((s_stb_i && !s_cyc_i) ? 1 : 0);
if (!s_cyc_i && (term3 != 3'b000))
v_term_wo_cyc_o <= v_term_wo_cyc_o + 1;
if (term3 != 3'b000 && term3 != 3'b100 && term3 != 3'b010
&& term3 != 3'b001)
v_multi_term_o <= v_multi_term_o + 1;
if (prev_locked_q && owner_i != prev_owner_q)
v_lock_moved_o <= v_lock_moved_o + 1;
prev_owner_q <= owner_i;
prev_locked_q <= s_cyc_i && s_lock_i;
end
end
endmoduleReading it — the most important zero in this module
The monitor's table is in the run above, and every cell of it is zero.
STB without CYC 0 · term without CYC 0 · multi-term 0 · lock moved 0 — for all four rigs.
Three of those four interconnects are catastrophically broken. One assembles transfers out of two masters' fields and turns a read into a write. One hands a master another master's data and reports transfers that never happened. One changes owner in the middle of an unanswered phase. And every one of them satisfies RULE 3.25, RULE 3.30 and RULE 3.45 completely.
This is not a gap in the monitor. Those are the only rules there are to check here. B3 does not contain a rule an interconnect can violate by routing badly, because the specification does not describe an interconnect's internal behaviour at all — it describes a MASTER interface, a SLAVE interface, and what passes between them. The INTERCON is named in the glossary and left to you.
The practical consequence is worth stating flatly. A Wishbone protocol checker attached to the shared path will pass a broken multi-master system. A checker attached to each master's port will also pass it — every master in every rig obeyed every rule it is subject to. The defects live in the wiring between them, and the only instruments that find them are the ownership-aware ones in Section 6.
One honest limit on the monitor's last column. Lock moved: 0 is true, and under this interconnect's default policy it could not have been otherwise: an owner is retained for the whole of its CYC_O, which already satisfies LOCK_O's requirement. The column is not measuring a mechanism here; it is confirming that the policy subsumes one. Section 9 says what that means.
8. Ownership, CYC_O and LOCK_O
CYC_O tells the interconnect that a bus cycle is in progress. RULE 3.25 fixes its extent — asserted no later than the edge qualifying STB_O, negated no earlier than the edge qualifying its negation. Ownership must therefore last at least as long as CYC_O, and any policy that grants it away sooner is producing the preempt rig.
What CYC_O does not do is confer ownership. It is a request in this architecture, described as one by the specification "in these cases" and "often", and Chapter 16.2 counted six clocks in which it was asserted by a master that did not own the path.
LOCK_O answers a different question, and Module 15 established it. Ordinary ownership answers "who drives the bus now?" LOCK_O answers "may the interconnect hand ownership to another master before this cycle ends?" — in the words of its signal description:
Once the transfer has started, the INTERCON does not grant the bus to any other MASTER, until the current MASTER negates
[LOCK_O]or[CYC_O].
That is the only sentence in B3 that constrains a grant decision, and it is a signal description rather than a numbered rule.
Here is the part that is easy to get wrong. Under this chapter's default policy, a locked cycle is already protected and the lock term does nothing — retaining an owner for the whole of its CYC_O satisfies the requirement structurally. The monitor's zero says so.
That is not a general truth. Set RELEASE_ON_IDLE and the policy re-evaluates ownership whenever the owner is not presenting, and then the lock is the only thing holding the bus. That is Chapter 15.4's interconnect, and it measured what a system loses when HONOUR_LOCK is cleared: an interloper at a named clock, and a lost update.
So the correct statement is conditional, and it is the one to carry into a design review. Whether your interconnect needs an explicit lock term depends on its release policy. A policy that can release an owner mid-cycle needs one. A policy that cannot, does not. Knowing which kind you have is the question; assuming LOCK_O is either always necessary or always redundant is how both mistakes get made.
And LOCK_O never decides who wins. It protects an owner that already has the bus. Acquisition is a separate question with a separate answer, and Module 17 is where that answer is designed.
9. Failure Modes and Discriminating Evidence
SYMPTOM — a write lands at the wrong address, with the right data.
Candidates. Request-context mixing. An owner selected combinationally while the address mux is selected from something else. Two muxes with different select expressions.
Discriminating evidence. The shared ADR and DAT against each master's own ADR and DAT, on the same clock. If the shared address matches one master and the shared data matches the other, the context is split — and the count of such clocks is v_context_mix. Do not start from the slave. The slave performed the transfer it was given, correctly.
Where to look. The forward always_comb. One condition evaluated once, not four times.
SYMPTOM — a master completes a transaction it never issued.
Candidates. Broadcast return. A missing ownership qualifier on one of the three termination classes. A master whose state machine advances on ACK_I without checking that it was granted.
Discriminating evidence. Client completions against bus terminations. They must be equal. If completions exceed terminations, a termination was delivered more than once — and the identity holds regardless of arbitration policy, so it needs no model of the interconnect.
Where to look. The return assignments, all six of them. An interconnect that gates ACK and broadcasts ERR is a real shape.
SYMPTOM — a DMA copy is corrupted only when CPU traffic is present.
Candidates. Every defect in this chapter. All three are invisible with one master, because all three trigger on both masters being active.
Discriminating evidence. First: does the corruption survive removing the second master? If not, it is in the interconnect and not in either device. Then the three counters — context, non-owner termination, owner change mid-phase — name which one. In SIM F the DMA read the wrong word because the shared WE was not its own; in SIM G a master read the right memory and was handed the wrong answer. The two look identical from the DMA's log.
SYMPTOM — the owner changes while a slave is waiting.
Candidates. Ownership recomputed every clock. A release condition keyed on STB rather than CYC. A priority encoder with no retention state.
Discriminating evidence. The owner value on consecutive clocks, filtered to clocks where a phase was presented and unanswered. That is v_owner_change_active, and in the preempt rig it fired once in the whole run. A defect that fires once is still a defect, and it will fire more often against a slower slave.
Where to look. Whether the ownership state machine has a state at all. A pure combinational selector has this bug by construction.
SYMPTOM — everything passes and the system is still wrong.
Candidates. You are running a protocol checker on a routing problem.
Discriminating evidence. Section 7's table. Rule conformance and interconnect correctness are different properties, and B3 only has rules for the first.
10. Verification
Thirteen properties, and the classification is the point.
| # | property | class |
|---|---|---|
| P1 | owner encoding is one of NONE / CPU / DMA | local |
| P2 | no transfer presented with owner NONE | local |
| P3 | shared ADR/WE/SEL/DAT all come from the owner | local, motivated by RULE 3.60 |
| P4 | a non-owner sees no termination | local |
| P5 | no master termination without a shared-path termination | local |
| P6 | owner stable while a phase is unanswered | local policy |
| P7 | STB_O implies CYC_O, on every port | RULE 3.25 |
| P8 | at most one of ACK/ERR/RTY | RULE 3.45 |
| P9 | no termination while CYC is negated | RULE 3.30 |
| P10 | a locked owner is not displaced | [LOCK_O] description |
| P11 | DMA visits SRC+i and DST+i exactly | local |
| P12 | the word written is the word read | local |
| P13 | a master holds its request until answered | local |
Four of thirteen are spec-derived, and one of those four is a signal description rather than a numbered rule. Nine are this system's policy. Calling any of the nine a rule would be exactly the error this module exists to prevent — and it would make a reviewer look for a conformance failure that does not exist.
SVA REVIEWED BY INSPECTION ONLY. Icarus Verilog does not execute concurrent assertions; the property file was run through iverilog -g2012 and rejected with "sorry: concurrent_assertion_item not supported". None of the thirteen properties was executed as an assertion.
Every one of them has a procedural equivalent that was executed, and those are what the simulations report: P2, P3, P4 and P6 are counters in wb_bus_probe; P7, P8, P9 and P10 are counters in wb_rule_mon; P5 is the completions-versus-terminations identity in SIM G; P11 and P12 are the address log in SIM B and the COPY INTEGRITY checker. The mapping is named on each property in the source.
Elaboration is not synthesis. All eight published modules elaborate individually and together under iverilog -g2012, with no duplicate module names. Every always_comb block assigns all of its outputs on every path, so none infers a latch; every output has exactly one driver by inspection; and no combinational loop exists — one would not have produced ten deterministic runs. No synthesis tool was run, and no area, timing or resource claim is made anywhere in this module.
11. Common Mistakes
"Two masters can just drive the same bus."
Why it is wrong: in RTL that is multiple drivers on one net, which is not a design. One coherent request context must be selected, and SIM F measures what happens when the selection is per-field instead of per-master.
"The request mux is the interconnect."
Why it is wrong: it is half of it. SIM G's rig has a perfect forward mux — every field routed from exactly the right master, zero context violations — and it hands a master a read it never issued. The return path is the other half and it has no rule protecting it.
"ACK can be broadcast, because only the master that asked will care."
Why it is wrong: a waiting master is in its transfer state with STB_O asserted. It is precisely the state that consumes an ACK. "Only one will care" is true only if only one is waiting, which is the case that never needed an interconnect. Thirteen completions from eight terminations.
"If my master is not the owner, it can at least watch ACK to know when the bus is free."
Why it is dangerous: ACK belongs to a transfer, not to the bus. A shared-path ACK says one phase ended — it does not say the owner's cycle ended, and under this policy the owner keeps the bus until CYC_O drops, which may be many phases later. A master that infers availability from ACK will infer it wrongly, and if the interconnect broadcasts, it will consume it as its own.
"LOCK_O decides who wins arbitration."
Why it is wrong: LOCK_O protects an owner that already has the bus. It has nothing to say about acquisition. A master that asserts LOCK_O while waiting is asking for nothing it would not otherwise get.
"An owner change mid-transfer will corrupt data, so a data test will find it."
Why it is wrong: the preempt rig's copy came out correct. It corrupted nothing in this run. A structural checker found it; a functional one did not. Whether a mid-phase handover corrupts anything depends on the slave, the wait-state profile and which master returns first — which is another way of saying it depends on the day.
"Starvation is a protocol error."
Why it is wrong: no rule in B3 promises any master will ever own the bus. Chapter 16.4 measures a master that gets none for sixty clocks, and every transfer in that run is conformant.
12. Interview Reasoning
"Why must response routing follow ownership?"
Because a termination is the answer to one transfer, and exactly one master issued it. RULE 3.45 guarantees the slave raises one termination; it says nothing about how many masters receive it. Delivering it to a non-owner makes that master advance a state machine on an event that had nothing to do with it.
"What happens if ACK is broadcast to both masters?"
A waiting master completes. It reports success to its client, captures whatever is on the shared read bus, and moves on. In SIM G the CPU returned 0xAAAA0010 for a read of 0x00C — and its transfer never appeared on the bus at all.
"Why is mixing the address from one master with the data from another catastrophic rather than just untidy?"
Because it produces a transfer no master issued, and every party believes it was theirs. The memory performs it correctly. The owner is acknowledged. In SIM F a read became a write, so the engine both corrupted a location and mis-captured its own source word. Nothing in the system is in a position to notice.
"When should ownership be released?"
That is local policy, and the two common answers have different costs. Release on CYC_O negated — this chapter's — gives up the bus on any pause, which cost the DMA a full extra tenure in SIM E. Hold across pauses makes the other master wait instead. What is not a choice is releasing while a phase is unanswered.
"How do wait states affect ownership?"
They do not. The slave is not party to the arbitration and cannot see it. SIM E held one owner across a three-clock phase with a second master asking throughout, and the owner did not move. A design in which a wait state can change the owner has the preempt defect.
"You inherit an interconnect. What do you check before trusting it?"
Four things, none of which a protocol checker covers. Is there exactly one forward context per transfer? Is every termination class gated by ownership? Can the owner change while a phase is unanswered? Do client completions equal bus terminations? Section 7 is the reason the protocol checker is not on that list.
13. Understanding Check
In SIM E the CPU asserted CYC_O and STB_O for three clocks while the DMA owned the bus. Why did it not receive the acknowledgement on clock 7?
Because cpu_ack_o is sel_cpu && s_ack_i and sel_cpu was false. The gating is in the interconnect, not in the master — the master has no way to refuse an ACK it is given.
The read-data path is not gated by ownership. Why is that not a bug?
Because read data means nothing except on the clock a termination qualifies it. Both masters see the shared read bus every clock; only the one that receives a termination captures it. Gating the terminations is what makes gating the data unnecessary — which SIM G confirms in the negative, by ungating the terminations and immediately producing wrong data.
The preempt rig's copy came out correct. Should the defect be fixed?
Yes. It produced exactly one owner change inside an unanswered phase, and that is an unsound interconnect whether or not this particular run was damaged by it. The correctness of the data was a property of the stimulus, not of the design.
Your interconnect gates ACK and ERR by ownership but forwards RTY to both masters. What happens?
Under most traffic, nothing — RTY is rare, and a shared memory raises it only when it is busy. When it does happen, a waiting master will abandon and restart a transfer it never presented, and by Chapter 11.4 it will do so correctly and pointlessly, consuming a retry budget against a condition that was never its own.
A colleague says the interconnect is fine because the Wishbone checker passes. What do you say?
That the checker is answering a different question. Section 7 ran four interconnects through a monitor for RULE 3.25, RULE 3.30 and RULE 3.45 and all four were clean, including one that turns reads into writes. Rule conformance is necessary and it is not sufficient, and the specification never claimed otherwise — it hands the interconnect to the integrator by name.
14. What's Next
Ownership is now correct, in both directions, and verified against defects that were built to break it.
Once ownership is correct, what can a master assume about the state it shares?
Chapter 16.4 — Resource Sharing answers: less than it could before. A word changes between two correct reads with nothing on the bus to say so, the same splitter appears in a second place to show that contention is a property of a path rather than of having two masters, and one master gets no service at all for sixty clocks without a single rule being broken.
Continue learning
Related tutorials
- Related topic
Multi-Master Systems
Byte-identical masters against two interconnects. The ownership timeline names clock 19, and three checkers are validated against the defects they claim to catch.
- Related topic
Routing
Forward and return routing as one property with five parts, measured on both topologies — and five broken interconnects in which every master and every slave still obeys every rule in B3.
- 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.
