Skip to content
VLSI Mentor

I²C · Module 11

tSU;DAT and tHD;DAT — The I²C Data Window

Two parameters every bit of every byte must satisfy, measured against two different edges. One has a specified minimum of zero and is in practice among the tightest constraints on the bus — this chapter resolves that contradiction.

Chapter 11.2 constrained the clock. This chapter constrains the data relative to the clock, which is question 3 of Chapter 11.1 applied to every bit of every byte on the bus — address bits, payload bits, and the acknowledge alike.

There are two parameters and the single most important thing about them is that they are measured against different edges:

tSU;DAT — SDA stable before the RISING edge of SCL · tHD;DAT — SDA held after the FALLING edge

Getting that backwards is the most common error in a hand-built timing checker, and it produces a block that passes the case it was debugged against and misses the mirror case entirely.

The chapter also resolves something that reads as a contradiction in Table 10: tHD;DAT(min) is zero, and yet a device must provide 300 ns.

1. The Two Rows, and the Footnote That Matters

Four things in that block are worth extracting before going further.

Footnote [2] fixes the reference edge for the hold time, and it is the FALLING edge. This is the clause people skip. A hold time in most digital contexts is measured after the sampling edge; here it is measured after the edge that ends the sampling window. §2 explains why.

tHD;DAT applies to the acknowledge as well as to data. The ninth bit is subject to the same window as the other eight — which matters because the ninth bit is driven by the other device (Chapter 7.2), so the obligation changes hands within a byte.

Footnote [3] is where the real number lives. Table 10's minimum is zero; the footnote requires 300 ns. §3 is entirely about that.

Footnote [5] is the clause that bites a reused design. A Fast-mode device dropped into a Standard-mode system must meet the Standard-mode 250 ns setup, not its native 100 ns — and if it stretches the clock it must have the next bit out 1250 ns before releasing SCL. That is the largest single number in this chapter and it exists because the Standard-mode rise time is a full microsecond.

2. Why the Hold Time Is Referenced to the Falling Edge

The asymmetry looks arbitrary until you ask what each parameter protects.

tSU;DAT protects the receiver. The receiver samples on the rising edge, so the data must be settled before it — that is an ordinary setup time and the reference edge is the obvious one.

tHD;DAT protects the receiver too, but against a different hazard. SCL's falling edge is not instantaneous. It takes tf to traverse the undefined region between VIH and VIL, and during that traversal a receiver's input stage cannot say whether it has already sampled. If the transmitter changes SDA inside that window, some receivers will have latched the old bit and some the new one.

So the hold requirement is not "hold until the receiver samples" — the receiver sampled at the rising edge, long before. It is "hold until the clock has unambiguously fallen", and the reference edge for that is the falling edge itself.

That is what footnote [3] means by "to bridge the undefined region of the falling edge of SCL." The 300 ns is not a margin against a receiver's setup requirement. It is the width of the ambiguity that a finite fall time creates.

3. A Minimum of Zero That Is Not Zero

Table 10 says tHD;DAT(min) is 0 for I²C-bus devices. Footnote [3] says a device must provide at least 300 ns. Both are correct and they are about different things.

who it bindswhat it says
Table 10's 0the busa transmitter may change SDA the instant SCL falls, as far as the protocol is concerned
Footnote [3]'s 300 nsthe devicebut a compliant device must not actually do that

The bus-level minimum is zero because a receiver has already sampled by the time SCL falls — nothing downstream needs the data held. The device-level obligation is 300 ns because the falling edge has finite width and a change inside it makes the sampling instant ambiguous.

So the effective constraint is 300 ns in every speed mode. It does not scale with the speed grade — Standard, Fast and Fast-mode Plus all carry the same 300 ns — which makes it proportionally tightest at Fast-mode Plus, where the whole low phase is only 500 ns. A Fm+ device must hold for 300 ns of its 500 ns low phase, leaving 200 ns for everything else.

4. The Window, Drawn

tHD;DAT after the fall, tSU;DAT before the rise

10 cycles
Ten intervals. SCL is high for the first interval, low for the next seven, and high for the last two. SDA holds its previous value through the first three intervals after SCL falls, changes during the fourth, and is stable for the remaining intervals before SCL rises again. A region row marks the hold interval immediately after the falling edge, a free interval in which SDA may move, and the setup interval immediately before the rising edge.tHD;DAT min 300 nstHD;DAT min 300 nstSU;DAT min 100 nstSU;DAT min 100 nsSCL falls: tHD;DAT startsSCL falls: tHD;DAT startsSDA may move hereSDA may move hereSCL rises: tSU;DAT endsSCL rises: tSU;DAT endssclsdaregion0holdholdholdmovesetupsetupsetupsetupsetupt0t1t2t3t4t5t6t7t8t9
One bit's data window. The hold interval is referenced to the falling edge that ended the previous sampling window; the setup interval to the rising edge that begins the next one. Two intervals, two reference edges, one low phase.

Two things the figure makes concrete.

The two intervals do not overlap and do not touch. Between them is a region where SDA is free to move — and the width of that region is what a transmitter actually has to work in. Chapter 11.4 constrains how much of it the transmitter may consume, and Chapter 11.9 shows the whole low phase adding up.

Both intervals live inside one low phase. That is why Chapter 11.2 §3 could derive tLOW(min) from its contents: the hold, the transmitter's response, the rise, and the setup all fit between one falling edge and the next rising one.

5. Two Measurement Shapes

§2's callout gives the rule; here is what it looks like as hardware.

The setup measurement is a free-running timer, sampled. A counter tracks how long SDA has been stable, resetting on every change. At the rising edge, its value is tSU;DAT. The counter runs whether or not a measurement is wanted, because the alternative is to start it when the edge arrives — at which point the interval is already over.

The hold measurement is an armed timer, stopped. The falling edge arms a counter; the next SDA change stops it. The value at the stop is tHD;DAT.

And the hold measurement needs two things the setup measurement does not:

A repeated bit produces no hold measurement at all. If the transmitter sends the same value again, SDA never moves, so the interval has no end. That is not a violation — a bit that never changes trivially satisfies any hold requirement — and a checker that measured the whole low phase instead would report the low width as a hold time and never see a violation. §7's test 6 asserts nothing is reported.

The arm must be cancelled when the low phase ends. If SDA has not moved by the time SCL rises, the interval is abandoned. Leaving the timer armed means the next SDA change — possibly in a later high phase, possibly a framing event — gets reported as a hold time measured from a falling edge two phases ago. §7's tests 6b and 6c are about that, and §8 records that both were added after mutations survived.

6. The Data-Window Checker in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_checker.sv — two parameters, two reference edges, two measurement shapes
   // THE DATA WINDOW: tSU;DAT and tHD;DAT, the two parameters every bit of every byte must
   // satisfy. They are measured against DIFFERENT edges, and getting that wrong is the most
   // common error in a hand-built timing checker:
   //
   //     tSU;DAT   SDA stable BEFORE the RISING edge of SCL          (a receiver's need)
   //     tHD;DAT   SDA held   AFTER  the FALLING edge of SCL          (a transmitter's duty)
   //
   // The specification is explicit about the second, because it is the counter-intuitive one:
   //
   //   [2] "tHD;DAT is the data hold time that is measured from the falling edge of SCL,
   //        applies to data in transmission and the acknowledge."
   //
   // And then Table 10 says something that looks like a contradiction and is not. For
   // I2C-bus devices tHD;DAT(min) is ZERO -- there is no bus-level hold requirement at all.
   // But footnote [3] imposes a device-level one:
   //
   //   [3] "A device must internally provide a hold time of at least 300 ns for the SDA
   //        signal (with respect to the VIH(min) of the SCL signal) to bridge the undefined
   //        region of the falling edge of SCL."
   //
   // Both are true and they are about different things. The BUS does not require a hold,
   // because a receiver has already sampled by the time SCL falls. The DEVICE requires 300 ns
   // so that its own SDA change does not land inside the window where SCL is neither clearly
   // high nor clearly low -- during which a receiver's own input stage cannot say whether it
   // sampled before or after the change. So 300 ns is not a bus timing margin; it is the
   // width of the ambiguity that SCL's finite fall time creates.
   //
   // This block therefore checks the setup against a bus minimum and the hold against the
   // DEVICE obligation, and reports them separately because they answer to different clauses.
   //
   // PASSIVE: observes the two wires and drives nothing.
   module i2c_data_window_checker #(
       parameter int TICK_W = 16,
       // Fast-mode tSU;DAT = 100 ns = 10 ticks at 100 MHz.
       parameter int T_SU_DAT_MIN = 10,
       // Footnote [3]'s internal hold obligation, 300 ns = 30 ticks. NOT Table 10's
       // tHD;DAT(min), which is zero -- see the header.
       parameter int T_HD_DAT_MIN = 30
   )(
       input  logic clk,
       input  logic rst_n,
       input  logic sda_in,
       input  logic scl_in,

       // ---- measured per bit ----
       output logic              su_valid,        // pulse: a setup time has been measured
       output logic [TICK_W-1:0] t_su_dat,
       output logic              hd_valid,        // pulse: a hold time has been measured
       output logic [TICK_W-1:0] t_hd_dat,

       // ---- verdicts ----
       output logic viol_su,
       output logic viol_hd,

       // ---- totals and worst cases ----
       output logic [TICK_W-1:0] n_su,
       output logic [TICK_W-1:0] n_hd,
       output logic [TICK_W-1:0] n_viol_su,
       output logic [TICK_W-1:0] n_viol_hd,
       output logic [TICK_W-1:0] min_su_seen,
       output logic [TICK_W-1:0] min_hd_seen
   );
       logic sda_q, scl_q;
       logic scl_rise, scl_fall, sda_changed;
       assign scl_rise    = !scl_q &&  scl_in;
       assign scl_fall    =  scl_q && !scl_in;
       assign sda_changed = sda_q != sda_in;

       // SETUP is measured by a timer that runs ALWAYS and is sampled at the rising edge.
       // A timer started by the edge would be too late -- the interval being measured has
       // already finished by the time its end arrives, which is the defining property of a
       // setup time and the reason it cannot be measured the way a hold time can.
       logic [TICK_W-1:0] stable_ticks;
       logic [TICK_W-1:0] stable_now;
       assign stable_now = sda_changed ? '0 : (stable_ticks + 1'b1);

       // HOLD is the opposite: the interval STARTS at the falling edge, so a timer armed by
       // that edge and stopped by the next SDA change measures it directly. The asymmetry
       // between the two measurements is forced by which end of the interval the reference
       // edge sits at, and it is why one counter cannot serve both.
       logic              hd_arm;
       logic [TICK_W-1:0] hd_ticks;
       logic [TICK_W-1:0] hd_now;
       assign hd_now = hd_ticks + 1'b1;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               sda_q        <= 1'b1;
               scl_q        <= 1'b1;
               stable_ticks <= '0;
               hd_arm       <= 1'b0;
               hd_ticks     <= '0;
               su_valid     <= 1'b0;
               t_su_dat     <= '0;
               hd_valid     <= 1'b0;
               t_hd_dat     <= '0;
               viol_su      <= 1'b0;
               viol_hd      <= 1'b0;
               n_su         <= '0;
               n_hd         <= '0;
               n_viol_su    <= '0;
               n_viol_hd    <= '0;
               min_su_seen  <= {TICK_W{1'b1}};
               min_hd_seen  <= {TICK_W{1'b1}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               su_valid <= 1'b0;
               hd_valid <= 1'b0;

               stable_ticks <= stable_now;

               // ---- the setup half: sample the always-running timer at the rising edge ----
               if (scl_rise) begin
                   su_valid <= 1'b1;
                   t_su_dat <= stable_now;
                   viol_su  <= (stable_now < T_SU_DAT_MIN[TICK_W-1:0]);
                   n_su     <= n_su + 1'b1;
                   if (stable_now < T_SU_DAT_MIN[TICK_W-1:0]) n_viol_su <= n_viol_su + 1'b1;
                   if (stable_now < min_su_seen) min_su_seen <= stable_now;
               end

               // ---- the hold half: arm at the falling edge, stop at the next SDA change ----
               if (scl_fall) begin
                   hd_arm   <= 1'b1;
                   hd_ticks <= '0;
               end else if (hd_arm) begin
                   if (sda_changed) begin
                       // SDA has moved. The hold time is however long it waited. Note that a
                       // transmitter which does not change the bit at all holds it for the
                       // whole low phase, which trivially satisfies the obligation -- so only
                       // an ACTUAL change produces a measurement. A checker that measured
                       // every low phase would report the low phase width as a hold time and
                       // never see a violation.
                       hd_arm   <= 1'b0;
                       hd_valid <= 1'b1;
                       t_hd_dat <= hd_now;
                       viol_hd  <= (hd_now < T_HD_DAT_MIN[TICK_W-1:0]);
                       n_hd     <= n_hd + 1'b1;
                       if (hd_now < T_HD_DAT_MIN[TICK_W-1:0]) n_viol_hd <= n_viol_hd + 1'b1;
                       if (hd_now < min_hd_seen) min_hd_seen <= hd_now;
                   end else if (scl_rise) begin
                       // The low phase ended with no SDA change: the bit was repeated, so
                       // there is nothing to measure and nothing to complain about.
                       hd_arm <= 1'b0;
                   end else begin
                       hd_ticks <= hd_now;
                   end
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_checker_tb.sv — eleven scenarios, both boundaries exact
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tSU;DAT = 100 ns = 10 ticks; footnote [3]'s internal
   // hold obligation is 300 ns = 30 ticks.
   module i2c_data_window_checker_tb;
       localparam int TICK_W       = 16;
       localparam int T_SU_DAT_MIN = 10;
       localparam int T_HD_DAT_MIN = 30;

       logic clk = 1'b0;
       always #5 clk = ~clk;

       logic rst_n = 1'b0;
       logic sda_in = 1'b1, scl_in = 1'b1;

       logic su_valid, hd_valid, viol_su, viol_hd;
       logic [TICK_W-1:0] t_su_dat, t_hd_dat, n_su, n_hd, n_viol_su, n_viol_hd;
       logic [TICK_W-1:0] min_su_seen, min_hd_seen;

       int errors = 0;
       int base_su, base_hd;
       logic [TICK_W-1:0] vsu_before, vhd_before, nhd_before;

       i2c_data_window_checker #(.TICK_W(TICK_W), .T_SU_DAT_MIN(T_SU_DAT_MIN),
           .T_HD_DAT_MIN(T_HD_DAT_MIN)) dut (.*);

       initial begin #1000000; $display("FAIL: watchdog expired"); $finish; end

       logic [TICK_W-1:0] su_log [0:31];
       logic vsu_log [0:31];
       int n_sulog;
       always @(posedge clk) if (rst_n && su_valid && n_sulog < 32) begin
           su_log[n_sulog] = t_su_dat; vsu_log[n_sulog] = viol_su; n_sulog++;
       end

       logic [TICK_W-1:0] hd_log [0:31];
       logic vhd_log [0:31];
       int n_hdlog;
       always @(posedge clk) if (rst_n && hd_valid && n_hdlog < 32) begin
           hd_log[n_hdlog] = t_hd_dat; vhd_log[n_hdlog] = viol_hd; n_hdlog++;
       end

       task automatic tick(input int n);
           begin repeat (n) @(negedge clk); end
       endtask

       // One bit, with the two intervals under test made independently settable:
       //   `hold` ticks after SCL falls before SDA changes   -> tHD;DAT
       //   `setup` ticks after SDA changes before SCL rises  -> tSU;DAT
       task automatic bit_cell(input logic v, input int hold, input int setup, input int high);
           begin
               scl_in = 1'b0;  tick(hold);     // hold the PREVIOUS bit after the falling edge
               sda_in = v;     tick(setup);    // present the new bit, then wait the setup
               scl_in = 1'b1;  tick(high);
           end
       endtask

       initial begin
           tick(3);
           if (min_su_seen !== {TICK_W{1'b1}} || min_hd_seen !== {TICK_W{1'b1}}) begin
               $display("FAIL: worst-case trackers did not start at their maximum"); errors++; end
           rst_n = 1'b1; tick(2);
           sda_in = 1'b1; scl_in = 1'b1; tick(10);

           // ---- 1: four LEGAL bits, alternating so that every one produces a real hold
           //      measurement. Generous margins on both sides: nothing may be flagged.
           scl_in = 1'b1; tick(5);
           bit_cell(1'b0, 50, 40, 80);
           bit_cell(1'b1, 50, 40, 80);
           bit_cell(1'b0, 50, 40, 80);
           bit_cell(1'b1, 50, 40, 80);
           scl_in = 1'b0; tick(50);
           if (n_viol_su !== '0 || n_viol_hd !== '0) begin
               $display("FAIL: legal bits flagged -- %0d setup, %0d hold", n_viol_su, n_viol_hd);
               errors++; end
           if (n_sulog < 4) begin
               $display("FAIL: %0d setup measurements, expected 4", n_sulog); errors++; end
           if (n_hdlog < 4) begin
               $display("FAIL: %0d hold measurements, expected 4", n_hdlog); errors++; end
           if (su_log[1] < 16'd35 || su_log[1] > 16'd45) begin
               $display("FAIL: a 40-tick setup measured %0d", su_log[1]); errors++; end
           if (hd_log[1] < 16'd45 || hd_log[1] > 16'd55) begin
               $display("FAIL: a 50-tick hold measured %0d", hd_log[1]); errors++; end

           // ---- 2: SETUP too short, hold fine. Only the setup verdict may fail -- the two
           //      parameters are measured against different edges and are independent.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b0, 50, 4, 80);
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: a 4-tick setup was not flagged (min %0d)", T_SU_DAT_MIN);
                   errors++; end
               if (vhd_log[base_hd] !== 1'b0) begin
                   $display("FAIL: a short SETUP also flagged the HOLD"); errors++; end
           end

           // ---- 3: HOLD too short, setup fine. The mirror of test 2 -- and the one that
           //      proves the hold is measured from the FALLING edge rather than the rising
           //      one. A checker that referenced the wrong edge passes test 2 and fails here.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b1, 5, 60, 80);
               if (vhd_log[base_hd] !== 1'b1) begin
                   $display("FAIL: a 5-tick hold was not flagged (min %0d)", T_HD_DAT_MIN);
                   errors++; end
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: a short HOLD also flagged the SETUP"); errors++; end
               if (hd_log[base_hd] > 16'd7) begin
                   $display("FAIL: a 5-tick hold measured %0d ticks", hd_log[base_hd]); errors++; end
           end

           // ---- 4: BOTH too short at once. Both verdicts must fire; a design that reported
           //      only the first failure it found would hide half the problem.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b0, 3, 3, 80);
               if (!(vhd_log[base_hd] && vsu_log[base_su])) begin
                   $display("FAIL: both intervals short reported hold=%b setup=%b",
                            vhd_log[base_hd], vsu_log[base_su]); errors++; end
           end

           // ---- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal because the
           //      table's values are minima.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b1, T_HD_DAT_MIN, T_SU_DAT_MIN, 80);
               if (vhd_log[base_hd] !== 1'b0) begin
                   $display("FAIL: a hold of EXACTLY the minimum was rejected"); errors++; end
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: a setup of EXACTLY the minimum was rejected"); errors++; end
           end
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b0, T_HD_DAT_MIN - 1, T_SU_DAT_MIN - 1, 80);
               if (vhd_log[base_hd] !== 1'b1) begin
                   $display("FAIL: a hold one tick below the minimum was accepted"); errors++; end
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: a setup one tick below the minimum was accepted"); errors++; end
           end

           // ---- 6: A REPEATED BIT produces NO hold measurement. If the transmitter sends
           //      the same value again, SDA never moves, so there is no hold interval to
           //      measure -- and nothing to complain about, because a bit that never changes
           //      trivially satisfies any hold requirement. A checker that measured the low
           //      phase instead would report the low width as a hold time and could never
           //      see a violation at all.
           begin
               nhd_before = n_hd; vhd_before = n_viol_hd;
               sda_in = 1'b0; scl_in = 1'b0; tick(60);
               scl_in = 1'b1; tick(80);
               scl_in = 1'b0; tick(60);          // same value: SDA does not move
               scl_in = 1'b1; tick(80);
               scl_in = 1'b0; tick(20);
               if (n_hd !== nhd_before) begin
                   $display("FAIL: a repeated bit produced %0d hold measurements",
                            n_hd - nhd_before); errors++; end
               if (n_viol_hd !== vhd_before) begin
                   $display("FAIL: a repeated bit was flagged as a hold violation"); errors++; end
           end

           // ---- 6b: and the consequence of NOT cancelling the arm. Straight after the repeated
           //      bit above, a bit with a genuinely SHORT hold must still be flagged. If the arm
           //      had survived the repeated bit, the timer would have been running since the
           //      EARLIER falling edge, the measured hold would be enormous, and this real
           //      violation would be missed entirely.
           begin
               // Leave SCL HIGH first, so bit_cell supplies a FRESH falling edge. Without that
               // the hold would be timed from the repeated-bit sequence's own earlier fall and
               // would measure 24 ticks rather than 4 -- correct behaviour, wrong test.
               scl_in = 1'b1; tick(40);
               base_hd = n_hdlog;
               bit_cell(1'b1, 4, 60, 80);
               if (vhd_log[base_hd] !== 1'b1) begin
                   $display("FAIL: a 4-tick hold right after a repeated bit was not flagged -- a stale arm inflated the measurement");
                   errors++; end
               if (hd_log[base_hd] > 16'd10) begin
                   $display("FAIL: the hold measured %0d ticks, so it was timed from an earlier falling edge",
                            hd_log[base_hd]); errors++; end
           end

           // ---- 6c: and the sharper consequence. After a repeated bit, an SDA change during a
           //      HIGH phase must produce NO hold measurement -- a hold is an interval that starts
           //      at a falling edge, and this change has no falling edge in front of it that has
           //      not already been closed out. With the arm left standing, that change is timed
           //      from the previous fall and reported as a hold that never happened.
           begin
               nhd_before = n_hd;
               sda_in = 1'b0; scl_in = 1'b0; tick(60);
               scl_in = 1'b1;               tick(40);   // the low phase ends, no SDA change
               sda_in = 1'b1;               tick(40);   // SDA moves while SCL is HIGH
               scl_in = 1'b0;               tick(40);
               if (n_hd !== nhd_before) begin
                   $display("FAIL: an SDA change during a HIGH phase produced %0d hold measurements -- the arm outlived its low phase",
                            n_hd - nhd_before); errors++; end
           end

           // ---- 7: STRETCHING. A very long low phase gives an enormous hold time, which is
           //      legal -- tHD;DAT has a minimum and this block checks a minimum, so more is
           //      always better. This confirms the comparison has the right sense.
           begin
               vhd_before = n_viol_hd;
               bit_cell(1'b1, 2000, 40, 80);
               if (n_viol_hd !== vhd_before) begin
                   $display("FAIL: a 2000-tick hold was flagged"); errors++; end
           end

           // ---- 8: the worst case is TRACKED, not the most recent. A clean run after a
           //      violation must not erase the record of it.
           begin
               repeat (4) bit_cell(1'b0, 90, 90, 80);
               repeat (4) bit_cell(1'b1, 90, 90, 80);
               if (min_su_seen > 16'd10 || min_hd_seen > 16'd30) begin
                   $display("FAIL: worst cases were erased -- min_su %0d, min_hd %0d",
                            min_su_seen, min_hd_seen); errors++; end
           end

           // ---- 9: an IDLE bus measures nothing. No SCL edges, no setup and no hold.
           begin
               vsu_before = n_su;
               sda_in = 1'b1; scl_in = 1'b1; tick(300);
               if (n_su !== vsu_before) begin
                   $display("FAIL: an idle bus produced %0d setup measurements",
                            n_su - vsu_before); errors++; end
           end

           if (errors == 0)
               $display("PASS: setup referenced to the rising edge and hold to the falling one, independent verdicts, both boundaries exact, a repeated bit measures nothing");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_checker.v — the same checker in Verilog-2001
   // THE DATA WINDOW: tSU;DAT and tHD;DAT, the two parameters every bit of every byte must
   // satisfy. They are measured against DIFFERENT edges, and getting that wrong is the most
   // common error in a hand-built timing checker:
   //
   //     tSU;DAT   SDA stable BEFORE the RISING edge of SCL          (a receiver's need)
   //     tHD;DAT   SDA held   AFTER  the FALLING edge of SCL          (a transmitter's duty)
   //
   // The specification is explicit about the second, because it is the counter-intuitive one:
   //
   //   [2] "tHD;DAT is the data hold time that is measured from the falling edge of SCL,
   //        applies to data in transmission and the acknowledge."
   //
   // And then Table 10 says something that looks like a contradiction and is not. For
   // I2C-bus devices tHD;DAT(min) is ZERO -- there is no bus-level hold requirement at all.
   // But footnote [3] imposes a device-level one:
   //
   //   [3] "A device must internally provide a hold time of at least 300 ns for the SDA
   //        signal (with respect to the VIH(min) of the SCL signal) to bridge the undefined
   //        region of the falling edge of SCL."
   //
   // Both are true and they are about different things. The BUS does not require a hold,
   // because a receiver has already sampled by the time SCL falls. The DEVICE requires 300 ns
   // so that its own SDA change does not land inside the window where SCL is neither clearly
   // high nor clearly low -- during which a receiver's own input stage cannot say whether it
   // sampled before or after the change. So 300 ns is not a bus timing margin; it is the
   // width of the ambiguity that SCL's finite fall time creates.
   //
   // This block therefore checks the setup against a bus minimum and the hold against the
   // DEVICE obligation, and reports them separately because they answer to different clauses.
   //
   // PASSIVE: observes the two wires and drives nothing.
   // (Verilog-2001)
   module i2c_data_window_checker #(
       parameter TICK_W = 16,
       // Fast-mode tSU;DAT = 100 ns = 10 ticks at 100 MHz.
       parameter T_SU_DAT_MIN = 10,
       // Footnote [3]'s internal hold obligation, 300 ns = 30 ticks. NOT Table 10's
       // tHD;DAT(min), which is zero -- see the header.
       parameter T_HD_DAT_MIN = 30
   )(
       input  wire  clk,
       input  wire  rst_n,
       input  wire  sda_in,
       input  wire  scl_in,

       // ---- measured per bit ----
       output reg                su_valid,        // pulse: a setup time has been measured
       output reg   [TICK_W-1:0] t_su_dat,
       output reg                hd_valid,        // pulse: a hold time has been measured
       output reg   [TICK_W-1:0] t_hd_dat,

       // ---- verdicts ----
       output reg   viol_su,
       output reg   viol_hd,

       // ---- totals and worst cases ----
       output reg   [TICK_W-1:0] n_su,
       output reg   [TICK_W-1:0] n_hd,
       output reg   [TICK_W-1:0] n_viol_su,
       output reg   [TICK_W-1:0] n_viol_hd,
       output reg   [TICK_W-1:0] min_su_seen,
       output reg   [TICK_W-1:0] min_hd_seen
   );
       reg sda_q, scl_q;
       wire scl_rise, scl_fall, sda_changed;
       assign scl_rise    = !scl_q &&  scl_in;
       assign scl_fall    =  scl_q && !scl_in;
       assign sda_changed = sda_q != sda_in;

       // SETUP is measured by a timer that runs ALWAYS and is sampled at the rising edge.
       // A timer started by the edge would be too late -- the interval being measured has
       // already finished by the time its end arrives, which is the defining property of a
       // setup time and the reason it cannot be measured the way a hold time can.
       reg [TICK_W-1:0] stable_ticks;
       wire [TICK_W-1:0] stable_now;
       assign stable_now = sda_changed ? {TICK_W{1'b0}} : (stable_ticks + 1'b1);

       // HOLD is the opposite: the interval STARTS at the falling edge, so a timer armed by
       // that edge and stopped by the next SDA change measures it directly. The asymmetry
       // between the two measurements is forced by which end of the interval the reference
       // edge sits at, and it is why one counter cannot serve both.
       reg              hd_arm;
       reg [TICK_W-1:0] hd_ticks;
       wire [TICK_W-1:0] hd_now;
       assign hd_now = hd_ticks + 1'b1;

       always @(posedge clk) begin
           if (!rst_n) begin
               sda_q        <= 1'b1;
               scl_q        <= 1'b1;
               stable_ticks <= {TICK_W{1'b0}};
               hd_arm       <= 1'b0;
               hd_ticks     <= {TICK_W{1'b0}};
               su_valid     <= 1'b0;
               t_su_dat     <= {TICK_W{1'b0}};
               hd_valid     <= 1'b0;
               t_hd_dat     <= {TICK_W{1'b0}};
               viol_su      <= 1'b0;
               viol_hd      <= 1'b0;
               n_su         <= {TICK_W{1'b0}};
               n_hd         <= {TICK_W{1'b0}};
               n_viol_su    <= {TICK_W{1'b0}};
               n_viol_hd    <= {TICK_W{1'b0}};
               min_su_seen  <= {TICK_W{1'b1}};
               min_hd_seen  <= {TICK_W{1'b1}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               su_valid <= 1'b0;
               hd_valid <= 1'b0;

               stable_ticks <= stable_now;

               // ---- the setup half: sample the always-running timer at the rising edge ----
               if (scl_rise) begin
                   su_valid <= 1'b1;
                   t_su_dat <= stable_now;
                   viol_su  <= (stable_now < T_SU_DAT_MIN);
                   n_su     <= n_su + 1'b1;
                   if (stable_now < T_SU_DAT_MIN) n_viol_su <= n_viol_su + 1'b1;
                   if (stable_now < min_su_seen) min_su_seen <= stable_now;
               end

               // ---- the hold half: arm at the falling edge, stop at the next SDA change ----
               if (scl_fall) begin
                   hd_arm   <= 1'b1;
                   hd_ticks <= {TICK_W{1'b0}};
               end else if (hd_arm) begin
                   if (sda_changed) begin
                       // SDA has moved. The hold time is however long it waited. Note that a
                       // transmitter which does not change the bit at all holds it for the
                       // whole low phase, which trivially satisfies the obligation -- so only
                       // an ACTUAL change produces a measurement. A checker that measured
                       // every low phase would report the low phase width as a hold time and
                       // never see a violation.
                       hd_arm   <= 1'b0;
                       hd_valid <= 1'b1;
                       t_hd_dat <= hd_now;
                       viol_hd  <= (hd_now < T_HD_DAT_MIN);
                       n_hd     <= n_hd + 1'b1;
                       if (hd_now < T_HD_DAT_MIN) n_viol_hd <= n_viol_hd + 1'b1;
                       if (hd_now < min_hd_seen) min_hd_seen <= hd_now;
                   end else if (scl_rise) begin
                       // The low phase ended with no SDA change: the bit was repeated, so
                       // there is nothing to measure and nothing to complain about.
                       hd_arm <= 1'b0;
                   end else begin
                       hd_ticks <= hd_now;
                   end
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_checker_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tSU;DAT = 100 ns = 10 ticks; footnote [3]'s internal
   // hold obligation is 300 ns = 30 ticks.
   module i2c_data_window_checker_tb;   // Verilog-2001
       localparam TICK_W       = 16;
       localparam T_SU_DAT_MIN = 10;
       localparam T_HD_DAT_MIN = 30;

       reg clk = 1'b0;
       always #5 clk = ~clk;

       reg rst_n = 1'b0;
       reg sda_in = 1'b1, scl_in = 1'b1;

       wire su_valid, hd_valid, viol_su, viol_hd;
       wire [TICK_W-1:0] t_su_dat, t_hd_dat, n_su, n_hd, n_viol_su, n_viol_hd;
       wire [TICK_W-1:0] min_su_seen, min_hd_seen;

       integer errors = 0;
       integer base_su = 0, base_hd = 0;
       reg [TICK_W-1:0] vsu_before, vhd_before, nhd_before;

       i2c_data_window_checker #(.TICK_W(TICK_W), .T_SU_DAT_MIN(T_SU_DAT_MIN),
           .T_HD_DAT_MIN(T_HD_DAT_MIN)) dut (
           .clk(clk), .rst_n(rst_n), .sda_in(sda_in), .scl_in(scl_in), .su_valid(su_valid),
           .t_su_dat(t_su_dat), .hd_valid(hd_valid), .t_hd_dat(t_hd_dat), .viol_su(viol_su),
           .viol_hd(viol_hd), .n_su(n_su), .n_hd(n_hd), .n_viol_su(n_viol_su),
           .n_viol_hd(n_viol_hd), .min_su_seen(min_su_seen), .min_hd_seen(min_hd_seen));

       initial begin #1000000; $display("FAIL: watchdog expired"); $finish; end

       reg [TICK_W-1:0] su_log [0:31];
       reg vsu_log [0:31];
       integer n_sulog = 0;
       always @(posedge clk) if (rst_n && su_valid && n_sulog < 32) begin
           su_log[n_sulog] = t_su_dat; vsu_log[n_sulog] = viol_su; n_sulog = n_sulog + 1;
       end

       reg [TICK_W-1:0] hd_log [0:31];
       reg vhd_log [0:31];
       integer n_hdlog = 0;
       always @(posedge clk) if (rst_n && hd_valid && n_hdlog < 32) begin
           hd_log[n_hdlog] = t_hd_dat; vhd_log[n_hdlog] = viol_hd; n_hdlog = n_hdlog + 1;
       end

       task tick;
           input integer n;
       begin repeat (n) @(negedge clk);     end
       endtask

       // One bit, with the two intervals under test made independently settable:
       //   `hold` ticks after SCL falls before SDA changes   -> tHD;DAT
       //   `setup` ticks after SDA changes before SCL rises  -> tSU;DAT
       task bit_cell;
           input v;
           input integer hold;
           input integer setup;
           input integer high;
       begin
               scl_in = 1'b0;  tick(hold);     // hold the PREVIOUS bit after the falling edge
               sda_in = v;     tick(setup);    // present the new bit, then wait the setup
               scl_in = 1'b1;  tick(high);
               end
       endtask

       initial begin
           tick(3);
           if (min_su_seen !== {TICK_W{1'b1}} || min_hd_seen !== {TICK_W{1'b1}}) begin
               $display("FAIL: worst-case trackers did not start at their maximum"); errors = errors + 1; end
           rst_n = 1'b1; tick(2);
           sda_in = 1'b1; scl_in = 1'b1; tick(10);

           // ---- 1: four LEGAL bits, alternating so that every one produces a real hold
           //      measurement. Generous margins on both sides: nothing may be flagged.
           scl_in = 1'b1; tick(5);
           bit_cell(1'b0, 50, 40, 80);
           bit_cell(1'b1, 50, 40, 80);
           bit_cell(1'b0, 50, 40, 80);
           bit_cell(1'b1, 50, 40, 80);
           scl_in = 1'b0; tick(50);
           if (n_viol_su !== {TICK_W{1'b0}} || n_viol_hd !== {TICK_W{1'b0}}) begin
               $display("FAIL: legal bits flagged -- %0d setup, %0d hold", n_viol_su, n_viol_hd);
               errors = errors + 1; end
           if (n_sulog < 4) begin
               $display("FAIL: %0d setup measurements, expected 4", n_sulog); errors = errors + 1; end
           if (n_hdlog < 4) begin
               $display("FAIL: %0d hold measurements, expected 4", n_hdlog); errors = errors + 1; end
           if (su_log[1] < 16'd35 || su_log[1] > 16'd45) begin
               $display("FAIL: a 40-tick setup measured %0d", su_log[1]); errors = errors + 1; end
           if (hd_log[1] < 16'd45 || hd_log[1] > 16'd55) begin
               $display("FAIL: a 50-tick hold measured %0d", hd_log[1]); errors = errors + 1; end

           // ---- 2: SETUP too short, hold fine. Only the setup verdict may fail -- the two
           //      parameters are measured against different edges and are independent.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b0, 50, 4, 80);
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: a 4-tick setup was not flagged (min %0d)", T_SU_DAT_MIN);
                   errors = errors + 1; end
               if (vhd_log[base_hd] !== 1'b0) begin
                   $display("FAIL: a short SETUP also flagged the HOLD"); errors = errors + 1; end
           end

           // ---- 3: HOLD too short, setup fine. The mirror of test 2 -- and the one that
           //      proves the hold is measured from the FALLING edge rather than the rising
           //      one. A checker that referenced the wrong edge passes test 2 and fails here.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b1, 5, 60, 80);
               if (vhd_log[base_hd] !== 1'b1) begin
                   $display("FAIL: a 5-tick hold was not flagged (min %0d)", T_HD_DAT_MIN);
                   errors = errors + 1; end
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: a short HOLD also flagged the SETUP"); errors = errors + 1; end
               if (hd_log[base_hd] > 16'd7) begin
                   $display("FAIL: a 5-tick hold measured %0d ticks", hd_log[base_hd]); errors = errors + 1; end
           end

           // ---- 4: BOTH too short at once. Both verdicts must fire; a design that reported
           //      only the first failure it found would hide half the problem.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b0, 3, 3, 80);
               if (!(vhd_log[base_hd] && vsu_log[base_su])) begin
                   $display("FAIL: both intervals short reported hold=%b setup=%b",
                            vhd_log[base_hd], vsu_log[base_su]); errors = errors + 1; end
           end

           // ---- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal because the
           //      table's values are minima.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b1, T_HD_DAT_MIN, T_SU_DAT_MIN, 80);
               if (vhd_log[base_hd] !== 1'b0) begin
                   $display("FAIL: a hold of EXACTLY the minimum was rejected"); errors = errors + 1; end
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: a setup of EXACTLY the minimum was rejected"); errors = errors + 1; end
           end
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               bit_cell(1'b0, T_HD_DAT_MIN - 1, T_SU_DAT_MIN - 1, 80);
               if (vhd_log[base_hd] !== 1'b1) begin
                   $display("FAIL: a hold one tick below the minimum was accepted"); errors = errors + 1; end
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: a setup one tick below the minimum was accepted"); errors = errors + 1; end
           end

           // ---- 6: A REPEATED BIT produces NO hold measurement. If the transmitter sends
           //      the same value again, SDA never moves, so there is no hold interval to
           //      measure -- and nothing to complain about, because a bit that never changes
           //      trivially satisfies any hold requirement. A checker that measured the low
           //      phase instead would report the low width as a hold time and could never
           //      see a violation at all.
           begin
               nhd_before = n_hd; vhd_before = n_viol_hd;
               sda_in = 1'b0; scl_in = 1'b0; tick(60);
               scl_in = 1'b1; tick(80);
               scl_in = 1'b0; tick(60);          // same value: SDA does not move
               scl_in = 1'b1; tick(80);
               scl_in = 1'b0; tick(20);
               if (n_hd !== nhd_before) begin
                   $display("FAIL: a repeated bit produced %0d hold measurements",
                            n_hd - nhd_before); errors = errors + 1; end
               if (n_viol_hd !== vhd_before) begin
                   $display("FAIL: a repeated bit was flagged as a hold violation"); errors = errors + 1; end
           end

           // ---- 6b: and the consequence of NOT cancelling the arm. Straight after the repeated
           //      bit above, a bit with a genuinely SHORT hold must still be flagged. If the arm
           //      had survived the repeated bit, the timer would have been running since the
           //      EARLIER falling edge, the measured hold would be enormous, and this real
           //      violation would be missed entirely.
           begin
               // Leave SCL HIGH first, so bit_cell supplies a FRESH falling edge. Without that
               // the hold would be timed from the repeated-bit sequence's own earlier fall and
               // would measure 24 ticks rather than 4 -- correct behaviour, wrong test.
               scl_in = 1'b1; tick(40);
               base_hd = n_hdlog;
               bit_cell(1'b1, 4, 60, 80);
               if (vhd_log[base_hd] !== 1'b1) begin
                   $display("FAIL: a 4-tick hold right after a repeated bit was not flagged -- a stale arm inflated the measurement");
                   errors = errors + 1; end
               if (hd_log[base_hd] > 16'd10) begin
                   $display("FAIL: the hold measured %0d ticks, so it was timed from an earlier falling edge",
                            hd_log[base_hd]); errors = errors + 1; end
           end

           // ---- 6c: and the sharper consequence. After a repeated bit, an SDA change during a
           //      HIGH phase must produce NO hold measurement -- a hold is an interval that starts
           //      at a falling edge, and this change has no falling edge in front of it that has
           //      not already been closed out. With the arm left standing, that change is timed
           //      from the previous fall and reported as a hold that never happened.
           begin
               nhd_before = n_hd;
               sda_in = 1'b0; scl_in = 1'b0; tick(60);
               scl_in = 1'b1;               tick(40);   // the low phase ends, no SDA change
               sda_in = 1'b1;               tick(40);   // SDA moves while SCL is HIGH
               scl_in = 1'b0;               tick(40);
               if (n_hd !== nhd_before) begin
                   $display("FAIL: an SDA change during a HIGH phase produced %0d hold measurements -- the arm outlived its low phase",
                            n_hd - nhd_before); errors = errors + 1; end
           end

           // ---- 7: STRETCHING. A very long low phase gives an enormous hold time, which is
           //      legal -- tHD;DAT has a minimum and this block checks a minimum, so more is
           //      always better. This confirms the comparison has the right sense.
           begin
               vhd_before = n_viol_hd;
               bit_cell(1'b1, 2000, 40, 80);
               if (n_viol_hd !== vhd_before) begin
                   $display("FAIL: a 2000-tick hold was flagged"); errors = errors + 1; end
           end

           // ---- 8: the worst case is TRACKED, not the most recent. A clean run after a
           //      violation must not erase the record of it.
           begin
               repeat (4) bit_cell(1'b0, 90, 90, 80);
               repeat (4) bit_cell(1'b1, 90, 90, 80);
               if (min_su_seen > 16'd10 || min_hd_seen > 16'd30) begin
                   $display("FAIL: worst cases were erased -- min_su %0d, min_hd %0d",
                            min_su_seen, min_hd_seen); errors = errors + 1; end
           end

           // ---- 9: an IDLE bus measures nothing. No SCL edges, no setup and no hold.
           begin
               vsu_before = n_su;
               sda_in = 1'b1; scl_in = 1'b1; tick(300);
               if (n_su !== vsu_before) begin
                   $display("FAIL: an idle bus produced %0d setup measurements",
                            n_su - vsu_before); errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: setup referenced to the rising edge and hold to the falling one, independent verdicts, both boundaries exact, a repeated bit measures nothing");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_checker.vhd — the same checker in VHDL
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- THE DATA WINDOW: tSU;DAT and tHD;DAT, the two parameters every bit of every byte must
   -- satisfy. They are measured against DIFFERENT edges, and getting that wrong is the most
   -- common error in a hand-built timing checker:
   --
   --     tSU;DAT   SDA stable BEFORE the RISING edge of SCL          (a receiver's need)
   --     tHD;DAT   SDA held   AFTER  the FALLING edge of SCL         (a transmitter's duty)
   --
   --   [2] "tHD;DAT is the data hold time that is measured from the falling edge of SCL, applies
   --        to data in transmission and the acknowledge."
   --
   -- And then Table 10 says something that looks like a contradiction and is not. For I2C-bus
   -- devices tHD;DAT(min) is ZERO -- there is no bus-level hold requirement at all. But footnote
   -- [3] imposes a device-level one:
   --
   --   [3] "A device must internally provide a hold time of at least 300 ns for the SDA signal
   --        (with respect to the VIH(min) of the SCL signal) to bridge the undefined region of
   --        the falling edge of SCL."
   --
   -- Both are true and they are about different things. The BUS does not require a hold, because
   -- a receiver has already sampled by the time SCL falls. The DEVICE requires 300 ns so that
   -- its own SDA change does not land inside the window where SCL is neither clearly high nor
   -- clearly low. So 300 ns is not a bus timing margin; it is the width of the ambiguity that
   -- SCL's finite fall time creates.
   --
   -- PASSIVE: observes the two wires and drives nothing.
   entity i2c_data_window_checker is
       generic (
           TICK_W : positive := 16;
           -- Fast-mode tSU;DAT = 100 ns = 10 ticks at 100 MHz.
           T_SU_DAT_MIN : natural := 10;
           -- Footnote [3]'s internal hold obligation, 300 ns = 30 ticks. NOT Table 10's
           -- tHD;DAT(min), which is zero -- see the header.
           T_HD_DAT_MIN : natural := 30
       );
       port (
           clk    : in std_logic;
           rst_n  : in std_logic;
           sda_in : in std_logic;
           scl_in : in std_logic;

           su_valid : out std_logic;
           t_su_dat : out unsigned(TICK_W - 1 downto 0);
           hd_valid : out std_logic;
           t_hd_dat : out unsigned(TICK_W - 1 downto 0);

           viol_su : out std_logic;
           viol_hd : out std_logic;

           n_su      : out unsigned(TICK_W - 1 downto 0);
           n_hd      : out unsigned(TICK_W - 1 downto 0);
           n_viol_su : out unsigned(TICK_W - 1 downto 0);
           n_viol_hd : out unsigned(TICK_W - 1 downto 0);
           min_su_seen : out unsigned(TICK_W - 1 downto 0);
           min_hd_seen : out unsigned(TICK_W - 1 downto 0)
       );
   end entity;

   architecture rtl of i2c_data_window_checker is
       signal sda_q, scl_q : std_logic := '1';
       signal scl_rise, scl_fall, sda_changed : std_logic;

       -- SETUP is measured by a timer that runs ALWAYS and is sampled at the rising edge. A timer
       -- started by the edge would be too late -- the interval being measured has already
       -- finished by the time its end arrives, which is the defining property of a setup time and
       -- the reason it cannot be measured the way a hold time can.
       signal stable_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal stable_now   : unsigned(TICK_W - 1 downto 0);

       -- HOLD is the opposite: the interval STARTS at the falling edge, so a timer armed by that
       -- edge and stopped by the next SDA change measures it directly. The asymmetry is forced by
       -- which end of the interval the reference edge sits at, and it is why one counter cannot
       -- serve both.
       signal hd_arm   : std_logic := '0';
       signal hd_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal hd_now   : unsigned(TICK_W - 1 downto 0);
   begin
       scl_rise    <= (not scl_q) and scl_in;
       scl_fall    <= scl_q and (not scl_in);
       sda_changed <= '1' when sda_q /= sda_in else '0';

       stable_now <= (others => '0') when sda_changed = '1' else stable_ticks + 1;
       hd_now     <= hd_ticks + 1;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   sda_q        <= '1';
                   scl_q        <= '1';
                   stable_ticks <= (others => '0');
                   hd_arm       <= '0';
                   hd_ticks     <= (others => '0');
                   su_valid     <= '0';
                   t_su_dat     <= (others => '0');
                   hd_valid     <= '0';
                   t_hd_dat     <= (others => '0');
                   viol_su      <= '0';
                   viol_hd      <= '0';
                   n_su         <= (others => '0');
                   n_hd         <= (others => '0');
                   n_viol_su    <= (others => '0');
                   n_viol_hd    <= (others => '0');
                   min_su_seen  <= (others => '1');
                   min_hd_seen  <= (others => '1');
               else
                   sda_q <= sda_in;
                   scl_q <= scl_in;

                   su_valid <= '0';
                   hd_valid <= '0';

                   stable_ticks <= stable_now;

                   -- the setup half: sample the always-running timer at the rising edge
                   if scl_rise = '1' then
                       su_valid <= '1';
                       t_su_dat <= stable_now;
                       if stable_now < to_unsigned(T_SU_DAT_MIN, TICK_W) then
                           viol_su   <= '1';
                           n_viol_su <= n_viol_su + 1;
                       else
                           viol_su <= '0';
                       end if;
                       n_su <= n_su + 1;
                       if stable_now < min_su_seen then min_su_seen <= stable_now; end if;
                   end if;

                   -- the hold half: arm at the falling edge, stop at the next SDA change
                   if scl_fall = '1' then
                       hd_arm   <= '1';
                       hd_ticks <= (others => '0');
                   elsif hd_arm = '1' then
                       if sda_changed = '1' then
                           -- SDA has moved. The hold time is however long it waited. A
                           -- transmitter that does not change the bit at all holds it for the
                           -- whole low phase, which trivially satisfies the obligation -- so only
                           -- an ACTUAL change produces a measurement. A checker that measured
                           -- every low phase would report the low width as a hold time and never
                           -- see a violation.
                           hd_arm   <= '0';
                           hd_valid <= '1';
                           t_hd_dat <= hd_now;
                           if hd_now < to_unsigned(T_HD_DAT_MIN, TICK_W) then
                               viol_hd   <= '1';
                               n_viol_hd <= n_viol_hd + 1;
                           else
                               viol_hd <= '0';
                           end if;
                           n_hd <= n_hd + 1;
                           if hd_now < min_hd_seen then min_hd_seen <= hd_now; end if;
                       elsif scl_rise = '1' then
                           -- The low phase ended with no SDA change: the bit was repeated, so
                           -- there is nothing to measure and nothing to complain about.
                           hd_arm <= '0';
                       else
                           hd_ticks <= hd_now;
                       end if;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_checker_tb.vhd — the VHDL testbench, single-writer throughout
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- 100 MHz sample clock. Fast-mode tSU;DAT = 100 ns = 10 ticks; footnote [3]'s internal hold
   -- obligation is 300 ns = 30 ticks.
   entity i2c_data_window_checker_tb is
   end entity;

   architecture sim of i2c_data_window_checker_tb is
       constant TICK_W       : positive := 16;
       constant T_SU_DAT_MIN : natural  := 10;
       constant T_HD_DAT_MIN : natural  := 30;

       signal clk   : std_logic := '0';
       signal rst_n : std_logic := '0';
       signal sda_in, scl_in : std_logic := '1';

       signal su_valid, hd_valid, viol_su, viol_hd : std_logic;
       signal t_su_dat, t_hd_dat : unsigned(TICK_W - 1 downto 0);
       signal n_su, n_hd, n_viol_su, n_viol_hd : unsigned(TICK_W - 1 downto 0);
       signal min_su_seen, min_hd_seen : unsigned(TICK_W - 1 downto 0);

       type tick_arr is array (0 to 31) of unsigned(TICK_W - 1 downto 0);
       type bit_arr  is array (0 to 31) of std_logic;
       signal su_log : tick_arr := (others => (others => '0'));
       signal hd_log : tick_arr := (others => (others => '0'));
       signal vsu_log, vhd_log : bit_arr := (others => '0');
       signal n_sulog, n_hdlog : natural := 0;

       signal test_done : std_logic := '0';
   begin
       dut : entity work.i2c_data_window_checker
           generic map (TICK_W => TICK_W, T_SU_DAT_MIN => T_SU_DAT_MIN,
                        T_HD_DAT_MIN => T_HD_DAT_MIN)
           port map (clk => clk, rst_n => rst_n, sda_in => sda_in, scl_in => scl_in,
                     su_valid => su_valid, t_su_dat => t_su_dat, hd_valid => hd_valid,
                     t_hd_dat => t_hd_dat, viol_su => viol_su, viol_hd => viol_hd,
                     n_su => n_su, n_hd => n_hd, n_viol_su => n_viol_su, n_viol_hd => n_viol_hd,
                     min_su_seen => min_su_seen, min_hd_seen => min_hd_seen);

       clk <= not clk after 5 ns;

       watchdog : process
       begin
           wait for 1 ms;
           if test_done = '0' then report "watchdog expired" severity failure; end if;
           wait;
       end process;

       obs_su : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and su_valid = '1' and n_sulog < 32 then
               su_log(n_sulog) <= t_su_dat; vsu_log(n_sulog) <= viol_su; n_sulog <= n_sulog + 1;
           end if;
       end process;

       obs_hd : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and hd_valid = '1' and n_hdlog < 32 then
               hd_log(n_hdlog) <= t_hd_dat; vhd_log(n_hdlog) <= viol_hd; n_hdlog <= n_hdlog + 1;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable base_su, base_hd : natural;
           variable nhd_before, vhd_before, nsu_before : unsigned(TICK_W - 1 downto 0);

           procedure tick (n : in positive) is
           begin
               for i in 1 to n loop wait until falling_edge(clk); end loop;
           end procedure;

           -- One bit, with the two intervals under test independently settable:
           --   `hold` ticks after SCL falls before SDA changes   -> tHD;DAT
           --   `setup` ticks after SDA changes before SCL rises  -> tSU;DAT
           procedure bit_cell (v : in std_logic; hold, setup, high : in positive) is
           begin
               scl_in <= '0'; tick(hold);    -- hold the PREVIOUS bit after the falling edge
               sda_in <= v;   tick(setup);   -- present the new bit, then wait the setup
               scl_in <= '1'; tick(high);
           end procedure;
       begin
           tick(3);
           if min_su_seen /= (min_su_seen'range => '1')
              or min_hd_seen /= (min_hd_seen'range => '1') then
               report "worst-case trackers did not start at their maximum" severity error;
               errs := errs + 1; end if;
           rst_n <= '1'; tick(2);
           sda_in <= '1'; scl_in <= '1'; tick(10);

           -- 1: four LEGAL bits, alternating so every one produces a real hold measurement.
           scl_in <= '1'; tick(5);
           bit_cell('0', 50, 40, 80);
           bit_cell('1', 50, 40, 80);
           bit_cell('0', 50, 40, 80);
           bit_cell('1', 50, 40, 80);
           scl_in <= '0'; tick(50);
           if n_viol_su /= to_unsigned(0, TICK_W) or n_viol_hd /= to_unsigned(0, TICK_W) then
               report "legal bits were flagged" severity error; errs := errs + 1; end if;
           if n_sulog < 4 then
               report "too few setup measurements, expected 4" severity error;
               errs := errs + 1; end if;
           if n_hdlog < 4 then
               report "too few hold measurements, expected 4" severity error;
               errs := errs + 1; end if;
           if su_log(1) < to_unsigned(35, TICK_W) or su_log(1) > to_unsigned(45, TICK_W) then
               report "a 40-tick setup measured out of range" severity error; errs := errs + 1; end if;
           if hd_log(1) < to_unsigned(45, TICK_W) or hd_log(1) > to_unsigned(55, TICK_W) then
               report "a 50-tick hold measured out of range" severity error; errs := errs + 1; end if;

           -- 2: SETUP too short, hold fine. Only the setup verdict may fail -- the two parameters
           -- are measured against different edges and are independent.
           base_su := n_sulog; base_hd := n_hdlog;
           bit_cell('0', 50, 4, 80);
           if vsu_log(base_su) /= '1' then
               report "a 4-tick setup was not flagged" severity error; errs := errs + 1; end if;
           if vhd_log(base_hd) /= '0' then
               report "a short SETUP also flagged the HOLD" severity error; errs := errs + 1; end if;

           -- 3: HOLD too short, setup fine. The mirror -- and the test that proves the hold is
           -- measured from the FALLING edge rather than the rising one. A checker that referenced
           -- the wrong edge passes test 2 and fails here.
           base_su := n_sulog; base_hd := n_hdlog;
           bit_cell('1', 5, 60, 80);
           if vhd_log(base_hd) /= '1' then
               report "a 5-tick hold was not flagged" severity error; errs := errs + 1; end if;
           if vsu_log(base_su) /= '0' then
               report "a short HOLD also flagged the SETUP" severity error; errs := errs + 1; end if;
           if hd_log(base_hd) > to_unsigned(7, TICK_W) then
               report "a 5-tick hold measured too long" severity error; errs := errs + 1; end if;

           -- 4: BOTH too short at once. Both verdicts must fire; a design reporting only the
           -- first failure it found would hide half the problem.
           base_su := n_sulog; base_hd := n_hdlog;
           bit_cell('0', 3, 3, 80);
           if not (vhd_log(base_hd) = '1' and vsu_log(base_su) = '1') then
               report "both intervals short did not report both verdicts" severity error;
               errs := errs + 1; end if;

           -- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
           base_su := n_sulog; base_hd := n_hdlog;
           bit_cell('1', T_HD_DAT_MIN, T_SU_DAT_MIN, 80);
           if vhd_log(base_hd) /= '0' then
               report "a hold of EXACTLY the minimum was rejected" severity error;
               errs := errs + 1; end if;
           if vsu_log(base_su) /= '0' then
               report "a setup of EXACTLY the minimum was rejected" severity error;
               errs := errs + 1; end if;

           base_su := n_sulog; base_hd := n_hdlog;
           bit_cell('0', T_HD_DAT_MIN - 1, T_SU_DAT_MIN - 1, 80);
           if vhd_log(base_hd) /= '1' then
               report "a hold one tick below the minimum was accepted" severity error;
               errs := errs + 1; end if;
           if vsu_log(base_su) /= '1' then
               report "a setup one tick below the minimum was accepted" severity error;
               errs := errs + 1; end if;

           -- 6: A REPEATED BIT produces NO hold measurement. If the transmitter sends the same
           -- value again, SDA never moves, so there is no hold interval -- and nothing to complain
           -- about, because a bit that never changes trivially satisfies any hold requirement.
           nhd_before := n_hd; vhd_before := n_viol_hd;
           sda_in <= '0'; scl_in <= '0'; tick(60);
           scl_in <= '1'; tick(80);
           scl_in <= '0'; tick(60);          -- same value: SDA does not move
           scl_in <= '1'; tick(80);
           scl_in <= '0'; tick(20);
           if n_hd /= nhd_before then
               report "a repeated bit produced hold measurements" severity error;
               errs := errs + 1; end if;
           if n_viol_hd /= vhd_before then
               report "a repeated bit was flagged as a hold violation" severity error;
               errs := errs + 1; end if;

           -- 6b: and the consequence of NOT cancelling the arm. Straight after the repeated bit, a
           -- bit with a genuinely SHORT hold must still be flagged. If the arm had survived the
           -- repeated bit, the timer would have been running since the EARLIER falling edge, the
           -- measured hold would be enormous, and this real violation would be missed.
           --
           -- SCL is left HIGH first so bit_cell supplies a FRESH falling edge: without that the
           -- hold is timed from the repeated-bit sequence's own earlier fall, which is correct
           -- behaviour but the wrong test.
           scl_in <= '1'; tick(40);
           base_hd := n_hdlog;
           bit_cell('1', 4, 60, 80);
           if vhd_log(base_hd) /= '1' then
               report "a 4-tick hold right after a repeated bit was not flagged -- a stale arm "
                    & "inflated the measurement" severity error; errs := errs + 1; end if;
           if hd_log(base_hd) > to_unsigned(10, TICK_W) then
               report "the hold was timed from an earlier falling edge" severity error;
               errs := errs + 1; end if;

           -- 6c: and the sharper consequence. After a repeated bit, an SDA change during a HIGH
           -- phase must produce NO hold measurement -- a hold is an interval that starts at a
           -- falling edge, and this change has no unclosed falling edge in front of it. With the
           -- arm left standing, that change is timed from the previous fall and reported as a hold
           -- that never happened.
           nhd_before := n_hd;
           sda_in <= '0'; scl_in <= '0'; tick(60);
           scl_in <= '1';               tick(40);   -- the low phase ends, no SDA change
           sda_in <= '1';               tick(40);   -- SDA moves while SCL is HIGH
           scl_in <= '0';               tick(40);
           if n_hd /= nhd_before then
               report "an SDA change during a HIGH phase produced a hold measurement -- the arm "
                    & "outlived its low phase" severity error; errs := errs + 1; end if;

           -- 7: STRETCHING. A very long low phase gives an enormous hold time, which is legal --
           -- tHD;DAT has a minimum, so more is always better. This confirms the comparison's sense.
           vhd_before := n_viol_hd;
           bit_cell('1', 2000, 40, 80);
           if n_viol_hd /= vhd_before then
               report "a 2000-tick hold was flagged" severity error; errs := errs + 1; end if;

           -- 8: the worst case is TRACKED, not the most recent. A clean run after a violation must
           -- not erase the record of it.
           for i in 1 to 4 loop bit_cell('0', 90, 90, 80); end loop;
           for i in 1 to 4 loop bit_cell('1', 90, 90, 80); end loop;
           if min_su_seen > to_unsigned(10, TICK_W) or min_hd_seen > to_unsigned(30, TICK_W) then
               report "worst cases were erased" severity error; errs := errs + 1; end if;

           -- 9: an IDLE bus measures nothing. No SCL edges, no setup and no hold.
           nsu_before := n_su;
           sda_in <= '1'; scl_in <= '1'; tick(300);
           if n_su /= nsu_before then
               report "an idle bus produced setup measurements" severity error;
               errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_data_window_checker self-check complete: setup referenced to the rising "
                    & "edge and hold to the falling one, independent verdicts, both boundaries "
                    & "exact, a repeated bit measures nothing" severity note;
           else
               report "i2c_data_window_checker self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

6a. Five Decisions Worth Defending

Two counters with visibly different shapes, because the parameters have different reference-edge positions. §2's callout is the argument. A reviewer should be able to see from the code which parameter is which kind, and a single shared counter would hide exactly the distinction that matters.

The hold is compared against footnote [3]'s 300 ns, not against Table 10's zero. The parameter is named T_HD_DAT_MIN and its comment says which of the two numbers it carries. A checker that used the table row would pass every device §3's callout warns about.

A repeated bit produces no measurement. Only an actual SDA change closes a hold interval. §5 gives the reason and mutation C2 in §8 shows what happens without the corresponding disarm.

Every measurement includes the sampling cycle. Both counters are read through a combinational "including this cycle" value. Chapter 11.1 §6a records how the omission was found; the consequence here is that both boundary comparisons shift by one sample period.

The two verdicts are independent outputs. A bit can fail one and pass the other — they are measured against different edges — and §7's tests 2 and 3 are the two halves of that. Mutation C4, which judges the hold against the setup minimum, is killed because the two minima differ (30 ticks against 10).

6b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o d3 i2c_data_window_checker.sv i2c_data_window_checker_tb.sv && ./d3
   PASS: setup referenced to the rising edge and hold to the falling one, independent verdicts,
   both boundaries exact, a repeated bit measures nothing
   i2c_data_window_checker_tb.sv:228: $finish called at 65170000 (1ps)

   $ iverilog -g2005 -o v3 i2c_data_window_checker.v i2c_data_window_checker_tb.v && ./v3
   PASS: setup referenced to the rising edge and hold to the falling one, independent verdicts,
   both boundaries exact, a repeated bit measures nothing
   i2c_data_window_checker_tb.v:237: $finish called at 65170000 (1ps)

   $ nvc -a i2c_data_window_checker.vhd i2c_data_window_checker_tb.vhd
   $ nvc -e i2c_data_window_checker_tb && nvc -r i2c_data_window_checker_tb --stop-time=1200us
   ** Note: 65170ns+0: i2c_data_window_checker self-check complete: setup referenced to the
      rising edge and hold to the falling one, independent verdicts, both boundaries exact, a
      repeated bit measures nothing

All three at 65170 ns.

7. What the Testbench Proves

The stimulus drives one bit with the hold and setup intervals independently settable, so either can be made illegal alone.

#stimuluswhat it establishes
1four legal alternating bitsnothing flagged; both measurements land where intended
250-tick hold, 4-tick setuponly the setup verdict fails
35-tick hold, 60-tick setuponly the hold verdict fails — the mirror of test 2
4both shortboth verdicts fire
5exactly at each minimum, then one belowboth boundaries pinned from both sides
6a repeated bitno hold measurement, and no violation
6ba short hold immediately after a repeated bitstill flagged, and measured from the fresh falling edge
6cSDA moving during a high phase after a repeated bitno hold measurement at all
7a 2000-tick holdnot a violation — tHD;DAT is a minimum
8a clean run after violationsworst cases not erased
9an idle busnothing measured

Test 3 is the one that proves the reference edge. A checker that armed its hold timer on the rising edge passes test 2 — the setup half is unaffected — and fails here, because the interval it measures is not tHD;DAT at all. Mutation C1 is that single-token change, and test 3 is the only stimulus in the suite that separates them.

Tests 6b and 6c exist because mutations survived without them, and the two cover different consequences of the same defect. Without the disarm, the arm from an earlier falling edge is still standing: 6b shows a genuinely short hold being missed because the stale timer inflated the measurement to 24 ticks, and 6c shows a hold being invented for an SDA change during a high phase, where there is no unclosed falling edge in front of it. One is a false negative and the other a false positive, from one missing branch.

Test 6b also carries a stimulus lesson. Its first version left SCL low, so bit_cell supplied no fresh falling edge and the hold was timed from the repeated-bit sequence's own earlier fall — correct behaviour, wrong test, measuring 24 ticks instead of 4. Fixing it meant raising SCL first. A test for an interval must supply the edge that starts it.

8. Mutation Testing

Five defects injected into the SystemVerilog checker.

#injected defectoutcome
C1the hold is referenced to the rising edgekilled — test 3
C2the hold arm is not cancelled when the bit does not changekilled — tests 6b and 6c
C3the setup is measured with a timer armed by the edgekilled
C4the hold is judged against the setup minimumkilled
C5setup violations are counted but not reportedkilled

Five injected, five killed — with C2 requiring two new tests, recorded here rather than smoothed over.

C2 survived the first suite and the reason is instructive. Test 6 checked that a repeated bit produces no hold measurement, and it passed even with the disarm removed — because within test 6 itself SDA never moves at all, so no measurement appears either way. The defect only manifests later, when the stale arm meets the next SDA change. A test that checks "nothing happened during this window" cannot catch a defect whose effect is deferred, and the fix was to check what happens next.

That generalises: when a mutation removes a cleanup action, the test has to run past the cleanup to the operation that depends on it.

C4 is only observable because the two minima differ. With T_SU_DAT_MIN at 10 ticks and T_HD_DAT_MIN at 30, judging a hold against the setup limit accepts holds between 10 and 29 ticks that should fail. Had the testbench set both parameters to the same value the mutation would have been unobservable — the same parameter-degeneracy trap Chapter 11.4 §8 hits with tVD;DAT and tVD;ACK, where Table 10 gives the two the same number and the testbench has to deliberately differ.

9. Verification Connection — The Window Belongs to the Interface

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_data_window_props.sv — two properties, two reference edges
   // A continuously running stability timer. A setup interval ends at its reference edge, so it
   // cannot be timed by a counter armed there -- section 2's rule, as SVA.
   int stable_ticks;
   always_ff @(posedge clk)
      stable_ticks <= $changed(sda) ? 0 : stable_ticks + 1;

   property p_su_dat;
      @(posedge clk) $rose(scl) |-> (stable_ticks >= T_SU_DAT_MIN);
   endproperty
   assert property (p_su_dat)
      else $error("tSU;DAT violated: stable for %0d ticks, minimum %0d",
                  stable_ticks, T_SU_DAT_MIN);

   // The HOLD property is written the other way round: from the FALLING edge, SDA must not
   // change for T_HD_DAT_MIN cycles. Expressed as a bounded `throughout` rather than by timing
   // an interval, because what must be shown is that no change occurred inside a window -- and
   // a property about an interval cannot be checked at its endpoints (Chapter 10.1 section 9).
   property p_hd_dat;
      @(posedge clk) $fell(scl) |-> ($stable(sda) [*T_HD_DAT_MIN]);
   endproperty
   assert property (p_hd_dat)
      else $error("tHD;DAT violated: SDA moved within %0d ticks of SCL falling", T_HD_DAT_MIN);

   // And the NEGATIVE property. tHD;DAT is a MINIMUM, so a long hold is legal -- a design that
   // added an upper bound would flag every stretched transfer. Chapter 10.3 section 8 argues for
   // asserting what must NOT happen wherever the wrong belief is reasonable, and "a hold that
   // long must be wrong" is a very reasonable wrong belief.
   property p_hd_has_no_maximum;
      @(posedge clk) (hold_ticks > T_HD_DAT_MIN) |-> !viol_hd;
   endproperty
   assert property (p_hd_has_no_maximum)
      else $error("a long hold was reported as a violation -- tHD;DAT has no maximum");
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_window_cov.sv — where in the low phase the transition actually happened
   covergroup i2c_window_cg with function sample(int hold, int setup, int t_low,
                                                int hd_min, int su_min);
      // WHERE in the low phase the bit changed, as a fraction. This is the coverpoint that
      // matters: a suite whose transitions always land in the middle of the low phase has
      // exercised neither parameter, however many bits it sent.
      position: coverpoint ((hold * 100) / t_low) {
         bins at_the_fall  = {[0:5]};      // the tHD;DAT edge -- must be reachable, must stay
                                           // empty in regression
         bins early        = {[6:25]};
         bins middle       = {[26:70]};
         bins late         = {[71:94]};
         bins at_the_rise  = {[95:100]};   // the tSU;DAT edge
      }

      // Margins RELATIVE to each minimum, so the bins are reusable across speed modes -- the
      // same reasoning Chapter 11.1 section 9 applies. An absolute bin set written for Fast-mode
      // says nothing about Fast-mode Plus, where footnote [3]'s 300 ns is a much larger share of
      // a much shorter low phase.
      hd_margin: coverpoint (hold - hd_min) {
         bins violation = {[$:-1]};
         bins exact     = {0};
         bins tight     = {[1:10]};
         bins ample     = {[11:$]};
      }
      su_margin: coverpoint (setup - su_min) {
         bins violation = {[$:-1]};
         bins exact     = {0};
         bins tight     = {[1:10]};
         bins ample     = {[11:$]};
      }

      // The cross is the point. The two parameters are measured against different edges, so a
      // suite can have ample margin on one and none on the other -- and covering them
      // separately cannot show that both were ever tight at once.
      hd_x_su: cross hd_margin, su_margin;

      // A repeated bit is a distinct case that produces NO hold measurement, and a suite that
      // never sends one has not exercised the disarm path that mutation C2 attacks.
      repeated_bit: coverpoint (hold == 0 && setup == 0);
   endgroup

10. FPGA and ASIC Implications

Two counters, two compares — around 55 flops at TICK_W = 16. No arithmetic on any critical path.

The 300 ns of footnote [3] does not scale with speed, and that is the sizing consequence. It is the same 300 ns in all three modes, so as a share of the low phase it goes from 6 % of Standard-mode's 4.7 µs to 60 % of Fast-mode Plus's 500 ns. An Fm+ transmitter has 200 ns of its low phase left for everything else, and Chapter 11.9 shows that budget failing to close on the table's own worst-case numbers.

On the generating side, the hold is a delay you must insert deliberately. The natural RTL is to change SDA in the same clocked process that sees SCL fall, which gives a hold of one system-clock period — 10 ns at 100 MHz, thirty times short of the 300 ns required. Meeting footnote [3] means a counted delay between observing the falling edge and driving the new bit, and it is the kind of requirement that is easy to omit because the code looks correct without it.

The spike filter of Chapter 11.8 interacts with this measurement in a way worth stating. The filter delays transitions on both lines equally, so the measured intervals here are unchanged — a delay common to both ends of an interval cancels. What does not cancel is the effect on a transmitter: a filtered SCL means the falling edge is observed late, so a transmitter that starts its hold delay from the filtered edge finishes late too, and the time comes out of the low phase.

11. Debugging — The Hold Time Nobody Inserted

Pitfall — reading a minimum of zero as no hold requirement
Buggy Code
// A slave's byte-transmit path. On each falling edge of SCL, present the next bit:
//
//     always_ff @(posedge clk) begin
//        if (scl_fall) sda_drive_low <= ~next_bit;   // drive the new bit immediately
//     end
//
// The designer checked Table 10, found tHD;DAT with a minimum of 0 for I2C-bus devices, and
// concluded correctly-as-far-as-it-goes that no hold was required. The RTL changes SDA one
// system clock after observing SCL fall: 10 ns at 100 MHz.
//
// Footnote [3] requires 300 ns. The design provides 10.
//
// And on the bench it worked perfectly, because the bench board had one slave, a 2.2 kohm
// pull-up and about 40 pF of bus capacitance -- SCL's falling edge took maybe 20 ns, so the
// ambiguous window the 300 ns exists to bridge was barely there.
Symptom

The production board had six devices, longer traces and 10 kohm pull-ups fitted to save current. SCL's fall time went from about 20 ns to nearly 250 ns.

Reads from the new slave returned values with single-bit errors. Not every read -- perhaps one byte in two hundred -- and the corrupted bit was not always in the same position.

The single most misleading observation: a logic-analyser capture of a failing read looked completely clean. Every bit sat at a well-defined level for its whole high phase. The analyser thresholds the signal, so a bit that changed during SCL's slow fall still reads as one stable value per cell.

Worse, the OTHER master on the bus read the same slave correctly. Two devices reading the same byte off the same wires, one getting it right and one wrong -- which is not a symptom anybody expects from a protocol violation, and it sent the investigation towards the failing master's input stage.

That divergence was the clue, once someone recognised it for what it is. If two receivers disagree about the same bit, the bit is genuinely ambiguous -- and on this bus the only thing that makes a bit ambiguous is SDA moving while SCL is neither clearly high nor clearly low. A scope confirmed it immediately: SDA was changing partway down SCL's fall.

Root Cause

The slave changed SDA within 10 ns of SCL's falling edge, inside the roughly 250 ns during which SCL was traversing the undefined region between VIH and VIL. Receivers whose input threshold sat higher had already latched; receivers whose threshold sat lower had not. Both were behaving correctly; the bus was not presenting a single answer.

The design met Table 10's stated minimum of zero and violated footnote [3]'s 300 ns, and the two are about different things: the table row is what the BUS requires, the footnote is what a DEVICE must provide precisely so that a finite fall time cannot make a bit ambiguous.

12. Common Misconceptions

"tHD;DAT(min) is zero, so there is no hold requirement." Zero is what the bus requires. Footnote [3] requires a device to provide 300 ns, and §11 is that misreading in the field.

"A hold time is measured after the sampling edge." On this bus it is measured after the falling edge — footnote [2] says so — because its job is to bridge the ambiguity of that edge, not to give the receiver time to sample. The receiver sampled at the rising edge.

"The 300 ns scales with the speed grade." It does not. The same 300 ns applies in all three modes, so it is proportionally tightest at Fast-mode Plus, where it consumes 60 % of the low phase.

"Setup and hold can share one counter." Their reference edges sit at opposite ends of their intervals, so one needs a free-running timer sampled at the edge and the other an armed timer stopped by a change. §2's callout is the rule.

"A repeated bit violates the hold requirement, since SDA never moved." A bit that never changes trivially satisfies any hold requirement. There is no interval to measure and nothing to report — and a checker that measured the whole low phase instead would never see a real violation.

"A long hold is a violation." tHD;DAT is a minimum. Footnote [4] mentions a maximum, but note its condition: it applies only if the device does not stretch the clock, and it is bounded by tVD;DAT rather than being an independent limit.

"A clean logic-analyser capture rules out a hold violation." It rules out nothing about timing. The analyser thresholds the signal before recording it, so a bit changing inside SCL's fall still reads as one stable value.

13. Reason It Through

A design changes SDA one system clock after SCL falls, and meets Table 10's tHD;DAT(min) of zero. Is it compliant?

No. Footnote [3] requires the device to provide at least 300 ns, and one clock at 100 MHz is 10 ns. The table row states what the bus requires; the footnote states what a device must provide so that SCL's finite fall time cannot make a bit ambiguous. §11 is the consequence.

Two masters read the same byte from the same slave and get different values. What does that tell you, and where should you not look?

That the bit is genuinely ambiguous on the wire — two correct receivers cannot disagree about an unambiguous signal. Do not look at either receiver's input stage. On this bus the only thing that makes a bit ambiguous is an SDA edge inside a clock transition, which is a tHD;DAT violation.

Why does a checker that measures the whole low phase as its hold time never report a violation?

Because the low phase is always at least tLOW(min), which is far longer than the 300 ns hold requirement. The measurement would always pass. The hold interval must end at the SDA change, which is why a repeated bit produces no measurement at all rather than a very long one.

A mutation removes the code that cancels the hold arm at the end of a low phase, and the test checking "a repeated bit produces no measurement" still passes. Why, and what kind of test is needed?

Because within that window SDA never moves, so no measurement appears either way — the defect's effect is deferred to the next SDA change. A test has to run past the cleanup to the operation that depends on it: §7's tests 6b and 6c check the next bit's hold is measured from a fresh edge, and that a high-phase change produces nothing. When a mutation removes a cleanup, the test must reach the next user of the cleaned-up state.

Footnote [5] requires a Fast-mode device in a Standard-mode system to get the next bit out 1250 ns before releasing SCL. Where does 1250 come from, and why is it so much larger than the 100 ns Fast-mode setup?

It is tr(max) + tSU;DAT at Standard-mode values: 1000 + 250. The 1000 ns dominates, and it is Standard-mode's rise time — a full microsecond, because Standard-mode permits far slower edges. The setup requirement is not the large term; the settling is. It is the clearest single illustration of Chapter 11.7's point that the edges are inside the budget.

14. Understanding Check

15. Summary

Two parameters, two reference edges. tSU;DAT before the rising edge, tHD;DAT after the falling one — and footnote [2] states the second explicitly because it is the counter-intuitive one.

The hold is referenced to the falling edge because that is where the ambiguity is. SCL's fall has finite width, and during it a receiver cannot say whether it has sampled. The hold keeps SDA still until the clock has unambiguously fallen; it is not time for the receiver to sample, which already happened.

tHD;DAT(min) is zero for the bus and 300 ns for a device. Both are true and about different things. Reading the table row without footnote [3] produces a design that works on a lightly loaded board and fails as the fall time grows.

The 300 ns does not scale with speed, so it is proportionally tightest at Fast-mode Plus — 60 % of a 500 ns low phase, which is why Chapter 11.9's budget does not close there on worst-case numbers.

Which end of an interval its reference edge sits at decides how it can be measured. Setup needs a free-running timer sampled at the edge; hold needs an armed timer stopped by a change. One counter cannot serve both, and a checker whose shapes are visibly different is one a reviewer can check.

A repeated bit measures nothing, and the arm must be cancelled when its low phase ends. Omitting the cancellation both misses real violations and invents false ones — and the test for it has to run past the omission to the next operation that depends on it.

Two receivers disagreeing about one bit is a diagnosis, not a mystery. It means the bit is ambiguous on the wire, and on this bus that means an SDA edge inside a clock transition.

16. What Comes Next

Chapter 11.4 takes the other side of this bargain. tSU;DAT says what a receiver needs; tVD;DAT says how quickly a transmitter must produce the bit — and it is a maximum, which inverts every comparison and every worst-case tracker in the checker.

It also explains why the acknowledge gets its own parameter, tVD;ACK, when Table 10 gives it the identical number in all three speed modes: because a different device drives it, so it is a different device's promise. Two parameters with one value are still two obligations, and a testbench that set them equal could not tell whether a design applied them independently — which is exactly the parameter-degeneracy trap that let one of that chapter's mutations survive until the configured values were deliberately made to differ.

Continue learning