Wishbone · Module 15
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.
Chapter 15.3 bounded what a bus lock can reach. Two chapters have used a two-master system as laboratory equipment without looking at it closely.
What does the interconnect have to do, and what happens when it does not?
1. RTL — The Smallest Thing That Can Give Two Masters One Slave
// ─────────────────────────────────────────────────────────────────────────
// wb_owner2 — the smallest thing that can give two masters one slave.
//
// THIS IS LABORATORY EQUIPMENT, NOT AN ARBITER DESIGN. It exists so that
// atomicity can be measured against competition, because atomicity cannot
// be demonstrated honestly with one master. Fairness, starvation, priority
// schemes, round-robin and scalable fabrics are Module 17's subject and
// none of them appear here: this selector is fixed-priority, two-port, and
// deliberately uninteresting.
//
// The specification is explicit that this is the integrator's territory:
// "Arbitration methodology is defined by the end user (priority arbiter,
// round-robin arbiter, etc.)." So every behaviour below is LOCAL POLICY.
//
// THE ONE PARAMETER THAT MATTERS: HONOUR_LOCK.
//
// HONOUR_LOCK = 1 once a master owns the bus with LOCK_O asserted, the
// owner is not changed until that master negates LOCK_O
// or CYC_O. This is the behaviour LOCK_O's description
// states: "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]."
//
// HONOUR_LOCK = 0 ownership is re-evaluated whenever the current owner
// is not presenting a transfer, LOCK_O or not.
//
// HONOUR_LOCK = 0 IS NOT A PROTOCOL VIOLATION IN ANY RULE I CAN CITE. No
// numbered rule in the Classic chapter constrains an arbiter. What it
// violates is LOCK_O's stated meaning, and Chapter 15.4 measures what a
// system loses when a signal's meaning is not implemented.
//
// RECOMMENDATION 3.05 is the closest the specification comes to describing
// arbiter behaviour: "Arbitration logic often uses [CYC_I] to select
// between MASTER interfaces." Note "often" - it is advisory, and it
// describes selection, not retention.
// ─────────────────────────────────────────────────────────────────────────
module wb_owner2 #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter bit HONOUR_LOCK = 1'b1
) (
input logic clk_i,
input logic rst_i,
// master A
input logic a_cyc_i,
input logic a_stb_i,
input logic a_lock_i,
input logic a_we_i,
input logic [AW-1:0] a_adr_i,
input logic [DW-1:0] a_dat_i,
input logic [DW/8-1:0] a_sel_i,
output logic [DW-1:0] a_dat_o,
output logic a_ack_o,
output logic a_err_o,
// master B
input logic b_cyc_i,
input logic b_stb_i,
input logic b_lock_i,
input logic b_we_i,
input logic [AW-1:0] b_adr_i,
input logic [DW-1:0] b_dat_i,
input logic [DW/8-1:0] b_sel_i,
output logic [DW-1:0] b_dat_o,
output logic b_ack_o,
output logic b_err_o,
// slave side
output logic s_cyc_o,
output logic s_stb_o,
output logic s_we_o,
output logic [AW-1:0] s_adr_o,
output logic [DW-1:0] s_dat_o,
output logic [DW/8-1:0] s_sel_o,
input logic [DW-1:0] s_dat_i,
input logic s_ack_i,
input logic s_err_i,
// observation only
output logic owner_o, // 0 = A, 1 = B
output logic owned_o // somebody holds it
);
logic owner_q, owned_q;
// The current owner's own signals, whoever that is.
logic cur_cyc, cur_stb, cur_lock;
assign cur_cyc = owner_q ? b_cyc_i : a_cyc_i;
assign cur_stb = owner_q ? b_stb_i : a_stb_i;
assign cur_lock = owner_q ? b_lock_i : a_lock_i;
// Who else wants it.
logic other_cyc;
assign other_cyc = owner_q ? a_cyc_i : b_cyc_i;
// OWNERSHIP, in one always_comb so the precedence is readable.
//
// This selector implements LOCK_O's stated meaning and nothing more:
// "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]." A LOCKED owner is therefore untouchable until it drops one.
//
// An UNLOCKED owner is a case the specification does not legislate.
// RECOMMENDATION 3.05 observes only that arbiters "often use [CYC_I] to
// select between MASTER interfaces", and warns that "keeping [CYC_O]
// asserted may lead to arbitration problems". This selector's LOCAL
// POLICY is the ordinary one that warning describes: an unlocked owner
// that is not presenting a transfer yields to a master that is waiting.
//
// That single rule is what makes retaining CYC_O and asserting LOCK_O
// measurably different. Without it, an owner would keep the bus through
// its own idle clocks and every policy would look identical.
//
// With HONOUR_LOCK cleared, the locked branch is removed and a locked
// owner is treated exactly like an unlocked one - Chapter 15.4's broken
// interconnect.
logic next_owner, next_owned;
always_comb begin
next_owner = owner_q;
next_owned = owned_q;
if (!owned_q) begin
if (a_cyc_i) begin next_owner = 1'b0; next_owned = 1'b1; end
else if (b_cyc_i) begin next_owner = 1'b1; next_owned = 1'b1; end
end else if (!cur_cyc) begin
// the owner has finished its cycle
if (other_cyc) begin next_owner = ~owner_q; next_owned = 1'b1; end
else next_owned = 1'b0;
end else if (HONOUR_LOCK && cur_lock) begin
next_owner = owner_q; // locked: nothing moves
end else if (!cur_stb && other_cyc) begin
next_owner = ~owner_q; // unlocked and idle: yield
end
end
always_ff @(posedge clk_i) begin
if (rst_i) begin
owner_q <= 1'b0; owned_q <= 1'b0;
end else begin
owner_q <= next_owner;
owned_q <= next_owned;
end
end
// Route the owner's request down, and the slave's answer back to the
// owner only. A non-owner sees no termination and no data, so it simply
// waits - which is what makes it a competitor rather than a corrupter.
assign s_cyc_o = owned_q && (owner_q ? b_cyc_i : a_cyc_i);
assign s_stb_o = owned_q && (owner_q ? b_stb_i : a_stb_i);
assign s_we_o = owner_q ? b_we_i : a_we_i;
assign s_adr_o = owner_q ? b_adr_i : a_adr_i;
assign s_dat_o = owner_q ? b_dat_i : a_dat_i;
assign s_sel_o = owner_q ? b_sel_i : a_sel_i;
assign a_ack_o = owned_q && !owner_q && s_ack_i;
assign a_err_o = owned_q && !owner_q && s_err_i;
assign a_dat_o = s_dat_i;
assign b_ack_o = owned_q && owner_q && s_ack_i;
assign b_err_o = owned_q && owner_q && s_err_i;
assign b_dat_o = s_dat_i;
assign owner_o = owner_q;
assign owned_o = owned_q;
endmoduleReading it
HONOUR_LOCK is the only functional difference between SIM G's system and SIM H's. One branch of one always_comb.
The locked branch is LOCK_O's description turned into logic. "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." — so while cur_cyc && cur_lock, ownership does not move.
The unlocked branch is the case the specification does not legislate, and the policy chosen here is the ordinary one: an unlocked owner that is not presenting a transfer yields to a waiting master. RECOMMENDATION 3.05 warns in exactly this direction — "Keeping CYC_O asserted may lead to arbitration problems."
That single rule is what makes CYC_O retention and LOCK_O measurably different. Without it, an owner would keep the bus through its own idle clocks and every policy in Chapter 15.2's table would look identical.
Response routing is the other half of correctness and it is three lines. A non-owner sees no termination and no data, so it waits — which is what makes it a competitor rather than a corrupter. A selector that let a non-owner see the slave's acknowledgement would produce two masters believing one answer.
2. RTL — The Instrument
Every claim in this chapter is a claim about ownership over an interval, so the interval has to be defined in one place.
// ─────────────────────────────────────────────────────────────────────────
// wb_rmw_probe — the ownership timeline, and three checkers built on it.
//
// The three checkers are the ones Chapter 15.4 validates in BOTH
// directions: each must pass a correct system AND fail a targeted broken
// one, for the reason it claims to check. A checker that has only ever
// passed is an untested checker.
//
// OWNER STABILITY ownership does not change while a locked owner is
// mid-operation. Targeted defect: an owner selector
// that ignores LOCK_O.
// COMMIT COUNT one successful increment produces exactly one write
// commit. Targeted defect: a slave that re-executes a
// write while its termination is held.
// ATOMIC RESULT N successful increments move the counter by exactly
// N. Targeted defect: any lost update.
//
// The protected interval is defined here once, because every ownership
// claim in the module depends on which clocks are inside it:
//
// from the clock a master's CYC_O is first asserted for the operation
// through the clock its write phase is answered
//
// That is the interval an RMW must protect. An owner change inside it is
// the defect; an owner change outside it is ordinary arbitration.
//
// Everything here is simulation instrumentation. `int unsigned` counters
// are not synthesizable and none of this is a Wishbone feature.
// ─────────────────────────────────────────────────────────────────────────
module wb_rmw_probe (
input logic clk_i,
input logic rst_i,
input logic owner_i, // 0 = A, 1 = B
input logic owned_i,
// the two masters' own signals
input logic a_cyc_i,
input logic a_lock_i,
input logic b_cyc_i,
input logic b_lock_i,
// the slave's commit evidence
input logic wr_commit_i, // one clock per committed write
output int unsigned owner_changes_o,
output int unsigned unsafe_changes_o, // changes inside a protected interval
output int unsigned commits_o,
output int unsigned a_cycles_o,
output int unsigned b_cycles_o
);
logic owner_q, owned_q, a_cyc_q, b_cyc_q;
// A master is mid-operation while its own CYC_O is asserted. Protection
// is claimed only while it also asserts LOCK_O - that is the difference
// between requesting the resource and holding it.
logic a_protected, b_protected, cur_protected;
assign a_protected = a_cyc_i && a_lock_i;
assign b_protected = b_cyc_i && b_lock_i;
assign cur_protected = owner_i ? b_protected : a_protected;
always_ff @(posedge clk_i) begin
if (rst_i) begin
owner_q <= 1'b0; owned_q <= 1'b0; a_cyc_q <= 1'b0; b_cyc_q <= 1'b0;
owner_changes_o <= 0; unsafe_changes_o <= 0; commits_o <= 0;
a_cycles_o <= 0; b_cycles_o <= 0;
end else begin
owner_q <= owner_i; owned_q <= owned_i;
a_cyc_q <= a_cyc_i; b_cyc_q <= b_cyc_i;
if (owned_q && owned_i && (owner_i != owner_q)) begin
owner_changes_o <= owner_changes_o + 1;
// Was the master that just lost the bus still holding it under
// lock? If so the selector took the bus from a protected owner.
if (owner_q ? b_protected : a_protected)
unsafe_changes_o <= unsafe_changes_o + 1;
end
if (wr_commit_i) commits_o <= commits_o + 1;
if (a_cyc_i && !a_cyc_q) a_cycles_o <= a_cycles_o + 1;
if (b_cyc_i && !b_cyc_q) b_cycles_o <= b_cycles_o + 1;
end
end
endmoduleReading it
unsafe_changes_o is the module's central measurement and its definition is narrow on purpose. It counts an ownership change only when the master losing the bus was asserting both CYC_O and LOCK_O — that is, only when it had actually claimed protection.
That narrowness is why Chapter 15.1's CYC-only row reports zero unsafe changes while losing an update. That master never asserted LOCK_O. It was not robbed; it never asked. A counter that flagged every ownership change would have reported a violation there and been wrong about what happened.
The commit input comes from the slave's own register update, not from acknowledgements. Section 6 shows why: a slave that executes a write several times per phase still produces one acknowledgement, so a bus-side counter cannot see that defect at all.
3. Simulation — SIM G: An Interconnect That Honours LOCK_O
Both masters released on the same clock, both asserting LOCK_O across their read and write halves. The timeline records every clock on which the slave port is active.
=== SIM G - two masters, the selector honours LOCK_O ===
Both masters are released on the same clock. Both assert
LOCK_O across their read and write halves.
clk owner CYC LOCK STB WE ACK phase
4 A 1 1 1 0 1 read answered
5 A 1 1 0 0 0 -
6 A 1 1 1 1 1 write commits
8 B 1 1 1 0 1 read answered
9 B 1 1 0 0 0 -
10 B 1 1 1 1 1 write commits
A read 10, wrote 11 B read 11, wrote 12
owner changes 1
owner changes inside a protected interval 0
write commits 2
counter 10 -> 12 expected 12
Ownership changed once, and it changed between the two
operations rather than inside either of them.Reading it
Read the owner column down the timeline: A, A, A, then B, B, B. One change, and it falls between the two operations rather than inside either.
Clock 5 is the interesting one. The owner is A, CYC is high, LOCK is high, and STB is low — A is presenting nothing. That is exactly the clock an unlocked owner would have yielded on, and Chapter 15.1's CYC-only row did. Here the lock holds it.
A read 10 and wrote 11. B read 11 and wrote 12. B's read was not served until A's write had committed, so it saw the updated value and derived from it. Counter 10 → 12.
Owner changes inside a protected interval: 0. Two write commits for two increments.
4. Simulation — SIM H: An Interconnect That Does Not
Identical masters, identical slave, identical stimulus. HONOUR_LOCK is cleared and nothing else changes.
=== SIM H - the same masters, the selector ignores LOCK_O ===
Identical masters, identical slave, identical stimulus.
HONOUR_LOCK is cleared, so a locked owner is treated
exactly like an unlocked one.
clk owner CYC LOCK STB WE ACK phase
17 A 1 1 1 0 1 read answered
18 A 1 1 0 0 0 -
19 B 1 1 1 0 1 read answered
20 B 1 1 0 0 0 -
21 A 1 1 1 1 1 write commits
23 B 1 1 1 1 1 write commits
A read 10, wrote 11 B read 10, wrote 11
owner changes 3
owner changes inside a protected interval 2
write commits 2
counter 10 -> 11 expected 12
Both masters are byte-identical to SIM G's. Both asserted
LOCK_O for the whole interval. The signal was correct and
the interconnect did not implement its meaning.Reading it — the timeline names the clock
Clock 18: owner A, LOCK high, STB low. A has read 10 and is between its halves.
Clock 19: owner B. The selector granted the bus to B while A was holding a lock. B reads 10 — the value A has already captured and is about to supersede.
Clock 21: owner A again, write commits 11. Clock 23: B commits 11. Counter 10 → 11.
Owner changes: 3. Inside a protected interval: 2.
And notice what the ownership timeline gives that the final value does not. "Counter is 11, expected 12" says something is wrong. "Owner changed at clock 19 while A held a lock" says what, where and whose.
When the other master was able to enter
10 cyclesThe two owner rows are identical except at cycle 3, and cycle 3 is the clock on which A: STB_O is low while A: CYC_O and A: LOCK_O are both high.
That is the whole difference. A is between its halves, presenting nothing, and holding a lock. One interconnect treats the lock as binding and one does not.
The bottom row is the consequence. B reads 10 — a value A captured two clocks earlier and is about to supersede — and it will write 11 over A's 11.
5. Simulation — SIM I: Wait States Inside the Protected Interval
The slave withholds its termination for two clocks on every phase, so each half of each operation waits. The interval an interloper would need to enter is three times longer.
=== SIM I - wait states inside the protected interval ===
The slave withholds its termination for two clocks on
every phase, so each half of each RMW waits.
clk owner CYC LOCK STB WE ACK phase
30 A 1 1 1 0 0 read
31 A 1 1 1 0 0 read
32 A 1 1 1 0 1 read answered
33 A 1 1 0 0 0 -
34 A 1 1 1 1 0 write
35 A 1 1 1 1 0 write
36 A 1 1 1 1 1 write commits
38 B 1 1 1 0 0 read
39 B 1 1 1 0 0 read
40 B 1 1 1 0 1 read answered
41 B 1 1 0 0 0 -
42 B 1 1 1 1 0 write
43 B 1 1 1 1 0 write
44 B 1 1 1 1 1 write commits
owner changes inside a protected interval 0
write commits 2
counter 10 -> 12 expected 12
Longer interval, same guarantee. The wait states stretch
the window an interloper would need, and ownership did
not move for any of those clocks. Note also that a held
phase produced ONE commit, not one per clock.Reading it
Fourteen active clocks instead of six, and the owner column still reads A, A, A, A, A, A, A, then B seven times.
Zero owner changes inside a protected interval. The wait states stretched the window and the lock covered all of it — because the master's LOCK_O is derived from its state machine, which leaves the read, gap and write states only on an answered phase. The release cannot precede the commit by construction rather than by timing.
And the commit count is the second finding in this run. Each write phase is presented for three clocks and commits once. wr_fire is true only on the answered edge, so a held phase produces one register update. Chapter 15.1 called this load-bearing; this is where it bears the load — a slave committing per presented clock would have written six times here.
This also reconnects Module 9 directly — the transaction extension Chapter 9.4 measured. A wait state is the slave withholding its termination, and it has no effect on ownership whatsoever — the lock is a master-to-interconnect statement and the slave is not party to it.
6. The Negative-Control Gate
A checker that has only ever passed has not been shown to check anything. Each of the three below is run twice: once against a correct system, once against a system containing the specific defect it claims to detect.
No checker was weakened to make a broken case fail. Where a checker initially passed both, the experiment was wrong and was corrected — Section 7 says which.
=== NEGATIVE-CONTROL GATE ===
Each checker run against a correct system and against the
one defect it claims to detect. A checker that cannot
fail has not been shown to check anything.
checker invariant correct broken
OWNER STABILITY no owner change inside a PASS FAIL
protected interval
COMMIT COUNT two increments, two write PASS FAIL
commits
ATOMIC RESULT counter moves by exactly two PASS FAIL
targeted defects:
OWNER STABILITY wb_owner2 with HONOUR_LOCK = 0
COMMIT COUNT slave committing on every presented clock
ATOMIC RESULT the same lock-ignoring selector
'commits' below is the slave's own register-update count,
not a count of acknowledgements. The held-phase rig
produces one ACK per phase either way.
evidence behind each verdict:
correct rig unsafe owner changes 0 commits 2 counter 12
lock-ignoring unsafe owner changes 2 commits 2 counter 11
held-phase unsafe owner changes 0 commits 6 counter 12
CHECKERS REQUIRED >= 3
CHECKERS PASSING CORRECT DUT 3 / 3
CHECKERS FAILING TARGET DEFECT 3 / 3Reading it
Three checkers, three correct-system passes, three targeted failures. Each fails for the reason it claims.
The third row of the evidence block is the one worth studying. The held-phase rig shows 6 commits and a counter of 12 — the final value is correct.
So ATOMIC RESULT passes that rig. The counter moved by exactly two, because writing 11 three times leaves 11. Only COMMIT COUNT sees the defect, and it sees it because it reads the slave's own register-update count rather than counting acknowledgements on the bus.
That is the case for having three checkers rather than one. Each catches something the others cannot:
| defect | OWNER STABILITY | COMMIT COUNT | ATOMIC RESULT |
|---|---|---|---|
interconnect ignores LOCK_O | catches | misses | catches |
| slave commits per presented clock | misses | catches | misses |
| a stale read from any cause | misses | misses | catches |
A single end-to-end check would have passed the middle row, and on a FIFO port or a command register that defect is destructive rather than harmless.
7. What This Gate Cost, Honestly
Two of the three checkers worked first time. The third did not, and the reason is worth publishing.
COMMIT COUNT initially passed both the correct system and the broken one. The "broken" rig was a slave with wait states, which is not broken at all — a held phase still commits once. The experiment was testing nothing.
The fix was to build the real defect, not to loosen the checker: a slave parameter that gates the commit on the transfer being presented rather than answered, which is the repeated-side-effect failure Modules 5, 7, 9 and 11 each measured in their own way.
It still passed both. The second problem was the instrument: the commit counter was reading ACK on the bus, and a slave that commits six times still acknowledges once per phase. Pointing the counter at the slave's own register update made the defect visible — 6 against 2.
Both corrections were to the experiment. Neither weakened an invariant, and the gate's value is precisely that it forced them: before this, the commit checker had never failed and there was no evidence it could.
8. Simulation — SIM J: Errors Inside a Read-Modify-Write
The slave implements one offset and answers ERR for any other, which is Chapter 12.3's ordinary unmapped behaviour. It also supports write-protection. Both give a genuine ERR without a specially faulty slave.
=== SIM J - errors inside a read-modify-write ===
The slave implements offset 0 and answers ERR for any
other offset, which is Module 12's ordinary unmapped
behaviour. Aiming a phase at offset 1 injects a real ERR.
--- ERR on the read half ---
client result failure
read phases served 0
write commits 0
counter 10 -> 10
LOCAL MASTER POLICY: an error on the read abandons the
operation. No write phase was presented, so nothing was
committed and there is nothing to undo. The client is
told the operation failed.
--- ERR on the write half ---
value read 10
value the master derived 11
client result failure
read phases served 1
write commits 0
counter 10 -> 10
The read succeeded and the write did not. The client is
told failure, and the counter is unchanged.
THE COUNTER IS UNCHANGED BECAUSE OF THIS SLAVE, NOT
BECAUSE OF WISHBONE. wb_counter_slave gates its commit on
an acknowledged write, so a phase it answers with ERR
commits nothing - LOCAL SLAVE POLICY, and verifiable in
its source. A slave that latched first and errored after
would leave the value changed and report failure, and
nothing in the specification forbids that.
NOTHING HERE IS A ROLLBACK. Wishbone defines error
termination; it defines no mechanism for undoing a
committed write, and no master can restore a value it
never knew had changed.
Across both cases: one client request, one client
completion, and no partial success reported as success.Reading it
The read-half error is the simple case. Nothing was committed because no write was ever presented, and there is nothing to undo. One client request, one client completion, classified as failure.
The write-half error is the one that needs care. The read succeeded, the master derived 11, the write was refused, and the counter is unchanged.
And the counter is unchanged because of this slave, not because of Wishbone. wb_counter_slave computes its acknowledgement and its commit from the same refused term, so a phase it answers with ERR commits nothing. That is LOCAL SLAVE POLICY and it is verifiable in the source — P12 in Chapter 15.3 states it.
A slave that latched first and errored afterwards would leave the value changed and report failure, and nothing in the specification forbids that. The master would have no way to know which kind it was talking to.
9. Failure Modes and Discriminating Evidence
Symptom: correct masters, correct slave, lost updates.
Candidate causes. An interconnect that does not honour LOCK_I.
Discriminating evidence. An ownership timeline across the interval. An owner change while the losing master asserted CYC_O and LOCK_O is conclusive and names the clock. Inspecting the master will find nothing — measured here with byte-identical masters on both sides.
Likely location: the interconnect, not either master.
Symptom: the final value is correct and something is still wrong.
Candidate causes. Repeated commits, invisible for an idempotent write.
Discriminating evidence. The slave's register-update count against the number of successful operations. Six against two. Acknowledgement counts will not show it, and for a FIFO or command port the same defect is destructive rather than harmless.
Symptom: an operation fails only when the slave is slow.
Candidate causes. A lock released on write presentation rather than write completion.
Discriminating evidence. The last clock on which LOCK_O is asserted, against the clock the write commits. With a zero-wait slave these coincide and the defect is invisible; with wait states they separate. SIM I is the condition that exposes it.
Symptom: an RMW reports success after an ERR.
Candidate causes. The client completion folds all terminations into one path.
Discriminating evidence. The termination class on each phase against the reported client result. P7 requires exactly one of success or failure per completion, and P8 requires an error not to be reported as success.
Symptom: a system works on the bench and fails in the product.
Candidate causes. The bench had one master.
Discriminating evidence. Whether a competitor exists at all. A point-to-point system has no arbiter and no interloper, so every protection scheme in Chapter 15.2's table passes. The defect appears when a second master is added, which is an integration event rather than a code change.
10. Verification
The interconnect's own two properties, and both are local — the specification places no numbered requirement on an arbiter.
// ─────────────────────────────────────────────────────────────────────────
// wb_owner_props — the interconnect's half of atomicity. All LOCAL: the
// specification places no numbered requirement on an arbiter, and states
// that "Arbitration methodology is defined by the end user".
// ─────────────────────────────────────────────────────────────────────────
module wb_owner_props (
input logic clk_i,
input logic rst_i,
input logic owner_i,
input logic owned_i,
input logic a_cyc_i,
input logic a_lock_i,
input logic b_cyc_i,
input logic b_lock_i
);
default disable iff (rst_i);
logic cur_locked;
assign cur_locked = owner_i ? (b_cyc_i && b_lock_i) : (a_cyc_i && a_lock_i);
// P9 — LOCAL INTERCONNECT POLICY, implementing LOCK_O's stated meaning:
// "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]." This is the property wb_owner2 with HONOUR_LOCK = 0
// violates, and it is validated in both directions in Chapter 15.4.
property p_owner_stable_while_locked;
@(posedge clk_i) (owned_i && cur_locked) |=> $stable(owner_i);
endproperty
a_owner_stable_while_locked: assert property (p_owner_stable_while_locked);
// P10 — LOCAL INTERCONNECT POLICY. A master that holds nothing is not
// the owner. Ownership implies somebody is asserting CYC_O.
property p_owned_implies_requesting;
@(posedge clk_i) owned_i |-> (a_cyc_i || b_cyc_i);
endproperty
a_owned_implies_requesting: assert property (p_owned_implies_requesting);
endmoduleP9 is LOCK_O's description written as a property, and it is the one validated in both directions in Section 6 — it passes the honouring selector and fails the one that ignores locks.
P10 is the sanity property that prevents the selector claiming an owner nobody asked to be.
Neither is specification-derived, and the distinction is the module's whole point. A design can satisfy every numbered rule in the Classic chapter and still provide no atomicity, because no numbered rule mentions an arbiter at all.
11. Common Mistakes
"The masters are conformant, so the operation is atomic."
Wrong mental model: conformance composes upward.
What is true: measured false with byte-identical masters. Correct master RTL against an interconnect that ignores LOCK_I loses the update, and every transfer on the bus is conformant throughout.
"The arbiter must hold ownership while LOCK_O is asserted."
Wrong mental model: a rule requires it.
What is true: LOCK_O's description says what an INTERCON does; no numbered rule compels one. Arbitration methodology is "defined by the end user", so an integration has to establish it rather than assume it.
"An error rolls back the operation."
Wrong mental model: the bus has transaction semantics.
What is true: nothing undoes a committed write. This slave commits nothing on an error, which is verifiable in its source and is its policy — a different slave could latch first and error afterwards.
"A held ACK means the write happened more than once."
Wrong mental model: acknowledgement equals commit.
What is true: they are the same only in a well-built slave. SIM I holds a phase for three clocks and commits once. The broken variant commits three times and still acknowledges once — which is why a commit count has to be taken at the resource.
"Checking the final value is enough."
Wrong mental model: one end-to-end check covers everything.
What is true: six commits with a correct final value. For an increment that defect is invisible in the state; on a FIFO port it is data loss.
"Two masters is Module 16's subject, so this is out of scope."
Wrong mental model: any second master means multi-master architecture.
What is true: atomicity cannot be measured without a competitor, and the selector here is laboratory equipment. Why multiple masters exist, how they are arbitrated and how fabrics scale are all Module 16 and Module 17 — none of it is taught here.
12. Interview Reasoning
Atomicity is lost and every master in the system is still conformant.
Measured directly. Two masters asserting LOCK_O across their read and write halves, an interconnect with that branch removed, and the ownership timeline shows the second master granted the bus at clock 19 — while the first was holding a lock, between its read and its write. It reads the stale value and both write 11.
Nothing on the bus is malformed. Four transfers, four terminations, correct addresses, correct data.
And no numbered rule was broken. The Classic chapter places no requirement on an arbiter, and the introduction states that arbitration methodology is "defined by the end user". What was violated is LOCK_O's stated meaning, which is a different thing from a rule.
The practical consequence is the one to lead with in a review: a master cannot verify atomicity by inspecting itself. The guarantee is the interconnect's, and whether a given one honours LOCK_I is an integration fact that RULE 2.15's datasheet should record.
13. Understanding Check
Clock 19.
At clock 18 the owner is A, CYC and LOCK are both high, and STB is low — A has read 10 and is between its halves.
At clock 19 the owner is B. The selector granted the bus to a different master while A was holding a lock.
Everything after that follows correctly from a wrong decision. B reads 10, which is still the true value. Both masters derive 11. Both write it. The counter ends at 11.
The probe reports owner changes inside a protected interval: 2, and the timeline says which clocks they were.
Compare SIM G's timeline at the same point: clock 5, owner A, STB low, and the owner column does not change. Same master, same slave, same stimulus — one branch of one always_comb apart.
14. What Module 15 Established
An operation is atomic only over an interval, only against a named set of competitors, and only if something enforces it.
15.1 — the failure, and the cycle. Two conformant masters, four conformant transfers, and a counter that moves by one. The RMW cycle is defined by §3.4 as read half, write half, CYC_O across both — and measured, that structure alone loses the update.
15.2 — the interval. From before the read is observable to the write's commit, continuously. Two locks that are real, correctly asserted, and useless: CYC_O requests, LOCK_O holds, which the specification says in one sentence that is not in the RMW section.
15.3 — the scope. A perfect lock, an honouring arbiter, no second master, one update lost. LOCK_I promises a slave is "accessed by a single MASTER only" — and everything that is not a master is outside that sentence.
15.4 — the enforcer. Byte-identical masters against two interconnects; the timeline names clock 19. And three checkers validated against the defects they claim to catch, one of which needed two corrections to the experiment before it could fail at all.
The thread is one question asked four ways: what, exactly, is excluded — and by whom? Every defect in this module is an answer assumed rather than established.
15. What's Next
Read-modify-write is complete. The interval, the competitors, the enforcer, and the measured consequences of getting each one wrong.
Two masters have been laboratory equipment for three chapters. Nothing has explained why a system has them, or how an interconnect decides between them when neither holds a lock.
Why do real systems have more than one master, and what does sharing a bus actually require?
Module 16 — Multi-Master Systems takes up the architecture this module borrowed: why a CPU and a DMA engine share an interconnect, how ownership is granted, and what resource sharing costs. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Synchronization
Two locks that are real, correctly asserted and useless. Measured: protecting the read loses an update, and protecting the write loses the same one.
- Related topic
CPU + DMA Systems
Two initiators in contention, with the clocks counted in four columns: holding work, asking for the bus, owning it, and being answered. Contention cost two clocks out of twenty-one.
- Related topic
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.
- Related topic
Masters and Slaves
Master and slave are transaction roles, not a statement about importance or hierarchy. The role determines exactly which information each side owns: the initiator supplies address, direction and write data; the target supplies read data, completion and any error. Getting that ownership wrong is the source of an entire family of integration bugs.
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.
