I2C in One Picture

Draw the two I2C lines with their pull-ups and both drivers, say why any device pulling low wins, and read a measured rise time against the Fast-mode limit.

Lesson
P8.1
Time
25 min
Runs on
Your browser
Check
4 questions

A board with two I2C devices worked until firmware enabled the second one. Then SDA sat between 0.3 and 0.7 VDD and nothing decoded. One part drove a 1 "for speed" while the other pulled 0, and every receiver read a different value.

Who Pulls SDA Low?Two devices on one line. Each can pull it low or let go; the pull-up raises it only when both let go.
VDD · pull-upSDAControllerctrl_low = pull lowTargettgt_low = pull low

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

01234SDActrl_lowtgt_low
  1. Cycle 0

    Both devices let go. The pull-up resistor holds SDA high: the idle bus.

  2. Cycle 1

    The controller pulls. One pull is enough: SDA is low.

  3. Cycle 2

    Both pull. Nothing fights; the line is simply low.

  4. Cycle 3

    The controller lets go, but the target still pulls. SDA stays low, and the controller can see it by reading the pin.

  5. Cycle 4

    The target lets go too. Only now does the resistor pull SDA back up.

SDA is high only when every device has let go: a wired-AND. Clock stretching and arbitration both come from this one property.

One Line, Many Hands

No I2C device ever drives a 1. Each one can pull a line low or let go, and a resistor pulls the line high when everyone has let go (§3.1.1). Step through the scene above: the line is the AND of every device's release.

Both lines, SCL and SDA, work this way, and both are high when the bus is free. The bench writes this as tri1 scl, sda; with every device driving 0 or z.

Who Pulls, and When

The bus hides who is pulling. The bench shows it: ctrl_low and tgt_low are 1 while that device pulls SDA. Every I2C figure in this module has these lanes, because a logic analyser cannot show them.

A Write of One Byte, with the Driver ViewSCL and SDA are the wires. ctrl_low and tgt_low show which device pulls SDA low in each quarter bit.
VDD · pull-upSDAControllerctrl_low = pull lowTargettgt_low = pull low

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

START0xa0 + ACK0x3c + ACKSTOP 012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879SCLSDActrl_lowtgt_lowSAAPSDA sampled at 6SDA sampled at 10SDA sampled at 14SDA sampled at 18SDA sampled at 22SDA sampled at 26SDA sampled at 30SDA sampled at 34SDA sampled at 38SDA sampled at 42SDA sampled at 46SDA sampled at 50SDA sampled at 54SDA sampled at 58SDA sampled at 62SDA sampled at 66SDA sampled at 70SDA sampled at 74
Read phases and sample points (quarter bit)
  • START: columns 2 through 3.
  • 0xa0 + ACK: columns 4 through 39.
  • 0x3c + ACK: columns 40 through 75.
  • STOP: columns 76 through 79.
  • SDA sampled at 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74.
  1. Cycle 1

    START: the controller pulls SDA while SCL is high.

  2. Cycle 6

    Address bits: the controller pulls for every 0 and lets go for every 1. The target only listens.

  3. Cycle 38

    The ninth clock: ctrl_low is 0 and tgt_low is 1. The target pulls SDA to acknowledge.

  4. Cycle 74

    The data byte has its own ninth clock, and the target pulls again.

  5. Cycle 78

    STOP: the controller lets SDA rise while SCL is high.

Generated from the transaction and decoded back from SCL and SDA. Dots mark where SDA is sampled, once per SCL high.

The bench sends the same address and prints what its decoder saw: I2C S 50 W A P. In the address bits the controller pulls; in the ninth clock the target pulls. P8.3 is about that handover.

The Slow Edge

Falling is fast: a transistor discharges the bus. Rising is slow: the pull-up resistor charges the bus capacitance. UM10204 measures both between 0.3 VDD and 0.7 VDD, the input thresholds (§3.1.2).

TransientThe Rise Is a Resistor, the Fall Is a Transistor4.7 kΩ pull-up, 100 pF of bus, 3.3 V. The pull-down lets go at 0 and pulls again at 1 µs.
00.5 V1.0 V1.5 V2.0 V2.5 V3.0 V3.5 Vv(sda)0.2 µs0.4 µs0.6 µs0.8 µs1.0 µs1.2 µsTime0.3 VDD = 0.99 V0.7 VDD = 2.31 V168 ns566 nspull-down on01 V2 V3 V4 Vv(sda)0.5 µs1.0 µsTime0.3 VDD = 0.99 V0.7 VDD = 2.31 V168 ns566 nspull-down on
  1. Time 167.6 nsv(sda) = 990 mV

    SDA crosses 0.3 VDD at 168 ns. Below this, every receiver reads 0.

  2. Time 565.9 nsv(sda) = 2.31 V

    It crosses 0.7 VDD at 566 ns: a rise time of 398 ns. Fast-mode allows 300.

  3. Time 1.01 µsv(sda) = 1.453 V

    The pull-down closes. SDA falls through the same band in 11.7 ns.

Read the plot as a table
Timev(sda)
167.6 ns990 mV
200 ns1.144 V
400 ns1.891 V
565.9 ns2.31 V
600 ns2.379 V
800 ns2.698 V
1 µs2.907 V
1.01 µs1.453 V
1.2 µs90.81 mV

Simulated on the Analog engine (ngspice 42) from the library's pull-up netlist. The pull-down is a switch with 133 Ω on-resistance (0.4 V at 3 mA, Table 10).

The charge takes 0.8473 RC between the two thresholds (§7.1): 398 ns for 4.7 kΩ and 100 pF. The run measures 398 ns. Table 11 allows 1000 ns in Standard-mode and 300 ns in Fast-mode, so this pull-up is legal at 100 kHz only. P8.7 sizes one properly.

When a Device Drives a 1

Set target.push_pull_target = 1; and the target drives SDA high whenever it is not pulling. The first time the controller pulls against it, the simulator resolves the fight honestly: sda is x at 2700: cdl = 1, high_drive = 1.

Measured: the push-pull twin under the bench checker, decoder log off.
sda is x at 2700: cdl = 1, high_drive = 1
I2C CHECK push_pull_contention at 2701
I2C CHECK push_pull_contention at 5911
I2C CHECK push_pull_contention at 10931
I2C CHECK push_pull_contention at 25991
ack = 0, ck.broken = 4

The checker counts 4 contentions in one address byte. On a board the line sits at whatever voltage the two transistors agree on, often inside the band where no receiver is sure.

What Is Mandatory, and How Fast

FeatureSingle controllerMulti-controllerTarget
START, STOP, acknowledgeMMM
7-bit target addressMMM
Synchronization, arbitrationn/aMn/a
Clock stretchingO (note 2)O (note 2)O
10-bit address, general call, software resetOOO
START byten/aOn/a
Device IDn/an/aO

UM10204 Table 3, grouped: M mandatory, O optional. Note 2: a controller on a bus where no target stretches need not handle stretching. The lessons follow this order, mandatory first.

ModeRateRise time limitBusLesson
Standard-mode (Sm)100 kbit/s1000 nsopen-drain, wired-ANDP8.2 to P8.8
Fast-mode (Fm)400 kbit/s300 nsopen-drain, wired-ANDthe bench default
Fast-mode Plus (Fm+)1 Mbit/s120 nsopen-drain, 20 mA driveP8.9
High-speed mode (Hs)3.4 Mbit/sTable 13current-source pull-up on SCLHP8.9
Ultra Fast-mode (UFm)5 Mbit/s50 ns (Table 15)push-pull, one directionP8.12

UM10204 §5, Table 11, Table 13 and Table 15. Sm, Fm, Fm+ and Hs share one protocol and are downward compatible. UFm is a different, write-only bus.

The Rest of This ModuleArrows show the order of the lessons; each builds on the one before.
The rest of this moduleArrows show the order of the lessons; each builds on the one before. component Bits and bytes P8.2 START/STOP,P8.3 ACK component Addressing P8.4 7-bit, 10-bit,reserved component Clock stretching P8.5 a target holdsSCL component Arbitration P8.6 two controllers component Timing andpull-ups P8.7 Table 11, Rpwindow component Stuck buses P8.8 nine clocks component Fm+ and Hs P8.9 what changes component Checkers and bughunts P8.10, P8.11
  1. The protocol

    Bits, bytes and addresses: everything Table 3 marks mandatory for every device.

  2. The shared clock

    Stretching and arbitration both use the wired-AND on SCL and SDA.

  3. The analog side

    Rise times, pull-ups, recovery and the faster modes.

  4. Verification

    A monitor, a checker that sees the driver view, and bug hunts.

Read the diagram as text
  • Bits and bytes: P8.2 START/STOP, P8.3 ACK
  • Addressing: P8.4 7-bit, 10-bit, reserved
  • Clock stretching: P8.5 a target holds SCL
  • Arbitration: P8.6 two controllers
  • Timing and pull-ups: P8.7 Table 11, Rp window
  • Stuck buses: P8.8 nine clocks
  • Fm+ and Hs: P8.9 what changes
  • Checkers and bug hunts: P8.10, P8.11
  • Bits and bytes → Addressing
  • Addressing → Clock stretching
  • Clock stretching → Arbitration
  • Arbitration → Timing and pull-ups
  • Timing and pull-ups → Stuck buses
  • Stuck buses → Fm+ and Hs
  • Fm+ and Hs → Checkers and bug hunts

Every lesson runs on the same bench: one controller, a register target at 0x50 and a Fast-mode checker.

Try It

The controller sends START, address 0x50 with the write bit, and STOP. The decoder mon prints one line per transaction; the checker ck counts Fast-mode timing misses in ck.broken.

Add target.push_pull_target = 1; before #2000; and set mon.trace = 0; to read the checker lines and the x on SDA.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: I2C S 50 W A P ack = 1, ck.broken = 0
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 outputTwo devices share SDA through a pull-up. The controller has let go (cdl = 0) while the target still pulls (tdl = 1). What does the program print?
`timescale 1ns/1ns
module tb;
  tri1 sda;                                  // the pull-up
  reg cdl = 0, tdl = 1;                      // controller released, target pulling
  assign sda = cdl ? 1'b0 : 1'bz;
  assign sda = tdl ? 1'b0 : 1'bz;
  initial #1 $display("%b", sda);
endmodule
Spot the bugEach option is one device's SDA output stage, driven by sda_low. The harness checks the idle line and the line while another device pulls low. Which stage fails?
ConceptUM10204 allows a push-pull SCL output on some buses but never a push-pull SDA. Why?
ConceptThe run above rises in about 400 ns and falls in about 12 ns. What sets the rise?

Remember

  • Open-drain outputs and a pull-up make a wired-AND: any device pulling low wins, and the line is high only when all let go.
  • The bus hides who pulls; the driver-view lanes ctrl_low and tgt_low show it.
  • The rise is an RC charge, 0.8473 Rp × Cb between 0.3 and 0.7 VDD, and it sets the speed limit.
  • Table 3 marks START, STOP, ACK and 7-bit addressing mandatory; synchronization and arbitration only for several controllers.
  • Sm, Fm, Fm+ and Hs share one protocol; UFm is a different, push-pull bus.

Practice