Two microcontrollers on the same I2C bus decide, at the same instant, to talk to a sensor. There is no arbiter chip, no request lines, no grant lines: just two shared wires and a pull-up resistor on each. Both start sending. One of them finishes its message as though nothing happened. The other notices, a few bits in, that it has lost, and goes quiet without corrupting a single bit.
How can competitors arbitrate with no arbiter, and how does the loser know it lost?
The obvious answer is a central arbiter: every master asks it for permission. That needs request and grant wires for every master, and a single place every master can reach, which a two-wire bus between independent chips does not have. The other obvious answer, "just send and hope", fails when two senders drive opposite values: the wire's value is undefined, and on most buses both messages are destroyed.
Make the wire itself combine competitors' bits in a defined way. With open-drain drivers and a pull-up, any device driving 0 pulls the wire low; the wire is 1 only if everyone releases it. The wire computes the AND of all senders' bits. Now arbitration is a rule each sender follows on its own:
Send your message bit by bit, and read the wire back on every bit.
If you sent 1 (released) but read 0, someone else sent 0: you have lost. Stop driving and listen.
If you sent what you read, keep going. The sender whose message has a 0 where the others have a 1 wins, and its message on the wire is exactly what it sent.
Nobody needs to know who else is competing, and the winner's message is never corrupted. Priority is built into the message: lower values (more leading 0s) win.
Master A sends address 1010010; master B sends 1011010. They agree for three bits and differ at the fourth, where A sends 0 and B sends 1. One bit per clock here; real I2C also synchronises the two masters' clocks on the wired-AND clock line.
// Two I2C masters start at the same moment and send different addresses, MSB first,
// on a wired-AND SDA line: the line is low if either master drives low. A master
// that sends a 1 (releases the line) but reads a 0 has lost: it stops driving and
// listens. Master A sends 1010010 (0x52), master B sends 1011010 (0x5A); they first
// differ at the fourth bit, where A sends 0 and B sends 1. One bit per clock here.
module tb;
logic clk = 0;
always #5 clk = ~clk;
initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end
localparam logic [6:0] ADDR_A = 7'h52, ADDR_B = 7'h5A;
int bit_n = -1; // -1: idle; then bit 0 = MSB
logic lost_a = 0, lost_b = 0;
wire active = (bit_n >= 0) && (bit_n < 7);
wire sda_a = (!active || lost_a) ? 1'b1 : ADDR_A[6 - bit_n]; // 1 = release (pulled up)
wire sda_b = (!active || lost_b) ? 1'b1 : ADDR_B[6 - bit_n];
wire sda = sda_a & sda_b; // wired-AND
always @(posedge clk) begin
if (active) begin
if (!lost_a && sda_a && !sda) lost_a <= 1;
if (!lost_b && sda_b && !sda) lost_b <= 1;
end
if (bit_n < 7) bit_n <= bit_n + 1;
end
// The address seen on the bus must be the winner's, unharmed
logic [6:0] seen = 0;
always @(posedge clk) if (active) seen <= {seen[5:0], sda};
initial begin
#100;
if (lost_b && !lost_a && seen == ADDR_A) $display("PASS: B lost at the fourth bit; the bus carried A's address 0x%0h intact", seen);
else $display("FAIL: lost_a %0b lost_b %0b seen %0h", lost_a, lost_b, seen);
$finish;
end
endmoduleThe whole arbitration is one comparison per master: driving 1 while the wire reads 0 means lost.
How to read it, slot by slot:
Slots 0 to 2: both masters send 1, 0, 1 (sda_a, sda_b); the line (sda) carries the same.
Slot 3: A sends 0, B sends 1 (releases). The line reads 0. B sent 1 but sees 0: it has lost. lost_b rises on the next edge.
Slots 4 to 6: B keeps its line released (1) and only listens; the line follows A: 0, 1, 0. The bus carried 1010010, A's address, as if B had never spoken.
Scheme | When two send at once | Result |
|---|---|---|
Wired-AND bitwise arbitration (I2C, CAN) | The dominant value (0) overrides the recessive one | One winner, message intact; the loser retries later |
Collision detection (classic shared Ethernet) | Both signals are corrupted; senders detect the collision | Nobody wins; both back off for a random time and retry |
Central arbiter (on-chip buses) | Cannot happen: only the granted master drives | Needs request and grant wires to every master |
Bitwise arbitration wastes no time, since the winner's message continues, but it requires a wire that combines values deterministically and every sender reading back each bit. That limits bit rate and bus length: the bit must settle along the whole wire before anyone reads it.
Protocol | Distributed arbitration |
|---|---|
I2C | Open-drain SDA and SCL. Arbitration runs bit by bit on SDA while SCL is high; a master that drives 1 but reads 0 loses and stops driving. The wired-AND SCL synchronises the masters' clocks. |
CAN | Dominant and recessive bits; arbitration on the identifier field, bit by bit. The lowest identifier wins without being corrupted, so identifiers double as message priorities. |
Classic shared Ethernet | Carrier sense and collision detection on a shared medium, with random back-off. Modern switched, full-duplex Ethernet has no shared medium and needs none of it. |
A board (illustrative) put a master that did not support multi-master arbitration on an I2C bus with another master. It never read SDA back while sending, so when it lost arbitration it kept driving its address bits over the winner's. Both transactions failed intermittently. Every master on a shared I2C bus must implement arbitration, or the bus must have only one master.
0x120: they differ only in the last bit, where 0x120 sends dominant 0 and 0x121 sends recessive 1, so 0x121 loses at that bit.
The wired-AND line takes the dominant value, which is the winner's bit; with ordinary drivers, conflicting values corrupt the line for both.
Distributed arbitration lets the wire decide: a wired-AND line carries 0 if anyone sends 0.
Each sender reads back every bit; sending 1 but reading 0 means it lost and must stop.
The winner's message is intact and priority lives in the message; collision detection instead discards both and backs off.