Bits, START, STOP and Repeated START

Draw a START, a STOP and a repeated START on both lines, say why SDA may change only while SCL is low, and spot the data edge that every device reads as a STOP.

Lesson
P8.2
Time
30 min
Runs on
Your browser
Check
4 questions

A controller changed SDA on the rising edge of SCL "to give the target the whole high period". The logic analyser, sampling at the rising edge, decoded every byte correctly. The EEPROM saw SDA move while SCL was high, read a START or STOP in the middle of the byte, and stored the data at random addresses.

The data_change_scl_high twin: one SDA edge under a high SCL
0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051SCLSDActrl_lowSDA falls: a STARTSDA rises: a STOP

Measured. The controller moves SDA 100 ns after SCL rises. Every device on the bus reads those edges as START and STOP; the checker prints I2C CHECK tsu_sta at 4711.

One Clock per Bit

The controller makes one SCL pulse per bit. SDA must hold while SCL is high and may change only while SCL is low (§3.1.3). That is what makes START and STOP detectable at all.

One data bit: change while low, hold while high
may changestablemay changestable 0123456789SCLSDASDA sampled at 3SDA sampled at 7
Read phases and sample points (quarter bit)
  • may change: columns 1 through 2.
  • stable: columns 3 through 4.
  • may change: columns 5 through 6.
  • stable: columns 7 through 8.
  • SDA sampled at 3, 7.

UM10204 Fig. 4 redrawn: a 1 then a 0. The receiver samples while SCL is high, and SDA may move only while SCL is low.

START and STOP

START (S) is SDA falling while SCL is high; STOP (P) is SDA rising while SCL is high (§3.1.4). Only the controller makes them. After S the bus is busy. After P it is free once tBUF has passed: 1.3 µs in Fast-mode (Table 11), measured in P8.7.

START and STOP: The Two SDA Edges Under a High SCLOne address byte between a START and a STOP, with the driver view.
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 + ACKSTOP 012345678910111213141516171819202122232425262728293031323334353637383940414243SCLSDActrl_lowtgt_lowSAPSDA 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 38
Read phases and sample points (quarter bit)
  • START: columns 2 through 3.
  • 0xa0 + ACK: columns 4 through 39.
  • STOP: columns 40 through 43.
  • SDA sampled at 6, 10, 14, 18, 22, 26, 30, 34, 38.
  1. Cycle 2

    START: SCL is high and the controller pulls SDA. The bus is now busy.

  2. Cycle 6

    Data: SDA moves only in the low half of each SCL period and holds while SCL is high.

  3. Cycle 42

    STOP: SCL is high and the controller lets SDA rise. The bus is free again after tBUF.

Generated and decoded back. S and P are the only places where SDA moves while SCL is high.

Two transactions print two lines: I2C S 50 W A P twice, then starts 2, stops 2, ck.broken 0.

Repeated START

A repeated START (Sr) has the shape of a START but comes without a STOP before it. The bus stays busy, and every target resets its bus logic and expects an address (§3.1.10 note 4). This is how a register read keeps its pointer write and its read together.

Repeated START: A New Address Without Letting Go of the BusPointer write, Sr, read: the register read of P8.4, drawn with its driver view.
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 + ACK0x50 + ACKSTART0xa1 + ACK0x50 + NACKSTOP 0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155SCLSDActrl_lowtgt_lowSAASrANAPSDA 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 74SDA sampled at 82SDA sampled at 86SDA sampled at 90SDA sampled at 94SDA sampled at 98SDA sampled at 102SDA sampled at 106SDA sampled at 110SDA sampled at 114SDA sampled at 118SDA sampled at 122SDA sampled at 126SDA sampled at 130SDA sampled at 134SDA sampled at 138SDA sampled at 142SDA sampled at 146SDA sampled at 150
Read phases and sample points (quarter bit)
  • START: columns 2 through 3.
  • 0xa0 + ACK: columns 4 through 39.
  • 0x50 + ACK: columns 40 through 75.
  • START: columns 76 through 79.
  • 0xa1 + ACK: columns 80 through 115.
  • 0x50 + NACK: columns 116 through 151.
  • STOP: columns 152 through 155.
  • SDA sampled at 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 130, 134, 138, 142, 146, 150.
  1. Cycle 2

    S, then address 0x50 with the write bit, then the pointer byte 0x50.

  2. Cycle 78

    Sr: SCL goes high with SDA high, then the controller pulls SDA. No STOP came first, so no other controller may start.

  3. Cycle 150

    The read ends with the controller's NACK and a STOP.

Generated and decoded back. Between S and P the bus never goes free; Sr has the shape of a START.

Measured: combined_read with the pointer 0x50 (one transaction, one STOP), then with ctrl.stop_between = 1 and the pointer 0x3c (P then S: two STOPs). Register 0x50 holds 0x50 because the target's memory holds its own index.
I2C S 50 W A 50 A Sr 50 R A 50 NA P
value 50, starts 2, stops 1
I2C S 50 W A 3c A P
I2C S 50 R A 3c NA P
value 3c, starts 2, stops 2

With one controller both forms read the right byte. P8.4 adds a second controller, and the STOP form breaks.

The Accidental START

Every correct device reads an SDA edge under a high SCL as S or P, because the specification says that is what it is. A controller that moves SDA during the high phase sends conditions it never meant.

Run: two transactions of start; address7(0x50, 0); stopmon.startsmon.stopsck.brokenChecker rules
Correct controller220none
ctrl.data_change_scl_high = 1866tsu_sta ×2, tsu_sto ×4

Measured: starts 2, stops 2, ck.broken 0 against starts 8, stops 6, ck.broken 6. Two transactions became 8 STARTs on the wire.

A START immediately followed by a STOP is a "void message" (§3.1.10 note 5): illegal, though many devices tolerate it. The bench decoder prints I2C S P and counts it as a frame.

Try It

combined_read sends S, the address with W, the pointer, Sr, the address with R, reads one byte, NACKs it and sends P. The decoder counts S and Sr together in mon.starts.

Set ctrl.stop_between = 1; before #2000; to put a STOP and a fresh START where the Sr was, then ctrl.data_change_scl_high = 1; to watch the counts go wrong.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: I2C S 50 W A 50 A Sr 50 R A 50 NA P value 50, starts 2, stops 1
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. ctrl.combined_read(7'h50, 8'h3c, value); then the program prints mon.stops and mon.starts (S and Sr together), 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
  initial begin
    #2000;
    ctrl.combined_read(7'h50, 8'h3c, value);
    $display("%0d %0d", mon.stops, mon.starts);
    $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 a START detector beside the bench, counting S and Sr in starts. After a register read it must agree with the decoder. Which one 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
ConceptThe receiver samples SDA at the rising edge of SCL. Why must SDA also stay still for the rest of the high period?
ConceptWhat is the difference between a repeated START and a STOP followed by a START?

Remember

  • SDA may change only while SCL is low and must hold while SCL is high.
  • START is SDA falling under a high SCL, STOP is SDA rising under a high SCL; only the controller makes them.
  • A repeated START looks like a START, keeps the bus busy, and makes every target expect an address.
  • An SDA edge under a high SCL is read as S or P by every correct device, whatever the sender meant.
  • The bus is free only tBUF after a STOP; S followed at once by P is an illegal void message.

Practice