Addressing and Arbitration

Pick between a select line per device and an address on a shared pair, show how two senders settle a wired-AND line without losing a bit, and say how I2C and I3C give out addresses.

Lesson
P5.6
Time
25 min
Runs on
Your browser
Check
5 questions

A board carried two copies of one temperature sensor on the same I2C bus, both strapped to the same fixed address. Every read was acknowledged, and every read returned a value neither sensor held.

Two Sensors Answer 0x48 at Once: 0x5a and 0xc3One column per data bit, most significant first. Each sensor pulls SDA low for its 0 bits; a 1 is released.
VDD · pull-upSDASensor Asensor_a_low = pull lowSensor Bsensor_b_low = pull low

A low lane of 1 pulls the net down. The pull-up raises it only when every device releases.

01234567SDAsensor_a_lowsensor_b_lowbit 7bit 0
  1. Cycle 0

    Bit 7: sensor A sends 0, sensor B sends 1. A 0 from either one pulls the line low.

  2. Cycle 7

    Bit 0: the controller has read 0x42, the AND of the two readings. Both sensors acknowledged the address, so nothing flagged it.

Measured on the I2C bench: sensor_a holds 5a, sensor_b holds c3, controller reads 42. The wire carries the AND of the two bytes.

Select Lines or an Address

Two Ways to Pick One DeviceArrows show what the controller sends to reach one device. Every device sees every clock.
Two ways to pick one deviceArrows show what the controller sends to reach one device. Every device sees every clock. component SPI controller a select line perdevice component Peripheral 0 CS0 low: it drivesMISO component Peripheral 1 CS1 high: MISOreleased component I2C controller one address byte pertransfer component Target 0x50 matches: pulls ACK component Target 0x48 no match: stays silent CS0 lowCS1 highS 50 WS 50 W
  1. A select line

    The controller lowers one select line. Only that peripheral listens and drives MISO; the address costs a pin, and no bus time.

  2. An address byte

    Every target hears the same byte after START. The one whose address matches pulls the ninth bit low; the rest wait for the next START.

Read the diagram as text
  • SPI controller: a select line per device
  • Peripheral 0: CS0 low: it drives MISO
  • Peripheral 1: CS1 high: MISO released
  • I2C controller: one address byte per transfer
  • Target 0x50: matches: pulls ACK
  • Target 0x48: no match: stays silent
  • SPI controller → Peripheral 0: CS0 low
  • SPI controller → Peripheral 1: CS1 high
  • I2C controller → Target 0x50: S 50 W
  • I2C controller → Target 0x48: S 50 W

A select line is decided before the first clock. An address byte takes 9 clocks of every transfer.

Three peripherals, three select lines (spi-shared)

for (d = 0; d < 3; d++) begin
  cs_lo(d); read register 2; cs_hi();
end

Measured: select 0 low: controller reads 11, select 1 low: controller reads 22, select 2 low: controller reads 33.

One target on a shared pair (i2c-shared)

for (a = 'h4f; a <= 'h51; a++)
  start; address7(a); stop;

Measured: I2C S 4f W NA P, I2C S 50 W A P, I2C S 51 W NA P. Only 0x50 acknowledges.

DevicesSPI wiresI2C wiresI3C wires
14 (CLK, MOSI, MISO and 1 select)2 (SCL, SDA)2 (SCL, SDA)
25 (CLK, MOSI, MISO and 2 selects)2 (SCL, SDA)2 (SCL, SDA)
47 (CLK, MOSI, MISO and 4 selects)2 (SCL, SDA)2 (SCL, SDA)
811 (CLK, MOSI, MISO and 8 selects)2 (SCL, SDA)2 (SCL, SDA)
1619 (CLK, MOSI, MISO and 16 selects)2 (SCL, SDA)2 (SCL, SDA)

Wires counted on each bus. The price moves into time: an I2C byte to one target takes 9 address clocks plus 9 data clocks, against 8 SPI clocks (counted from generated figures).

I2C has seven address bits, and two groups of eight addresses are reserved (UM10204 §3.1.12, Table 4). The hook shows the trap: two devices with one address both answer, and the bus returns the AND of their replies.

Arbitration on a Wired-AND Line

On an open-drain line a 0 always wins: anyone may pull low, nobody drives high. Two controllers that start together each read the line back. The first one to send a 1 and read a 0 has lost, and it lets go (UM10204 §3.1.8).

Two Controllers Address 0x50 and 0x58 at OnceEach controller pulls SDA low for a 0 and releases it for a 1, then reads SDA back while SCL is high.
VDD · pull-upSDAController 1ctrl1_low = pull lowController 2ctrl2_low = pull low

A low lane of 1 pulls the net down. The pull-up raises it only when every device releases.

bit 0bit 1bit 2bit 3bit 4bit 5bit 6bit 7 01234567891011121314151617181920212223242526272829303132SCLSDActrl1_lowctrl2_lowcontroller 2 dropsSDA sampled at 3SDA sampled at 7SDA sampled at 11SDA sampled at 15SDA sampled at 19SDA sampled at 23SDA sampled at 27SDA sampled at 31
Read phases and sample points (quarter bit)
  • bit 0: columns 1 through 4.
  • bit 1: columns 5 through 8.
  • bit 2: columns 9 through 12.
  • bit 3: columns 13 through 16.
  • bit 4: columns 17 through 20.
  • bit 5: columns 21 through 24.
  • bit 6: columns 25 through 28.
  • bit 7: columns 29 through 32.
  • SDA sampled at 3, 7, 11, 15, 19, 23, 27, 31.
  1. Cycle 3

    The first bit sent (band bit 0): both send 1. Nobody pulls, the line reads 1, and both stay in.

  2. Cycle 15

    The 4th bit sent (band bit 3): controller 1 sends 0 and pulls the line low; controller 2 sent 1 and reads 0. It drops out here.

  3. Cycle 31

    Controller 1 finishes its byte alone. Its address went out unharmed, so the target it wanted answers.

Generated and decoded back: the decoder finds the drop on the 4th bit sent. The bench measured the same SCL pulse: other drops out at SCL pulse 4.

Both read SDA back

fork
  ctrl:  S 50 W, 10, 3c, P
  other: S 58 W, 10, a5, P
join

Measured: other drops out at SCL pulse 4, ctrl ack 1 lost 0 | other ack 0 lost 1 | target register 10 holds 3c.

other.ctrl_ignores_sda = 1

other.ctrl_ignores_sda = 1;
fork
  ctrl:  S 50 W, 10, 3c, P
  other: S 58 W, 10, a5, P
join

Measured: ctrl ack 0 lost 1 | other ack 1 lost 0 | target register 10 holds 25.

With the rule, the lower address wins and its byte lands whole: 0x3c. Without it, other keeps sending after it lost. The two data bytes merge on the wire, and the honest controller loses partway through the data. The register ends up holding 0x25, a byte neither controller sent.

Addresses Given Out at Run Time

An I2C address is fixed in the part, sometimes with a few bits set by pins. The general call with second byte 04h or 06h tells devices to take in that programmable part (UM10204 §3.1.13). I3C goes further and gives every target its address on the bus.

I3C Dynamic Address AssignmentArrows show time order on the bus. Every round is the same wired-AND contest, played on a 48-bit ID.
I3C dynamic address assignmentArrows show time order on the bus. Every round is the same wired-AND contest, played on a 48-bit ID. component ENTDAA broadcast to everytarget component Sr, 7E read targets without anaddress ACK component IDs on SDA 48-bit ID, BCR, DCR,open drain component Lowest ID wins losers drop at theirfirst 1-vs-0 component Address + parity controller sends 7bits, odd parity component Winner ACKs then the next round repeat until NACK
  1. Enter the mode

    The controller broadcasts ENTDAA, then a repeated START and 7E with the read bit. Every target still without an address acknowledges.

  2. The contest

    The controller only clocks. Each target sends its ID most significant bit first and drops at the first bit where it sent 1 and saw 0.

  3. The address

    The controller sends a 7-bit address and an odd parity bit; the winner acknowledges and keeps it. The next round starts with the rest.

Read the diagram as text
  • ENTDAA: broadcast to every target
  • Sr, 7E read: targets without an address ACK
  • IDs on SDA: 48-bit ID, BCR, DCR, open drain
  • Lowest ID wins: losers drop at their first 1-vs-0
  • Address + parity: controller sends 7 bits, odd parity
  • Winner ACKs: then the next round
  • ENTDAA → Sr, 7E read
  • Sr, 7E read → IDs on SDA
  • IDs on SDA → Lowest ID wins
  • Lowest ID wins → Address + parity
  • Address + parity → Winner ACKs
  • Winner ACKs → Sr, 7E read: repeat until NACK

MIPI I3C Basic §5.1.4.2 (steps 3 to 9) and §5.1.9.3.4 (ENTDAA). A target with a known static address can be given one directly instead (SETDASA, §5.1.9.3.10).

On the I3C bench, target1 carries the higher ID and target2 the lower one. Measured: CTRL DAA PID 02aa01230000 BCR 06 DCR 00 -> 08, CTRL DAA PID 02aa01231000 BCR 06 DCR 00 -> 09, target1 gets 09, target2 gets 08. The IDs first differ at bit 36 of 48, so target2 won the first round and got the first address.

Where This Shows Up

IdeaWhere it returns
A select line per device, MISO sharedP7.4 only the selected peripheral drives
Address byte, R/W bit, reserved addressesP8.4 addressing and the combined format
Wired-AND arbitration and the loser's dutiesP8.6 clock synchronization and arbitration
Open drain where arbitration happensP9.3 open drain and push-pull in I3C
Dynamic addressesP9.4 ENTDAA on the I3C bench
Arbitration inside a chipP1.9 arbiters, fairness and atomic access

A wire arbitrates by value: the lowest wins every time. An on-chip arbiter can choose a policy instead, such as round robin.

Try It

ctrl and other start a write at the same moment: ctrl to 0x50, other to 0x58. The probe prints the SCL pulse on which other loses, counted from START, so pulse 1 is the first address bit.

Add other.ctrl_ignores_sda = 1; before the fork and run it again. Then try addresses that differ only in the last bit, such as 0x50 and 0x51.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: other drops out at SCL pulse 4 ctrl ack 1 lost 0 | other ack 0 lost 1 | target register 10 holds 3c
Read included file: i2c_ctrl.sv
`timescale 1ns/1ns
module i2c_ctrl(inout wire scl,sda,output reg scl_low=0,sda_low=0);
 parameter time TLOW=1300,THIGH=1200,HOLD=100,TBUF=1300,TIMEOUT=20000,THD_STA=600,TSU_STA=600,TSU_STO=600;
 bit ctrl_ignores_scl=0,ctrl_ignores_sda=0,eight_bit_addr=0,stop_between=0,data_change_scl_high=0,clear_eight_clocks=0,no_stop_after_clear=0,hold_zero_early=0,short_tbuf=0,fast_scl=0;
 bit loser_keeps_clocking_past_byte=0,loser_no_target_mode=0,drops_on_identical=0,arb_on_scl_low=0;
 bit lost=0,timed_out=0,active=0,target_mode=0;int clear_pulses=0;time stopped=0;
 assign scl=scl_low?1'b0:1'bz;assign sda=sda_low?1'b0:1'bz;
 task automatic wait_clock();time began;began=$time;
  if(!ctrl_ignores_scl)while(scl!==1'b1&&!timed_out)begin #10;if($time-began>=TIMEOUT)begin timed_out=1;scl_low=0;sda_low=0;end end
 endtask
 task automatic high_time();time began;began=$time-1;while(scl===1'b1&&$time-began<(fast_scl?100:THIGH))#1;endtask
 task automatic start();
  if(!active)begin lost=0;timed_out=0;target_mode=0;if($time-stopped<(short_tbuf?100:TBUF))#((short_tbuf?100:TBUF)-($time-stopped));end
  else begin scl_low=1;#(TLOW);end
  sda_low=0;#(HOLD);scl_low=0;wait_clock();#(TSU_STA);
  if(!timed_out)begin sda_low=1;#(THD_STA);scl_low=1;active=1;end
 endtask
 task automatic stop();
  if(!lost&&!timed_out)begin scl_low=1;#(HOLD);sda_low=1;#(TLOW);scl_low=0;wait_clock();#(TSU_STO);sda_low=0;stopped=$time;#1;end
  active=0;scl_low=0;sda_low=0;
 endtask
 task automatic bit_cycle(input bit value,input bit arbitrate,output bit sampled);
  sampled=1;
  if(!timed_out&&(!lost||loser_keeps_clocking_past_byte))begin
   scl_low=1;#(hold_zero_early?1:HOLD);sda_low=!value;
   if(arb_on_scl_low&&arbitrate&&value&&sda===0)lost=1;
   #(fast_scl?100:TLOW-(hold_zero_early?1:HOLD));scl_low=0;wait_clock();#1;sampled=sda;
   if(arbitrate&&!ctrl_ignores_sda&&((value&&!sampled)||drops_on_identical))begin
    lost=1;sda_low=0;target_mode=!loser_no_target_mode;
   end
   if(data_change_scl_high)begin #100;sda_low=!sda_low;end
   high_time();scl_low=1;
   if(lost&&!loser_keeps_clocking_past_byte)begin scl_low=0;sda_low=0;end
  end
 endtask
 task automatic write_byte(input reg [7:0] value,output bit ack);bit sampled;
  for(int b=7;b>=0;b--)bit_cycle(value[b],1,sampled);
  bit_cycle(1,0,sampled);ack=!sampled;sda_low=0;
 endtask
 task automatic read_byte(input bit ack,output reg [7:0] value);bit sampled;
  for(int b=7;b>=0;b--)begin bit_cycle(1,0,sampled);value[b]=sampled;end
  bit_cycle(!ack,0,sampled);sda_low=0;
 endtask
 task automatic address7(input reg [6:0] addr,input bit rd,output bit ack);
  write_byte(eight_bit_addr?{1'b0,addr}:{addr,rd},ack);
 endtask
 task automatic combined_read(input reg [6:0] addr,input reg [7:0] pointer,output reg [7:0] value);
  bit ack;start();address7(addr,0,ack);write_byte(pointer,ack);if(stop_between)stop();start();address7(addr,1,ack);read_byte(0,value);stop();
 endtask
 task automatic bus_clear(output bit cleared);
  bit sampled;clear_pulses=0;lost=0;timed_out=0;active=1;sda_low=0;scl_low=0;wait_clock();
  while(sda===0&&clear_pulses<(clear_eight_clocks?8:9)&&!timed_out)begin bit_cycle(1,0,sampled);#(HOLD);if(!timed_out)clear_pulses++;end
  cleared=sda===1&&!timed_out;
  if(!no_stop_after_clear)stop();else begin active=0;scl_low=0;sda_low=0;end
 endtask
endmodule
Read included file: i2c_target.sv
`timescale 1ns/1ns
module i2c_target #(parameter [9:0] ADDRESS=10'h050,parameter TEN_BIT=0)
 (inout wire scl,sda,output reg scl_low=0,sda_low=0,output wire high_drive);
 bit ack_not_released=0,ack_wrong_clock=0,stretch_never_released=0,wrong_addr_ack=0,no_stop_detect=0,autoinc_wrap_bug=0,push_pull_target=0,ignores_sr=0;
 // Legal receiver responses, not faults. rx_limit counts post-address bytes,
 // including the pointer byte; -1 is unlimited. A busy address is declined once.
 bit busy_nack=0;int rx_limit=-1,rx_count=0;
 // Optional general-call software reset: UM10204 §§3.1.13–14. This model
 // restores mem[i]=i and ptr=0; ADDRESS is fixed, not a programmable register.
 // Fault switches and bench configuration are not device reset state.
 time stretch_ns=0;bit general_call=1,software_reset=1,general_selected=0,reset_pending=0;int resets=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0,shift=0,tx=0;
 int state=0,count=0,bytes=0;bit active=0,selected=0,reading=0,need_ptr=1,ack_value=0,ack_active=0,low_addr_next=0,addressed10=0,reading_ack=0;
 // state: 0 receive, 1 receiver ACK, 2 transmit, 3 controller ACK, 4 ignore.
 assign high_drive=push_pull_target&&!sda_low;
 assign sda=sda_low?1'b0:high_drive?1'b1:1'bz;assign scl=scl_low?1'b0:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0];
 function automatic [7:0] increment(input reg [7:0] p);increment=autoinc_wrap_bug?((p+1)&8'h0f):p+1'b1;endfunction
 always @(negedge sda)if(scl===1)begin
  if(!(ignores_sr&&active))begin active=1;selected=0;state=0;count=0;bytes=0;rx_count=0;shift=0;reading=0;ack_active=0;sda_low=0;low_addr_next=0;general_selected=0;reset_pending=0;end
 end
 always @(posedge sda)if(scl===1&&active&&!no_stop_detect)begin active=0;selected=0;state=0;count=0;sda_low=0;addressed10=0;end
 always @(posedge scl)if(active)begin
  if(state==0)begin
   shift={shift[6:0],sda};count++;
   if(count==8)begin
    ack_value=0;
    if(bytes==0)begin
      reading=shift[0];
      general_selected=general_call&&shift==0;
      if(general_selected)begin selected=1;low_addr_next=0;end
      else if(TEN_BIT)begin selected=(shift[7:3]==5'b11110&&shift[2:1]==ADDRESS[9:8]&&(!reading||addressed10));low_addr_next=!reading;end
      else selected=(shift[7:1]==ADDRESS[6:0]||(wrong_addr_ack&&shift[7:1]==ADDRESS[6:0]+1'b1));
      if(selected&&busy_nack)begin selected=0;busy_nack=0;end
      ack_value=selected;need_ptr=1;
    end else if(low_addr_next)begin selected=selected&&shift==ADDRESS[7:0];ack_value=selected;addressed10=selected;low_addr_next=0;end
    else if(selected)begin
      ack_value=rx_limit<0||rx_count<rx_limit;
      if(general_selected)begin
       // Unsupported general-call commands are NACKed, never register writes.
       ack_value=ack_value&&bytes==1&&software_reset&&shift==8'h06;
       reset_pending=ack_value;
      end else if(ack_value)begin rx_count++;if(need_ptr)begin ptr=shift;need_ptr=0;end else begin mem[ptr]=shift;ptr=increment(ptr);end end
      if(!ack_value)selected=0;
    end
    bytes++;state=1;ack_active=0;count=0;
   end
  end else if(state==2)begin count++;if(count==8)begin state=3;count=0;reading_ack=0;end end
  else if(state==3&&reading_ack)begin if(sda===0)begin ptr=increment(ptr);tx=mem[ptr];state=2;count=0;end else state=4;end
 end
 always @(negedge scl)if(active)begin
  // Deliberate early-ACK fault: after seven samples, pull for clock 8.
  // The address prefix is already known; its R/W bit has not arrived yet.
  if(state==0&&count==7&&ack_wrong_clock)begin
   if(bytes==0)sda_low=!busy_nack&&((general_call&&shift[6:0]==0)||(TEN_BIT?(shift[6:2]==5'b11110&&shift[1:0]==ADDRESS[9:8]):(shift[6:0]==ADDRESS[6:0]||(wrong_addr_ack&&shift[6:0]==ADDRESS[6:0]+1'b1))));
   else sda_low=selected&&(rx_limit<0||rx_count<rx_limit);
  end else if(state==1)begin
   if(!ack_active)begin
    sda_low=ack_value&&!ack_wrong_clock;ack_active=1;
   end else begin
    sda_low=ack_not_released&&ack_value;ack_active=0;
    if(reset_pending)begin
     // Finish the ninth ACK clock before resetting and releasing the bus.
     for(int i=0;i<256;i++)mem[i]=i[7:0];
     ptr=0;shift=0;tx=0;count=0;bytes=0;rx_count=0;need_ptr=1;
     selected=0;reading=0;reading_ack=0;low_addr_next=0;addressed10=0;
     general_selected=0;reset_pending=0;resets++;sda_low=0;scl_low=0;state=4;
    end else if(!selected)state=4;
    else if(reading)begin state=2;tx=mem[ptr];count=0;sda_low=!tx[7];end
    else state=0;
    // Byte-level stretch after the ACK, as in UM10204 §3.1.9. Prepare
    // outgoing data before releasing SCL; overlap the controller's LOW.
    if(selected&&ack_value&&(stretch_ns>0||stretch_never_released))begin scl_low=1;if(!stretch_never_released)begin #(stretch_ns);scl_low=0;end end
   end
  end else if(state==2)sda_low=!tx[7-count];
  else if(state==3)begin sda_low=0;reading_ack=1;end
  else if(state==4)sda_low=0;
 end
endmodule
Read included file: i2c_checker.sv
`timescale 1ns/1ns
// Logical Fm timing only. Instantaneous digital pins cannot measure RC rise/fall or
// establish which high-SCL SDA edge was intended as data; driver intent is separate.
module i2c_checker #(parameter time TLOW=1300,THIGH=600,TBUF=1300,THD_STA=600,TSU_STA=600,TSU_DAT=100,THD_DAT=0,TSU_STO=600)
 (input wire scl,sda,input wire [2:0] sda_lows,scl_lows,input wire high_drive);
 int broken=0,starts=0,stops=0,pulses=0;bit active=0,seen_rise=0,seen_fall=0,start_pending=0;
 time rise=0,fall=0,changed=0,stop_time=0,start_time=0;
 task automatic bad(input string rule);broken++;$display("I2C CHECK %s at %0t",rule,$time);endtask
 always @(posedge scl)begin
  if(seen_fall&&$time-fall<TLOW)bad("tlow");
  if(active&&$time-changed<TSU_DAT)bad("tsu_dat");rise=$time;seen_rise=1;if(active)pulses++;
 end
 always @(negedge scl)begin
  if(active&&seen_rise&&$time-rise<THIGH)bad("thigh");
  if(start_pending&&$time-start_time<THD_STA)bad("thd_sta");start_pending=0;fall=$time;seen_fall=1;
 end
 always @(sda)begin
  if(scl===1)begin
   if(sda===0)begin
    if(!active&&stops>0&&$time-stop_time<TBUF)bad("tbuf");
    if(active&&$time-rise<TSU_STA)bad("tsu_sta");starts++;active=1;start_pending=1;start_time=$time;
   end else if(sda===1&&active)begin if($time-rise<TSU_STO)bad("tsu_sto");stops++;active=0;stop_time=$time;end
  end else if(seen_fall&&$time-fall<THD_DAT)bad("thd_dat");
  changed=$time;
 end
 always @(high_drive or sda_lows)begin #1;if(high_drive&&|sda_lows)bad("push_pull_contention");end
 // A separate intent-aware bench checks ACK ownership/ninth slot, lost-controller
 // withdrawal, timeouts and accidental START/STOP. Those are not inferable from SDA alone.
endmodule
Read included file: i2c_mon.sv
`timescale 1ns/1ns
module i2c_mon(input wire scl,sda);
 bit active=0,header=1,trace=0;int count=0,frames=0,starts=0,stops=0;reg [7:0] value=0;
 // Assemble the whole S ... Sr ... P transaction before one atomic report.
 // Incomplete/stuck transactions produce no line; counters remain live.
 string text="";
 always @(negedge sda)if(scl===1)begin if(active)text={text," Sr"};else text="I2C S";active=1;header=1;count=0;starts++;end
 always @(posedge sda)if(scl===1&&active)begin if(trace)$display("%s P",text);text="";active=0;count=0;frames++;stops++;end
 always @(posedge scl)if(active)begin
  if(count<8)begin value={value[6:0],sda};count++;end
  else begin
   if(header)text=$sformatf("%s %02h %s",text,value[7:1],value[0]?"R":"W");
   else text=$sformatf("%s %02h",text,value);
   text={text,sda?" NA":" A"};header=0;count=0;
  end
 end
endmodule
// Trusted recovery observer. Driver intent distinguishes the STOP setup edge
// from recovery pulses, including when a stuck target prevents an actual STOP.
module i2c_clear_mon(input wire scl,sda,ctrl_sda_low,enabled,done,timed_out);
 int pulses=0;bit stopped=0;
 always @(posedge enabled)begin pulses=0;stopped=0;end
 always @(posedge scl)if(enabled&&!ctrl_sda_low)pulses++;
 always @(posedge sda)if(enabled&&scl===1)stopped=1;
 always @(posedge done)$display("I2C CLEAR PULSES %0d STOP %0d CLEARED %0d TIMEOUT %0d",pulses,stopped,(sda===1&&!timed_out),timed_out);
endmodule

Check Yourself

Predict the outputSame bench, both controllers reading SDA back: ctrl writes to 0x50 and other to 0x54, starting together. The probe prints the SCL pulse on which other drops out. What does it print?
`timescale 1ns/1ns
module tb;
  tri1 scl, sda;                          // the two lines; tri1 is the pull-up
  wire csl, cdl, tsl, tdl, c2sl, c2dl;    // driver view: 1 = that device pulls SCL or SDA low
  wire high_drive;                        // 1 only while a push-pull target drives SDA high
  bit ack, ack2, cleared; reg [7:0] value;
  i2c_ctrl    ctrl  (scl, sda, csl, cdl);              // controller: task calls drive the pins
  i2c_target  target(scl, sda, tsl, tdl, high_drive);  // register target, mem[i] = i
  i2c_ctrl    #(.TLOW(2600), .THIGH(1500)) other(scl, sda, c2sl, c2dl);   // second controller, slower clock
  i2c_checker #(.TLOW(1300), .THIGH(600), .TBUF(1300), .THD_STA(600), .TSU_STA(600), .TSU_DAT(100), .THD_DAT(0), .TSU_STO(600))
              ck(scl, sda, {c2dl, tdl, cdl}, {c2sl, tsl, csl}, high_drive);   // prints I2C CHECK <rule>
  i2c_mon     mon(scl, sda);                                // prints I2C S 50 W A ... P
  always @(posedge other.lost) $display("other drops out at SCL pulse %0d", ck.pulses);   // bit 1 is the first address bit
  initial begin
    #2000;
    fork
      begin ctrl.start();  ctrl.address7(7'h50, 0, ack);  ctrl.write_byte(8'h10, ack);  ctrl.write_byte(8'h3c, ack);  ctrl.stop();  end
      begin other.start(); other.address7(7'h54, 0, ack2); other.write_byte(8'h10, ack2); other.write_byte(8'ha5, ack2); other.stop(); end
    join
    $finish;
  end
  initial begin #3000000; $fatal(1, "I2C watchdog"); end
endmodule
Read included file: i2c_ctrl.sv
`timescale 1ns/1ns
module i2c_ctrl(inout wire scl,sda,output reg scl_low=0,sda_low=0);
 parameter time TLOW=1300,THIGH=1200,HOLD=100,TBUF=1300,TIMEOUT=20000,THD_STA=600,TSU_STA=600,TSU_STO=600;
 bit ctrl_ignores_scl=0,ctrl_ignores_sda=0,eight_bit_addr=0,stop_between=0,data_change_scl_high=0,clear_eight_clocks=0,no_stop_after_clear=0,hold_zero_early=0,short_tbuf=0,fast_scl=0;
 bit loser_keeps_clocking_past_byte=0,loser_no_target_mode=0,drops_on_identical=0,arb_on_scl_low=0;
 bit lost=0,timed_out=0,active=0,target_mode=0;int clear_pulses=0;time stopped=0;
 assign scl=scl_low?1'b0:1'bz;assign sda=sda_low?1'b0:1'bz;
 task automatic wait_clock();time began;began=$time;
  if(!ctrl_ignores_scl)while(scl!==1'b1&&!timed_out)begin #10;if($time-began>=TIMEOUT)begin timed_out=1;scl_low=0;sda_low=0;end end
 endtask
 task automatic high_time();time began;began=$time-1;while(scl===1'b1&&$time-began<(fast_scl?100:THIGH))#1;endtask
 task automatic start();
  if(!active)begin lost=0;timed_out=0;target_mode=0;if($time-stopped<(short_tbuf?100:TBUF))#((short_tbuf?100:TBUF)-($time-stopped));end
  else begin scl_low=1;#(TLOW);end
  sda_low=0;#(HOLD);scl_low=0;wait_clock();#(TSU_STA);
  if(!timed_out)begin sda_low=1;#(THD_STA);scl_low=1;active=1;end
 endtask
 task automatic stop();
  if(!lost&&!timed_out)begin scl_low=1;#(HOLD);sda_low=1;#(TLOW);scl_low=0;wait_clock();#(TSU_STO);sda_low=0;stopped=$time;#1;end
  active=0;scl_low=0;sda_low=0;
 endtask
 task automatic bit_cycle(input bit value,input bit arbitrate,output bit sampled);
  sampled=1;
  if(!timed_out&&(!lost||loser_keeps_clocking_past_byte))begin
   scl_low=1;#(hold_zero_early?1:HOLD);sda_low=!value;
   if(arb_on_scl_low&&arbitrate&&value&&sda===0)lost=1;
   #(fast_scl?100:TLOW-(hold_zero_early?1:HOLD));scl_low=0;wait_clock();#1;sampled=sda;
   if(arbitrate&&!ctrl_ignores_sda&&((value&&!sampled)||drops_on_identical))begin
    lost=1;sda_low=0;target_mode=!loser_no_target_mode;
   end
   if(data_change_scl_high)begin #100;sda_low=!sda_low;end
   high_time();scl_low=1;
   if(lost&&!loser_keeps_clocking_past_byte)begin scl_low=0;sda_low=0;end
  end
 endtask
 task automatic write_byte(input reg [7:0] value,output bit ack);bit sampled;
  for(int b=7;b>=0;b--)bit_cycle(value[b],1,sampled);
  bit_cycle(1,0,sampled);ack=!sampled;sda_low=0;
 endtask
 task automatic read_byte(input bit ack,output reg [7:0] value);bit sampled;
  for(int b=7;b>=0;b--)begin bit_cycle(1,0,sampled);value[b]=sampled;end
  bit_cycle(!ack,0,sampled);sda_low=0;
 endtask
 task automatic address7(input reg [6:0] addr,input bit rd,output bit ack);
  write_byte(eight_bit_addr?{1'b0,addr}:{addr,rd},ack);
 endtask
 task automatic combined_read(input reg [6:0] addr,input reg [7:0] pointer,output reg [7:0] value);
  bit ack;start();address7(addr,0,ack);write_byte(pointer,ack);if(stop_between)stop();start();address7(addr,1,ack);read_byte(0,value);stop();
 endtask
 task automatic bus_clear(output bit cleared);
  bit sampled;clear_pulses=0;lost=0;timed_out=0;active=1;sda_low=0;scl_low=0;wait_clock();
  while(sda===0&&clear_pulses<(clear_eight_clocks?8:9)&&!timed_out)begin bit_cycle(1,0,sampled);#(HOLD);if(!timed_out)clear_pulses++;end
  cleared=sda===1&&!timed_out;
  if(!no_stop_after_clear)stop();else begin active=0;scl_low=0;sda_low=0;end
 endtask
endmodule
Read included file: i2c_target.sv
`timescale 1ns/1ns
module i2c_target #(parameter [9:0] ADDRESS=10'h050,parameter TEN_BIT=0)
 (inout wire scl,sda,output reg scl_low=0,sda_low=0,output wire high_drive);
 bit ack_not_released=0,ack_wrong_clock=0,stretch_never_released=0,wrong_addr_ack=0,no_stop_detect=0,autoinc_wrap_bug=0,push_pull_target=0,ignores_sr=0;
 // Legal receiver responses, not faults. rx_limit counts post-address bytes,
 // including the pointer byte; -1 is unlimited. A busy address is declined once.
 bit busy_nack=0;int rx_limit=-1,rx_count=0;
 // Optional general-call software reset: UM10204 §§3.1.13–14. This model
 // restores mem[i]=i and ptr=0; ADDRESS is fixed, not a programmable register.
 // Fault switches and bench configuration are not device reset state.
 time stretch_ns=0;bit general_call=1,software_reset=1,general_selected=0,reset_pending=0;int resets=0;
 reg [7:0] mem[0:255];reg [7:0] ptr=0,shift=0,tx=0;
 int state=0,count=0,bytes=0;bit active=0,selected=0,reading=0,need_ptr=1,ack_value=0,ack_active=0,low_addr_next=0,addressed10=0,reading_ack=0;
 // state: 0 receive, 1 receiver ACK, 2 transmit, 3 controller ACK, 4 ignore.
 assign high_drive=push_pull_target&&!sda_low;
 assign sda=sda_low?1'b0:high_drive?1'b1:1'bz;assign scl=scl_low?1'b0:1'bz;
 initial for(int i=0;i<256;i++)mem[i]=i[7:0];
 function automatic [7:0] increment(input reg [7:0] p);increment=autoinc_wrap_bug?((p+1)&8'h0f):p+1'b1;endfunction
 always @(negedge sda)if(scl===1)begin
  if(!(ignores_sr&&active))begin active=1;selected=0;state=0;count=0;bytes=0;rx_count=0;shift=0;reading=0;ack_active=0;sda_low=0;low_addr_next=0;general_selected=0;reset_pending=0;end
 end
 always @(posedge sda)if(scl===1&&active&&!no_stop_detect)begin active=0;selected=0;state=0;count=0;sda_low=0;addressed10=0;end
 always @(posedge scl)if(active)begin
  if(state==0)begin
   shift={shift[6:0],sda};count++;
   if(count==8)begin
    ack_value=0;
    if(bytes==0)begin
      reading=shift[0];
      general_selected=general_call&&shift==0;
      if(general_selected)begin selected=1;low_addr_next=0;end
      else if(TEN_BIT)begin selected=(shift[7:3]==5'b11110&&shift[2:1]==ADDRESS[9:8]&&(!reading||addressed10));low_addr_next=!reading;end
      else selected=(shift[7:1]==ADDRESS[6:0]||(wrong_addr_ack&&shift[7:1]==ADDRESS[6:0]+1'b1));
      if(selected&&busy_nack)begin selected=0;busy_nack=0;end
      ack_value=selected;need_ptr=1;
    end else if(low_addr_next)begin selected=selected&&shift==ADDRESS[7:0];ack_value=selected;addressed10=selected;low_addr_next=0;end
    else if(selected)begin
      ack_value=rx_limit<0||rx_count<rx_limit;
      if(general_selected)begin
       // Unsupported general-call commands are NACKed, never register writes.
       ack_value=ack_value&&bytes==1&&software_reset&&shift==8'h06;
       reset_pending=ack_value;
      end else if(ack_value)begin rx_count++;if(need_ptr)begin ptr=shift;need_ptr=0;end else begin mem[ptr]=shift;ptr=increment(ptr);end end
      if(!ack_value)selected=0;
    end
    bytes++;state=1;ack_active=0;count=0;
   end
  end else if(state==2)begin count++;if(count==8)begin state=3;count=0;reading_ack=0;end end
  else if(state==3&&reading_ack)begin if(sda===0)begin ptr=increment(ptr);tx=mem[ptr];state=2;count=0;end else state=4;end
 end
 always @(negedge scl)if(active)begin
  // Deliberate early-ACK fault: after seven samples, pull for clock 8.
  // The address prefix is already known; its R/W bit has not arrived yet.
  if(state==0&&count==7&&ack_wrong_clock)begin
   if(bytes==0)sda_low=!busy_nack&&((general_call&&shift[6:0]==0)||(TEN_BIT?(shift[6:2]==5'b11110&&shift[1:0]==ADDRESS[9:8]):(shift[6:0]==ADDRESS[6:0]||(wrong_addr_ack&&shift[6:0]==ADDRESS[6:0]+1'b1))));
   else sda_low=selected&&(rx_limit<0||rx_count<rx_limit);
  end else if(state==1)begin
   if(!ack_active)begin
    sda_low=ack_value&&!ack_wrong_clock;ack_active=1;
   end else begin
    sda_low=ack_not_released&&ack_value;ack_active=0;
    if(reset_pending)begin
     // Finish the ninth ACK clock before resetting and releasing the bus.
     for(int i=0;i<256;i++)mem[i]=i[7:0];
     ptr=0;shift=0;tx=0;count=0;bytes=0;rx_count=0;need_ptr=1;
     selected=0;reading=0;reading_ack=0;low_addr_next=0;addressed10=0;
     general_selected=0;reset_pending=0;resets++;sda_low=0;scl_low=0;state=4;
    end else if(!selected)state=4;
    else if(reading)begin state=2;tx=mem[ptr];count=0;sda_low=!tx[7];end
    else state=0;
    // Byte-level stretch after the ACK, as in UM10204 §3.1.9. Prepare
    // outgoing data before releasing SCL; overlap the controller's LOW.
    if(selected&&ack_value&&(stretch_ns>0||stretch_never_released))begin scl_low=1;if(!stretch_never_released)begin #(stretch_ns);scl_low=0;end end
   end
  end else if(state==2)sda_low=!tx[7-count];
  else if(state==3)begin sda_low=0;reading_ack=1;end
  else if(state==4)sda_low=0;
 end
endmodule
Read included file: i2c_checker.sv
`timescale 1ns/1ns
// Logical Fm timing only. Instantaneous digital pins cannot measure RC rise/fall or
// establish which high-SCL SDA edge was intended as data; driver intent is separate.
module i2c_checker #(parameter time TLOW=1300,THIGH=600,TBUF=1300,THD_STA=600,TSU_STA=600,TSU_DAT=100,THD_DAT=0,TSU_STO=600)
 (input wire scl,sda,input wire [2:0] sda_lows,scl_lows,input wire high_drive);
 int broken=0,starts=0,stops=0,pulses=0;bit active=0,seen_rise=0,seen_fall=0,start_pending=0;
 time rise=0,fall=0,changed=0,stop_time=0,start_time=0;
 task automatic bad(input string rule);broken++;$display("I2C CHECK %s at %0t",rule,$time);endtask
 always @(posedge scl)begin
  if(seen_fall&&$time-fall<TLOW)bad("tlow");
  if(active&&$time-changed<TSU_DAT)bad("tsu_dat");rise=$time;seen_rise=1;if(active)pulses++;
 end
 always @(negedge scl)begin
  if(active&&seen_rise&&$time-rise<THIGH)bad("thigh");
  if(start_pending&&$time-start_time<THD_STA)bad("thd_sta");start_pending=0;fall=$time;seen_fall=1;
 end
 always @(sda)begin
  if(scl===1)begin
   if(sda===0)begin
    if(!active&&stops>0&&$time-stop_time<TBUF)bad("tbuf");
    if(active&&$time-rise<TSU_STA)bad("tsu_sta");starts++;active=1;start_pending=1;start_time=$time;
   end else if(sda===1&&active)begin if($time-rise<TSU_STO)bad("tsu_sto");stops++;active=0;stop_time=$time;end
  end else if(seen_fall&&$time-fall<THD_DAT)bad("thd_dat");
  changed=$time;
 end
 always @(high_drive or sda_lows)begin #1;if(high_drive&&|sda_lows)bad("push_pull_contention");end
 // A separate intent-aware bench checks ACK ownership/ninth slot, lost-controller
 // withdrawal, timeouts and accidental START/STOP. Those are not inferable from SDA alone.
endmodule
Read included file: i2c_mon.sv
`timescale 1ns/1ns
module i2c_mon(input wire scl,sda);
 bit active=0,header=1,trace=0;int count=0,frames=0,starts=0,stops=0;reg [7:0] value=0;
 // Assemble the whole S ... Sr ... P transaction before one atomic report.
 // Incomplete/stuck transactions produce no line; counters remain live.
 string text="";
 always @(negedge sda)if(scl===1)begin if(active)text={text," Sr"};else text="I2C S";active=1;header=1;count=0;starts++;end
 always @(posedge sda)if(scl===1&&active)begin if(trace)$display("%s P",text);text="";active=0;count=0;frames++;stops++;end
 always @(posedge scl)if(active)begin
  if(count<8)begin value={value[6:0],sda};count++;end
  else begin
   if(header)text=$sformatf("%s %02h %s",text,value[7:1],value[0]?"R":"W");
   else text=$sformatf("%s %02h",text,value);
   text={text,sda?" NA":" A"};header=0;count=0;
  end
 end
endmodule
// Trusted recovery observer. Driver intent distinguishes the STOP setup edge
// from recovery pulses, including when a stuck target prevents an actual STOP.
module i2c_clear_mon(input wire scl,sda,ctrl_sda_low,enabled,done,timed_out);
 int pulses=0;bit stopped=0;
 always @(posedge enabled)begin pulses=0;stopped=0;end
 always @(posedge scl)if(enabled&&!ctrl_sda_low)pulses++;
 always @(posedge sda)if(enabled&&scl===1)stopped=1;
 always @(posedge done)$display("I2C CLEAR PULSES %0d STOP %0d CLEARED %0d TIMEOUT %0d",pulses,stopped,(sda===1&&!timed_out),timed_out);
endmodule
Spot the bugEach option is the arbitration rule of a sender on a wired-AND line. sent is the bit it put out, seen is the line read back while the clock is high. Which rule fails?
ConceptA board needs eight identical sensors. Which statement about the choice of bus is right?
ConceptTwo I2C controllers start together and send exactly the same address and data. What happens?
Read the waveformTwo controllers start together and send 0xA0 and 0xA4 (generated and decoded back). When does a controller drop out?
I2C-ARBITRATION on the wires
bit 0bit 1bit 2bit 3bit 4bit 5bit 6bit 7 01234567891011121314151617181920212223242526272829303132SCLSDActrl1_lowctrl2_lowcontroller 2 dropsSDA sampled at 3SDA sampled at 7SDA sampled at 11SDA sampled at 15SDA sampled at 19SDA sampled at 23SDA sampled at 27SDA sampled at 31
Read phases and sample points (quarter bit)
  • bit 0: columns 1 through 4.
  • bit 1: columns 5 through 8.
  • bit 2: columns 9 through 12.
  • bit 3: columns 13 through 16.
  • bit 4: columns 17 through 20.
  • bit 5: columns 21 through 24.
  • bit 6: columns 25 through 28.
  • bit 7: columns 29 through 32.
  • SDA sampled at 3, 7, 11, 15, 19, 23, 27, 31.

Generated and decoded back: two controllers send 0xA0 and 0xA4, one column per quarter bit.

Remember

  • A select line per device costs a pin each and no bus time; an address on a shared pair costs an address byte per transfer.
  • Two devices with the same address both answer, and an open-drain bus returns the AND of their replies.
  • On a wired-AND line the first sender to put out a 1 and read a 0 has lost; the lowest value wins with no bit lost.
  • A loser that never reads the line back corrupts the winner's data.
  • I2C addresses are fixed or pin-set; I3C assigns them on the bus by running the same contest on each target's 48-bit ID.