Timing: Table 11 and the Pull-Up

Read Table 11 as the bus timing, match each checker line to its row, explain why a hold time of zero still needs care, and size a pull-up from the rise time and the sink current.

Lesson
P8.7
Time
40 min
Runs on
Your browser
Check
4 questions

A board worked at 100 kHz and failed at 400 kHz with the same parts. Its 4.7 kΩ pull-ups and 150 pF of bus gave a rise of about 600 ns: legal in Standard-mode, double the Fast-mode limit. Everyone had copied the resistor from a 100 kHz reference design.

TransientThree Pull-Ups into 400 pFThe maximum Sm/Fm bus load.
  • 1 kΩ
  • 2.2 kΩ
  • 4.7 kΩ
00.5 V1.0 V1.5 V2.0 V2.5 V3.0 V3.5 V1 kΩ, 2.2 kΩ, 4.7 kΩ0.5 µs1.0 µs1.5 µs2.0 µs2.5 µs3.0 µsTime0.3 VDD0.7 VDDFm+ 120 nsFm 300 nsSm 1000 ns01 V2 V3 V4 V1 kΩ, 2.2 kΩ, 4.7 kΩ1 µs2 µs3 µsTime0.3 VDD0.7 VDDFm+ 120 nsFm 300 nsSm 1000 ns
Read the plot as a table
Time1 kΩ2.2 kΩ4.7 kΩ
500 ns2.355 V1.43 V770.6 mV
1 µs3.029 V2.241 V1.361 V
1.5 µs3.222 V2.7 V1.814 V
2 µs3.278 V2.96 V2.161 V
2.5 µs3.294 V3.107 V2.427 V
3 µs3.298 V3.191 V2.631 V

Simulated on the Analog engine (ngspice 42), three copies of the library's pull-up netlist at 3.3 V. Each rise time is the 0.7 VDD crossing minus the 0.3 VDD crossing, so read it between the two lines, not from zero.

The Table and the Checker

Every parameter has a definition in Fig. 38 and a value per mode. Eight are edge-to-edge times, and the bench checker measures them on the pins. Four are analog: rise, fall, spike width and bus capacitance. For those the page has SPICE and the pull-up arithmetic.

ParameterSmFmFm+Bench checker ruleMeasured on the bench
tHD;STA (after S or Sr)4.0 µs0.6 µs0.26 µsthd_sta600 ns
tLOW4.7 µs1.3 µs0.5 µstlow1300 ns
tHIGH4.0 µs0.6 µs0.26 µsthigh1210 ns
tSU;STA (before Sr)4.7 µs0.6 µs0.26 µstsu_sta610 ns
tHD;DAT (min)000thd_dat (minimum set by the bench)controller 100 ns, target 0 ns
tSU;DAT250 ns100 ns50 nstsu_dat1200 ns
tSU;STO4.0 µs0.6 µs0.26 µstsu_sto610 ns
tBUF4.7 µs1.3 µs0.5 µstbuf2000 ns
tr (max)1000 ns300 ns120 nsnot measurable on digital pinsSPICE below
tf (max)300 ns300 ns120 nsnot measurable on digital pins–
Cb (max)400 pF400 pF550 pFnot measurable on digital pins–
tVD;DAT, tVD;ACK (max)3.45 µs0.9 µs0.45 µs––

UM10204 Table 11 minima (maxima where marked). The last column is the shortest of each time on the bench's Fast-mode controller, measured from its pins. All Table 11 values are referred to 0.3 VDD and 0.7 VDD.

Fig. 38 On the Bench: START and the First BitMeasured on the Fast-mode bench, 100 ns per column, from the START into the second address 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.

012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849SCLSDActrl_lowtgt_lowS
  1. Cycle 3

    START: SDA falls while SCL is high.

  2. Cycle 9

    SCL falls 600 ns later: that is tHD;STA, and Fast-mode needs 600.

  3. Cycle 10

    The controller changes SDA 100 ns after the fall: its tHD;DAT.

  4. Cycle 22

    SCL rises. The LOW lasted 1300 ns (tLOW) and SDA was ready 1200 ns before (tSU;DAT).

  5. Cycle 35

    SCL falls again after 1210 ns of HIGH (tHIGH).

The other four parameters sit at the STOP and the next START: tSU;STO, tBUF, and before a repeated START tSU;STA. The table above lists all eight as measured.

Three Twins, Three Rows

SwitchChecker linesTable 11 row
ctrl.fast_scl = 1 (100 ns phases)tlow ×36, thigh ×36tLOW, tHIGH
ctrl.short_tbuf = 1I2C CHECK tbuf at 51290tBUF: 800 ns from STOP to START
ctrl.hold_zero_early = 1, minimum 50 nsthd_dat ×18tHD;DAT (note 2)

Measured over two write transactions. The bench controller is clean: ck.broken 0; under the Fm+ checker it is clean too (ck.broken 0), because slower is always legal.

A Hold Time of Zero

tHD;DAT may be 0 for I2C devices (Table 11). That is not a free pass. Note 2: the sender must make sure SCL has dropped below 0.3 VDD before SDA enters the 0.3 to 0.7 VDD band. A device provides its own internal hold, or measures its SCL fall and waits.

A hold time of 1 ns against 100 ns
012345678910111213141516171819SCLSDA (twin)SDA (bench)SCL falls

Measured. A receiver whose SCL input sees the fall a little late would sample the twin's new bit. With the checker's tHD;DAT minimum set to 50 ns: I2C CHECK thd_dat at 3301.

The bench target itself lets go of its ACK at the SCL fall: with the 50 ns minimum the checker flags it too (I2C CHECK thd_dat at 25890). The maximum, 0.9 µs in Fast-mode, binds only devices that do not stretch (note 3).

Sizing the Pull-Up

The rise from 0.3 to 0.7 VDD takes 0.8473 Rp × Cb (§7.1). At 400 pF, 1 kΩ rises in 339 ns and misses Fast-mode by 39 ns.

TransientThe Same Pull-Ups into 100 pFA short board bus.
  • 1 kΩ
  • 2.2 kΩ
  • 4.7 kΩ
00.5 V1.0 V1.5 V2.0 V2.5 V3.0 V3.5 V1 kΩ, 2.2 kΩ, 4.7 kΩ100 ns200 ns300 ns400 ns500 ns600 ns700 ns800 nsTime0.3 VDD0.7 VDDFm+ 120 nsFm 300 ns01 V2 V3 V4 V1 kΩ, 2.2 kΩ, 4.7 kΩ200 ns400 ns600 ns800 nsTime0.3 VDD0.7 VDDFm+ 120 nsFm 300 ns
Read the plot as a table
Time1 kΩ2.2 kΩ4.7 kΩ
100 ns2.086 V1.205 V632.5 mV
200 ns2.853 V1.97 V1.144 V
300 ns3.136 V2.456 V1.557 V
400 ns3.24 V2.764 V1.891 V
500 ns3.278 V2.96 V2.161 V
600 ns3.292 V3.084 V2.379 V
700 ns3.297 V3.163 V2.556 V
800 ns3.299 V3.213 V2.698 V

Simulated on the Analog engine (ngspice 42), three copies of the library's pull-up netlist at 3.3 V. Each rise time is the 0.7 VDD crossing minus the 0.3 VDD crossing, so read it between the two lines, not from zero.

RpCbPredicted 0.8473 RCMeasured riseLegal in
1.00 kΩ400 pF339 ns338.9 nsSm only
2.20 kΩ400 pF746 ns745.6 nsSm only
4.70 kΩ400 pF1593 ns1592.9 nsnone
1.00 kΩ100 pF85 ns84.7 nsFm+, Fm, Sm
2.20 kΩ100 pF186 ns186.4 nsFm, Sm
4.70 kΩ100 pF398 ns398.2 nsSm only

Measured crossings from the two runs above beside the §7.1 prediction.

Rp(max) comes from the rise limit (Eq. 1, Fig. 41); Rp(min) from the 3 mA a device must sink at 0.4 V (Eq. 2, Fig. 42). At Fm and 400 pF the maximum is below the minimum: no resistor works. That is why §5.1 asks for a current source or switched pull-up above 200 pF in Fast-mode.

ModeCbRp(min) = (VDD − 0.4 V) / IOLRp(max) = tr / (0.8473 Cb)Window
Sm100 pF967 Ω (3 mA)11.80 kΩ (1000 ns)967 Ω to 11.80 kΩ
Sm400 pF967 Ω (3 mA)2.95 kΩ (1000 ns)967 Ω to 2.95 kΩ
Fm100 pF967 Ω (3 mA)3.54 kΩ (300 ns)967 Ω to 3.54 kΩ
Fm400 pF967 Ω (3 mA)885 Ω (300 ns)empty: no resistor works
Fm+100 pF145 Ω (20 mA)1.42 kΩ (120 ns)145 Ω to 1.42 kΩ
Fm+550 pF145 Ω (20 mA)258 Ω (120 ns)145 Ω to 258 Ω

VDD 3.3 V. Eq. 1 and Eq. 2 of §7.1, computed. The spec's own example (§7.2.4): 5.5 V and 3 mA give Rp(min) = 1.7 kΩ, which limits a 300 ns rise to about 200 pF.

Try It

Two write transactions back to back. With short_tbuf the controller waits only 100 ns after its STOP before starting the next START sequence; the probe measures the gap on the wire. The checker names the row it breaks.

Remove the switch and the gap becomes 2000 ns: the 1300 ns wait plus the controller's START setup.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: I2C CHECK tbuf at 51290 1 800
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 outputBench defaults, no switches. After two back-to-back write transactions the program prints ck.broken and the STOP-to-START gap on the wire in ns, separated by a space. 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
  assign c2sl = 0; assign c2dl = 0;       // no second controller
  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
  time p_at = 0, gap = 0;   // the time from a STOP to the next START on the wire
  always @(posedge sda) if (scl === 1'b1) p_at = $time;
  always @(negedge sda) if (scl === 1'b1 && p_at) gap = $time - p_at;
  initial begin
    #2000;
    repeat (2) begin
      ctrl.start(); ctrl.address7(7'h50, 0, ack); ctrl.write_byte(8'h3c, ack); ctrl.stop();
    end
    $display("%0d %0d", ck.broken, gap);
    $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 overrides one controller parameter (ns). The harness runs two write transactions under the Fast-mode checker and passes when ck.broken is 0. Which override fails?
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
ConceptWhy are all Table 11 times measured between 0.3 VDD and 0.7 VDD rather than at 50 %?
ConceptVDD 3.3 V, Cb 400 pF, Fast-mode. Rp(max) = 300 ns / (0.8473 × 400 pF) = 885 Ω, and Rp(min) at 3 mA is 967 Ω. What follows?

Remember

  • Table 11 is the bus timing, referred to 0.3 and 0.7 VDD.
  • Eight parameters are edge-to-edge and a digital checker measures them; rise, fall, spikes and Cb need analog.
  • tHD;DAT may be 0, but the sender still owes an internal hold until SCL is below 0.3 VDD.
  • The rise is 0.8473 Rp × Cb; Rp(max) comes from tr, Rp(min) from the 3 mA sink.
  • The Rp window can be empty, which is what current sources and Fm+ drive fix.

Practice