Fast-Mode Plus and Hs-Mode

Say what changes from Standard-mode to Fast-mode Plus and what does not, run a 1 Mbit/s bus under the right checker, and run a bus into and out of Hs-mode, alone and behind a bridge.

Lesson
P8.9
Time
45 min
Runs on
Your browser
Check
5 questions

A team moved a working Fast-mode design to Fm+ and kept its 2.2 kΩ pull-ups. Simulation was clean, because a digital bench cannot see a rise. On the board the 186.4 ns rise ate most of the 260 ns high time, and 1s were sampled as 0s.

TransientFm+ at 100 pF: 2.2 kΩ Against 1 kΩThe library's fm_plus_slow_rise twin (2.2 kΩ) beside 1 kΩ, both into 100 pF at 3.3 V.
  • 1 kΩ
  • 2.2 kΩ
00.5 V1.0 V1.5 V2.0 V2.5 V3.0 V3.5 V1 kΩ, 2.2 kΩ50 ns100 ns150 ns200 ns250 ns300 ns350 ns400 nsTime0.3 VDD0.7 VDDFm+ limit 120 ns01 V2 V3 V4 V1 kΩ, 2.2 kΩ100 ns200 ns300 ns400 nsTime0.3 VDD0.7 VDDFm+ limit 120 ns
  1. Time 120.4 ns1 kΩ = 2.31 V · 2.2 kΩ = 1.391 V

    With 1 kΩ, SDA reaches 0.7 VDD after a 84.7 ns rise, inside Fm+'s 120 ns.

  2. Time 264.9 ns1 kΩ = 3.067 V · 2.2 kΩ = 2.31 V

    With 2.2 kΩ it takes 186.4 ns: legal in Fast-mode, over the Fm+ limit.

Read the plot as a table
Time1 kΩ2.2 kΩ
50 ns1.298 V670.9 mV
100 ns2.086 V1.205 V
120.4 ns2.31 V1.391 V
150 ns2.564 V1.631 V
200 ns2.853 V1.97 V
250 ns3.029 V2.241 V
264.9 ns3.067 V2.31 V
300 ns3.136 V2.456 V
350 ns3.2 V2.628 V
400 ns3.24 V2.764 V

Simulated on the Analog engine (ngspice 42). At 20 mA, Rp(min) at 3.3 V is 145 Ω, so 1 kΩ is well inside the window.

One Protocol, Four Speeds

From Sm to Fm the protocol does not change: the timing shrinks, inputs gain spike filters and Schmitt triggers, outputs gain slope control (§5.1). Fm+ keeps the protocol again and raises the sink current to 20 mA, so a heavier bus or a 1 Mbit/s bus still meets its rise time (§5.2).

SmFmFm+Hs
Bit rate100 kbit/s400 kbit/s1 Mbit/s3.4 Mbit/s (1.7 at 400 pF)
Sink current at 0.4 V3 mA3 mA20 mATable 12
Bus capacitance400 pF400 pF550 pF100 pF (400 pF at 1.7 Mbit/s)
Rise time (max)1000 ns300 ns120 nsSCLH 40 ns, 80 ns after Sr or ACK
Spike filter–50 ns50 ns10 ns
Arbitration during datayesyesyesno: it ends with the controller code
Clock stretchingbyte and bit levelbyte and bit levelbyte and bit levelafter an ACK only
Pull-upresistorresistor; current source above 200 pFresistorcurrent source on SCLH in the active controller
Entered byresetresetresetS + controller code 0000 1XXX + NACK

UM10204 §5 to §6.2, Tables 10 to 13. Sm, Fm and Fm+ change only timing and electrics; each faster device works on a slower bus by following its clock. A Standard-mode device on a Fast-mode bus does not (§5.1).

Fm+ on the Bench

The Fm+ controller preset runs the same register read in 38831 ns instead of 96281 ns, and the Fm+ checker is clean.

ControllerChecker parametersMeasured
Fm+ preset (500 / 500 ns)Fm+ (I2C_FMPLUS)value 3c, ck.broken 0, 38831 ns
Fm bench (1300 / 1200 ns)Fm+ (I2C_FMPLUS)value 3c, ck.broken 0
Fm+ presetFmthd_sta ×2, tlow ×38, thigh ×37, tsu_sta ×1, tsu_sto ×1

Measured. The same wire is legal or illegal depending on which table the bus has agreed to. The Fm+ preset also shortens tHD;STA, tSU;STA and tSU;STO to 260 ns, so the Fm checker flags those rows too.

The checker cannot see the rise. That is the part the SPICE figure above shows, and why the library's fm_plus_slow_rise twin is an analog run, not a digital switch.

Entering Hs-Mode

Hs-mode starts only after S, an 8-bit controller code 0000 1XXX and a NACK, all sent at F/S speed (§5.3.2). No device may acknowledge the code. It is the last point where controllers arbitrate, and it tells every device to switch to Hs timing.

Entering Hs-Mode: The FormatS, controller code 0000 1010, NACK, then Sr and a normal read. The figure shows the format, not the timing: after the NACK the bits run up to about eight times faster.
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.

START0x0a + NACKSTART0xa1 + ACK0x3c + NACKSTOP 0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119SCLSDActrl_lowtgt_lowSNASrANAPSDA 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 46SDA sampled at 50SDA sampled at 54SDA sampled at 58SDA sampled at 62SDA sampled at 66SDA sampled at 70SDA sampled at 74SDA sampled at 78SDA 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 114
Read phases and sample points (quarter bit)
  • START: columns 2 through 3.
  • 0x0a + NACK: columns 4 through 39.
  • START: columns 40 through 43.
  • 0xa1 + ACK: columns 44 through 79.
  • 0x3c + NACK: columns 80 through 115.
  • STOP: columns 116 through 119.
  • SDA sampled at 6, 10, 14, 18, 22, 26, 30, 34, 38, 46, 50, 54, 58, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114.
  1. Cycle 38

    t1: the NACK after the controller code. Arbitration and synchronisation end here; every Hs device switches its filters and timing.

  2. Cycle 42

    tH: once SCLH is high, the winning controller turns on its current-source pull-up. The Sr and everything after it run in Hs-mode.

  3. Cycle 118

    tFS: the STOP. The bus returns to F/S-mode, within tBUF.

Generated and decoded back: the decoder reads the code as an ordinary NACKed byte, which is what it is on the wire. The bench prints I2C S 05 W NA P: nobody acknowledges.

Hs-mode continues across repeated STARTs and ends only at STOP. Each Hs controller has its own code; up to eight can share a bus, with 0000 1000 kept for test and diagnostics.

What Hs Changes

No arbitration or clock synchronisation during the Hs transfer (§5.3.1). SCLH runs HIGH : LOW = 1 : 2. The active controller's current source speeds up SCLH's rise; it is switched off after each Sr and each ACK so a target may stretch there, and only there. At 100 pF, Table 13 allows tLOW 160 ns, tHIGH 60 ns and tSU;DAT 10 ns.

Every Hs device carries a recogniser for S, 0000 1XXX and NACK (§5.3.3). Between t1 and tH the winner adapts its filters, set-up and hold times, slopes and bit rate. Losers adapt their filters and wait for the STOP. Targets adapt filters and timing. The bench models the switch and when the current source is on.

Entering Hs-Mode on the BenchMeasured on the Hs bench, 50 ns per column: the ninth clock of the controller code, the LOW from t1 to tH, the Sr and the first Hs bits.
01234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889SCLHSDAHmcsHs modet1tHSr
  1. Cycle 11

    The ninth clock of the code. Nobody pulls SDAH, so it reads NACK.

  2. Cycle 35

    At t1 SCLH falls after the NACK, and the target's recogniser switches to Hs settings.

  3. Cycle 61

    At tH, 1321 ns later, SCLH is high again and the controller turns on its current source (mcs). This LOW still keeps F/S timing.

  4. Cycle 64

    Sr: the current source goes off for the first Hs rise, so a target could stretch here.

  5. Cycle 76

    Hs bits: 200 ns LOW and 100 ns HIGH each.

Measured. mcs is the enable of the SCLH current source; the bench models when it is on, not the faster rise it gives.

Part of the readSCLH pulsesShortest LOWShortest HIGHMinimum LOW / HIGH
S, code, NACK (F/S-mode)91300 ns1201 nsFm: 1300 / 600 ns (Table 11)
After the NACK (Hs-mode)39200 ns101 nsHs at 100 pF: 160 / 60 ns (Table 13)

Measured on one register read: I2C S 05 W NA Sr 50 W A 3c A Sr 50 R A 3c NA P, checker clean. The recogniser is in Hs-mode from 25809 to 39208 ns, the STOP. The read takes 37209 ns; the same controller without a code takes 95939 ns. Two reads after one code, linked by Sr: 49127 ns, against 75717 ns as two Hs transfers.

Hs Devices on Slower Buses

Hs devices start in Fast-mode after reset (§5.3.3). On a bus with no code they never leave it: they talk at F/S speed with the current source off, through SDAH and SCLH (§5.3.4). Through a closed bridge, everyone shares one F/S bus with open-drain outputs (§5.3.6).

BusTransferMeasured
Hs devices and an F/S target on one bus (Fig. 35)Hs controller, no codevalue 3c, 95939 ns | ckf.broken 0, Hs entries 0, mcs 0
Bridged bus (Fig. 36), bridge closedF/S controller reads the Hs targetF/S controller read 3c from the Hs target
Bridged bus (Fig. 36), bridge closedHs controller reads the F/S target, no codeHs controller read 3c from the F/S target | ckh.broken 0, ckf.broken 0
Hs devices and an F/S target on one bus, no bridgeHs controller sends its code and goes fastF/S checker: tsu_sta ×2, thigh ×38, thd_sta ×2, tlow ×38, tsu_sto ×1

Measured. Without a code nothing switches: no Hs entries, no current source (mcs 0), and the bridge did not move (0 bridge events). The last row is why F/S devices need the bridge.

An Hs Transfer on a Mixed-Speed Bus

The bridge is built into Hs controllers, or into any IC, and is driven only by the bus lines (§5.3.5). On the code it isolates the Hs section. The F/S devices see one slow transfer: S, a byte nobody owns, then SDA held low until a STOP (§5.3.7). Table 9 lists the rate each pair of devices can use.

A Mixed-Speed Bus with a BridgeArrows show the connection through the bridge inside one Hs controller.
A mixed-speed bus with a bridgeArrows show the connection through the bridge inside one Hs controller. component Hs controller current source onSCLH component Hs targets on SDAH, SCLH queue Bridge TR1 toTR3 open during Hs, closedin F/S component F/S devices on SDA, SCL SDAH, SCLHSDA, SCL
  1. F/S-mode

    The bridge is closed: everyone shares one bus at F/S speed.

  2. Hs-mode

    Between t1 and tH the bridge opens. The Hs section runs at 3.4 Mbit/s; the F/S side waits with SDA low and SCL high, ready for the STOP.

Read the diagram as text
  • Hs controller: current source on SCLH
  • Hs targets: on SDAH, SCLH
  • Bridge TR1 to TR3: open during Hs, closed in F/S
  • F/S devices: on SDA, SCL
  • Hs controller → Hs targets: SDAH, SCLH
  • Hs controller → Bridge TR1 to TR3
  • Bridge TR1 to TR3 → F/S devices: SDA, SCL

UM10204 §5.3.5 to §5.3.8, Fig. 36. Table 9: Hs to Hs at 3.4 Mbit/s; any transfer in a system that has a Standard-mode device runs at 100 kbit/s.

The Bridge at t1 and tHMeasured on the mixed-speed bench, 50 ns per column: both sections and the three bridge transistors (1 = on).
012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455SCLHSDAHSCLSDATR1TR2TR3t1tHSr
  1. Cycle 9

    TR1 opens while SCL is low, then TR3 pulls SDA low. A falling SDA under a low SCL is not a START.

  2. Cycle 35

    At tH both clocks are high and TR2 opens. SCL now stays high with SDA low.

  3. Cycle 38

    The Hs section runs on alone. The F/S section waits, ready for a STOP.

Measured. At tFS the order reverses: TR2 closes, TR3 lets SDA rise under a high SCL, which is the STOP the F/S devices see, and TR1 closes once SDA is high.

EventTR1 TR2 TR3Time after t1
t1: the code is NACKed; TR1 opens0 1 00 ns
TR3 pulls SDA low0 1 15 ns
tH: SCLH and SCL are high; TR2 opens0 0 11320 ns
tFS: STOP on SDAH; TR2 closes, TR3 lets SDA rise0 1 013399 ns
t2: SDA is high; TR1 closes1 1 013399 ns

Measured (1 = transistor on). The F/S decoder logs I2C S 05 W NA P: one short transfer that ends at tFS. The Hs decoder logs the whole read. Bench flags: ckh.broken 0, ckf.broken 0, F/S parked while TR2 open 1.

The bridge must act fast enough not to disturb SDAH and SCLH, and meet Fast-mode timing on SDA and SCL (§5.3.8). TR3 must release SDA in time for tBUF. In a hang-up, an F/S device can close the bridge by holding SCL low for at least 1 µs; the bench does not model that recovery.

SwitchMeasuredWhat broke
bridge.skips_tr3 = 1F/S section: starts 1, stops 0SDA was high before TR2 opened, so no STOP can follow: the F/S side stays busy
ctrl.skip_code = 1F/S checker: thd_sta ×2, tlow ×38, thigh ×37, tsu_sta ×1, tsu_sto ×1no code, so the bridge never opened and Hs timing reached the F/S devices
target address 0x05 (0000 101)I2C S 05 W A P, code_acked 1, hs 0, Hs entries 0an address in the reserved 0000 1XX range ACKs the code; the bench controller sends a STOP and stays in F/S-mode
second Hs controller, code 0000 1001ctrl: lost 1, back at 39209 | other: Hs from 25809 to 39209, value 10nothing broke: the lower code wins, and the loser waits for the STOP (§5.3.3)

Measured. The arbitration run logs I2C S 04 R NA Sr 50 W A 10 A Sr 50 R A 10 NA P: the decoder reads code 0000 1001 as address 04 with R.

Try It

The controller takes the Fm+ preset: 500 ns LOW, 500 ns HIGH, 50 ns hold. The checker takes the Fm+ column of Table 11. The protocol, the target and the decoder are unchanged.

Delete the checker parameters to judge the same wire against Fast-mode and read the lines.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: I2C S 50 W A 3c A Sr 50 R A 3c NA P value 3c, ck.broken 0, 38831 ns
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 outputThe Fm bench controller (1300 / 1200 ns) runs a register read under the Fm+ checker. The program prints ck.broken. 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(500), .THIGH(260), .TBUF(500), .THD_STA(260), .TSU_STA(260), .TSU_DAT(50), .THD_DAT(0), .TSU_STO(260))
              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", ck.broken);
    $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
Predict the outputBench defaults. ctrl.start(); ctrl.write_byte(8'h0a, ack); ctrl.stop(); sends an Hs controller code. The program prints ack. 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.start(); ctrl.write_byte(8'h0a, ack); ctrl.stop();
    $display("%0d", ack);
    $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 parameter of the Fast-mode controller with defparam. The harness runs a register read under the Fm+ checker and passes when it is clean. 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
Predict the outputThe mixed-speed bench with its bridge. bridge.skips_tr3 = 1, so TR3 never pulls SDA low. After one Hs register read the program prints how many STOPs the F/S section's decoder saw. What does it print?
`timescale 1ns/1ns
module tb;
  tri1 sclh, sdah;                        // the Hs section; tri1 is its pull-up Rp
  tri1 scl, sda;                          // the F/S section, with its own pull-ups
  wire csl, cdl, mcs, tsl, tdl, high_drive;   // driver view; mcs = the controller's SCLH current source is on
  wire c2sl, c2dl, mcs2, fcsl, fcdl, fsl, fdl, fhd;
  bit ack, entered; reg [7:0] value;
  i2c_hs_ctrl  ctrl(sclh, sdah, csl, cdl, mcs);                 // Hs controller, code 0000 1010
  assign c2sl = 0; assign c2dl = 0;       // no second controller
  i2c_target   target(sclh, sdah, tsl, tdl, high_drive);   // Hs target, mem[i] = i
  i2c_hs_mode  hsm(sclh, sdah);                                 // the target's Hs-mode recogniser
  i2c_checker  #(.TLOW(160), .THIGH(60), .TBUF(1300), .THD_STA(160), .TSU_STA(160), .TSU_DAT(10), .THD_DAT(0), .TSU_STO(160))
               ckh(sclh, sdah, {c2dl, tdl, cdl}, {c2sl, tsl, csl}, high_drive);   // Table 13, Cb = 100 pF
  i2c_mon      monh(sclh, sdah);                                // prints I2C S ... on the Hs section
  i2c_hs_bridge bridge(sdah, sclh, sda, scl);                   // TR1, TR2, TR3 of Fig. 36
  i2c_ctrl     fsc(scl, sda, fcsl, fcdl);                       // an F/S controller on the F/S section
  i2c_target   #(.ADDRESS(10'h060)) fst(scl, sda, fsl, fdl, fhd);   // an F/S target at 0x60
  i2c_checker  #(.TLOW(1300), .THIGH(600), .TBUF(1300), .THD_STA(600), .TSU_STA(600), .TSU_DAT(100), .THD_DAT(0), .TSU_STO(600))
               ckf(scl, sda, {fdl, fcdl, 1'b0}, {fsl, fcsl, 1'b0}, fhd);   // Fast-mode Table 11
  i2c_mon      monf(scl, sda);                                  // prints I2C S ... on the F/S section
  initial begin
    bridge.skips_tr3 = 1;
    #2000;
    ctrl.hs_read(7'h50, 8'h3c, value);
    $display("%0d", monf.stops);
    #2000;
    $finish;
  end
  initial begin #3000000; $fatal(1, "Hs watchdog"); end
endmodule
Read included file: i2c_hs_ctrl.sv
`timescale 1ns/1ns
// Hs-mode controller: open drain, reads both lines back, Fast-mode timing until its controller code has
// been NACKed (§5.3.2), Hs timing until the STOP (§5.3.3). mcs is the SCLH current-source enable: on once
// SCLH is HIGH (tH), off after each Sr and each ninth bit, on again when SCLH is HIGH (§5.3.2, Fig. 39).
module i2c_hs_ctrl(inout wire scl,sda,output reg scl_low=0,sda_low=0,output reg mcs=0);
 parameter time FS_TLOW=1300,FS_THIGH=1200,FS_HOLD=100,TBUF=1300,FS_THD_STA=600,FS_TSU_STA=600,FS_TSU_STO=600;
 parameter time HS_TLOW=200,HS_THIGH=100,HS_HOLD=20,HS_THD_STA=160,HS_TSU_STA=160,HS_TSU_STO=160,TIMEOUT=20000;
 bit skip_code=0;                               // fault: Hs timing without S + code + NACK
 bit hs=0,lost=0,active=0,timed_out=0,code_acked=0,entry_low=0;reg [7:0] code=8'h0a;time stopped=0,t_hs=0,t_fs=0;
 time tlow=FS_TLOW,thigh=FS_THIGH,hold=FS_HOLD,thd_sta=FS_THD_STA,tsu_sta=FS_TSU_STA,tsu_sto=FS_TSU_STO;
 assign scl=scl_low?1'b0:1'bz;assign sda=sda_low?1'b0:1'bz;
 task automatic timing(input bit h);hs=h;
  if(h)begin tlow=HS_TLOW;thigh=HS_THIGH;hold=HS_HOLD;thd_sta=HS_THD_STA;tsu_sta=HS_TSU_STA;tsu_sto=HS_TSU_STO;end
  else begin tlow=FS_TLOW;thigh=FS_THIGH;hold=FS_HOLD;thd_sta=FS_THD_STA;tsu_sta=FS_TSU_STA;tsu_sto=FS_TSU_STO;end
 endtask
 task automatic wait_clock();time began;began=$time;
  while(scl!==1'b1&&!timed_out)begin #1;if($time-began>=TIMEOUT)begin timed_out=1;scl_low=0;sda_low=0;end end
  if(hs&&!timed_out)mcs=1;
 endtask
 task automatic high_time();time began;began=$time-1;while(scl===1'b1&&$time-began<thigh)#1;endtask
 task automatic start();
  if(!active)begin lost=0;timed_out=0;code_acked=0;if($time-stopped<TBUF)#(TBUF-($time-stopped));end
  else begin scl_low=1;#(entry_low?FS_TLOW:tlow);entry_low=0;end   // t1 to tH is still an F/S LOW (§5.3.3)
  sda_low=0;#(hold);scl_low=0;wait_clock();#(tsu_sta);
  if(!timed_out)begin sda_low=1;if(hs)mcs=0;#(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;mcs=0;if(hs)begin t_fs=$time;timing(0);end
 endtask
 task automatic bit_cycle(input bit value,input bit arbitrate,input bit ninth,output bit sampled);
  sampled=1;
  if(!timed_out&&!lost)begin
   scl_low=1;#(hold);sda_low=!value;#(tlow-hold);scl_low=0;wait_clock();#1;sampled=sda;
   if(arbitrate&&value&&!sampled)begin lost=1;sda_low=0;scl_low=0;end
   else begin high_time();scl_low=1;if(ninth&&hs)mcs=0;end
  end
 endtask
 task automatic write_byte(input reg [7:0] value,output bit ack);bit s;
  for(int b=7;b>=0;b--)bit_cycle(value[b],1,0,s);bit_cycle(1,0,1,s);ack=!s;sda_low=0;
 endtask
 task automatic read_byte(input bit ack,output reg [7:0] value);bit s;
  for(int b=7;b>=0;b--)begin bit_cycle(1,0,0,s);value[b]=s;end bit_cycle(!ack,0,1,s);sda_low=0;
 endtask
 task automatic address7(input reg [6:0] addr,input bit rd,output bit ack);write_byte({addr,rd},ack);endtask
 // A controller that lost the code waits for the STOP that ends the Hs transfer (§5.3.3).
 task automatic wait_stop();do @(posedge sda);while(scl!==1'b1);active=0;endtask
 task automatic enter_hs(output bit entered);bit ack;
  entered=0;
  if(skip_code)begin timing(1);t_hs=$time;entered=1;end
  else begin
   start();write_byte(code,ack);
   if(lost)wait_stop();
   else if(ack)begin code_acked=1;stop();end     // no device may acknowledge a controller code (§5.3.2)
   else begin timing(1);entry_low=1;t_hs=$time;entered=1;end   // Hs settings now; the Hs bit rate after tH
  end
 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);start();address7(addr,1,ack);read_byte(0,value);stop();
 endtask
 // S, code, NACK at F/S speed, then Sr and the same register read in Hs-mode, then P (Fig. 33, 34).
 task automatic hs_read(input reg [6:0] addr,input reg [7:0] pointer,output reg [7:0] value);bit entered;
  value=8'hxx;enter_hs(entered);if(entered)combined_read(addr,pointer,value);
 endtask
endmodule
Read included file: i2c_hs_mode.sv
`timescale 1ns/1ns
// The recogniser every Hs-mode device has (§5.3.3): after a START (not an Sr) on a free bus, the byte
// 0000 1XXX followed by a NACK switches the device to Hs settings at t1, the fall of the ninth clock;
// the next STOP switches it back at tFS.
module i2c_hs_mode(input wire scl,sda);
 bit hs=0,busy=0,first=0,code=0;int count=0,entries=0;reg [7:0] shift=0;time t1=0,tfs=0;
 always @(negedge sda)if(scl===1)begin first=!busy;busy=1;count=0;end
 always @(posedge sda)if(scl===1&&busy)begin busy=0;first=0;if(hs)begin hs=0;tfs=$time;end end
 always @(posedge scl)if(busy&&first)begin
  count++;if(count<=8)shift={shift[6:0],sda};else begin code=shift[7:3]==5'b00001&&sda===1;first=0;end
 end
 always @(negedge scl)if(code)begin code=0;hs=1;t1=$time;entries++;end
endmodule
Read included file: i2c_hs_bridge.sv
`timescale 1ns/1ns
// The mixed-speed bridge of §5.3.5 and §5.3.7 (Fig. 36, 37): TR1 and TR2 are transfer gates between the
// Hs section (SDAH, SCLH) and the F/S section (SDA, SCL); TR3 pulls SDA low. The gates are tranif1, so a
// LOW passes both ways while they are on. It reacts only to S + 0000 1XXX + NACK, seen with i2c_hs_mode.
module i2c_hs_bridge(inout wire sdah,sclh,sda,scl);
 reg tr1=1,tr2=1,tr3=0;bit skips_tr3=0;        // fault: never holds SDA low during the Hs transfer
 tranif1 g1(sdah,sda,tr1);tranif1 g2(sclh,scl,tr2);
 assign sda=tr3?1'b0:1'bz;
 i2c_hs_mode code_seen(sclh,sdah);
 always @(posedge code_seen.hs)begin tr1=0;#5;tr3=!skips_tr3;      // after t1: TR1 opens, then TR3 pulls SDA low
  wait(sclh===1&&scl===1);tr2=0;end                                 // tH: TR2 opens before SCLH falls after Sr
 always @(negedge code_seen.hs)begin tr2=1;tr3=0;                   // tFS: P on SDAH; TR2 closes, TR3 lets SDA rise
  wait(sda===1);tr1=1;end                                           // t2: SDA is HIGH, TR1 closes
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
ConceptOne bridged bus holds an Hs controller, a Fast-mode sensor and a Standard-mode EEPROM. At what rate does the controller talk to the sensor?

Remember

  • Sm, Fm and Fm+ share the protocol; each step shrinks Table 11 and a faster device follows a slower clock.
  • Fm+ adds 20 mA drive for 550 pF or 1 Mbit/s; the checker sees its timing, SPICE sees its rise.
  • Hs-mode is entered with S, a controller code 0000 1XXX and a NACK, and ends at STOP.
  • In Hs-mode there is no arbitration, SCLH has a current-source pull-up, and stretching is allowed only after an ACK.
  • Without a code Hs devices stay in F/S-mode; with one, a bridge isolates the F/S section and parks SDA low until the STOP.

Practice