Skip to content
VLSI Mentor

I²C · Module 11

tSU;STA and tHD;STA — START and Repeated-START Margins

The first parameters measured between two different signals rather than against a clock edge — and the timing-level reason a repeated START is not simply a START in the middle of a transfer.

Every parameter so far has measured SDA against SCL: a stable interval ending at a clock edge, a hold beginning at one, a response delay bounded from one. The clock was always the reference.

The framing parameters are different. tSU;STA and tHD;STA measure the interval between an SCL edge and an SDA edge, in opposite directions:

tSU;STA — from SCL's rise to SDA's fall · tHD;STA — from SDA's fall to SCL's fall

Neither has a clock edge at both ends. Both ends of both intervals are a specific edge on a specific line, and getting the pairing wrong produces a checker that measures something real and reports it under the wrong name.

This chapter also answers, at the timing level, the question Chapter 10.2 answered at the protocol level: why a repeated START is a genuinely different event from a first START, when the waveform is the same shape.

1. The Two Rows, and an Identity

Three observations, and the third is the one that shapes the chapter.

tHD;STA(min) equals tHIGH(min) in all three modes. 4.0, 0.6, 0.26 — the same three numbers, exactly. That is not coincidence: after SDA falls, SCL is still high, and the interval before SCL may fall is the remainder of a high phase. The specification asks for a full high phase's worth. A START's hold time is a high phase wearing a different name, which is why the table can add "after this period, the first clock pulse is generated" — the high phase that has just elapsed is the first clock pulse's high time.

tSU;STA(min) equals tLOW(min) in Standard-mode only. 4.7 matches, but Fast-mode's 0.6 does not match 1.3, and Fm+'s 0.26 does not match 0.5. So the analogy that works for the hold time does not extend to the setup time — the repeated-START setup is a shorter requirement than a low phase in the faster modes, because it does not have to contain a data transition.

And the hazard: tSU;STA and tHD;STA are EQUAL in Fast-mode and Fast-mode Plus. 0.6 and 0.6; 0.26 and 0.26. They differ only in Standard-mode, 4.7 against 4.0.

2. Why a Repeated START Is Timed Differently

Both table rows say "(repeated) START" and "repeated START condition". That wording is deliberate, and it is where the two kinds of START part company.

a first STARTa repeated START
what precedes itan idle busthe previous transfer's last bit
the preceding requirement istBUF — bus free timetSU;STA — setup from SCL's rise
who else may be startinganyone — arbitration is possiblenobody — this master holds the bus
its hold requirementtHD;STAtHD;STA, identical

The hold half is the same for both. Once SDA has fallen while SCL is high, the event is a START, and SCL must stay high for tHD;STA regardless of what came before.

The setup half is a different parameter for each. A first START is preceded by tBUF, the bus-free time of Chapter 11.6 — because the bus was idle and the requirement is about how long it had been idle. A repeated START is preceded by tSU;STA, because the bus was active and the requirement is about the margin after the previous bit's clock pulse.

That is the timing-level reason the two events differ. Chapter 10.2 gave the protocol reason — a repeated START keeps the bus, so no other master may interleave — and the two are the same fact seen from two levels. A repeated START is not a START that happens to occur mid-transfer; it is an event whose entry condition is a clock-referenced margin rather than an idle interval.

And the numbers reflect it. In Standard-mode tBUF(min) is 4.7 µs and tSU;STA(min) is also 4.7 µs — equal, which looks like the two cases collapsing. In Fast-mode tBUF is 1.3 µs and tSU;STA is 0.6 µs: a repeated START may be issued in under half the time a first START requires. That is the speed advantage of holding the bus, expressed as a number, and it is why Chapter 10.1 prefers a repeated START for a write-then-read even setting atomicity aside.

3. The Two Margins, Drawn

tSU;STA ends where tHD;STA begins: at SDA's falling edge

10 cycles
Ten intervals. SCL is low for two intervals, rises and stays high for six, then falls. SDA is high for five intervals and falls during the sixth, while SCL is still high. A region row marks the setup margin from SCL's rise to SDA's fall, and the hold margin from SDA's fall to SCL's fall.tSU;STA min 0.6 ustSU;STA min 0.6 ustHD;STA min 0.6 ustHD;STA min 0.6 usSCL rises: tSU;STA startsSCL rises: tSU;STA startsSDA falls: STARTSDA falls: STARTSCL falls: tHD;STA endsSCL falls: tHD;STA endssclsdaregion00SUSUSUHDHDHDHDHDt0t1t2t3t4t5t6t7t8t9
A repeated START. The setup margin runs from SCL's rising edge to SDA's falling edge; the hold margin from SDA's falling edge to SCL's falling edge. The two share one endpoint — SDA's fall — and neither has a clock edge at both ends.

Three things the figure settles.

SDA's falling edge is the shared endpoint. One margin ends there and the other begins there, which means a single event closes one measurement and opens another — and a checker must do both in the same cycle. §6a is about getting that right.

SCL is high across the whole of both margins. SDA falling while SCL is high is the definition of a START (Chapter 5.2), so the figure is that definition with two intervals attached. If SCL were low, the SDA fall would be an ordinary data transition and neither margin would apply.

Together the two margins are longer than a high phase. In Fast-mode, 0.6 + 0.6 = 1.2 µs of SCL high time, against tHIGH(min) of 0.6 µs. A repeated START therefore costs a stretched high phase — SCL is held high for at least twice its normal minimum — which is the real reason a repeated START is not free even though it is cheaper than a STOP-then-START.

4. What Each Margin Protects

tSU;STA protects other devices' ability to see the START coming. The previous bit's clock pulse has just ended and SCL has risen again; every device on the bus is watching for a data bit. The setup margin is the time in which their input stages settle on the high SDA level before it falls, so that the fall is unambiguously an edge rather than part of a still-settling transition. Without it, a device whose SDA input had not yet resolved the previous bit could miss the START entirely and keep counting bits.

tHD;STA protects the START's own recognition. After SDA falls, SCL must remain high long enough for every device to register that a START occurred before the first clock pulse arrives. The table's note is explicit about the boundary — "after this period, the first clock pulse is generated" — so the hold margin is the gap between detecting a START and being clocked by it.

That is why tHD;STA equals tHIGH(min): the recognition of a START takes exactly as long as the sampling of a bit, because it is the same input stage doing the same job.

5. The Start Margin Checker in Three Languages

The design watches both lines, recognises a START by its definition, measures both margins, and reports each independently.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_margin_checker.sv — two margins measured between different signals
   // START AND REPEATED-START MARGINS: tSU;STA and tHD;STA. Together they bracket the SDA
   // falling edge that makes a START, and between them they are what keeps a repeated START
   // distinguishable from a data bit at speed.
   //
   //     SCL rises --[ tSU;STA ]--> SDA FALLS (the START) --[ tHD;STA ]--> SCL falls
   //                                                                      (first clock pulse)
   //
   // Table 10 names them:
   //     tHD;STA  "hold time (repeated) START condition. After this period, the first clock
   //               pulse is generated."     min 4.0 / 0.6 / 0.26 us
   //     tSU;STA  "set-up time for a repeated START condition"   min 4.7 / 0.6 / 0.26 us
   //
   // TWO THINGS ABOUT THE PAIR ARE WORTH NOTICING BEFORE THE CODE.
   //
   // First, tSU;STA is specified for a REPEATED start only, and that is not an oversight. A
   // plain START begins from an idle bus, where SCL has been released high for at least tBUF
   // and usually far longer -- so the setup is satisfied by the bus having been idle, and
   // there is nothing for a parameter to constrain. A repeated START is the case where SCL has
   // only just been released, and the margin has to be stated because it can be short.
   //
   // Second, these two are measured completely differently from tSU;DAT, and the reason is
   // structural rather than a matter of taste. tSU;DAT's interval ENDS at its reference edge,
   // so it cannot be timed by a counter armed at the start -- by the time the rising edge
   // arrives the interval is over, which is why Chapter 11.3 needs a continuously running
   // timer. Both of these intervals BEGIN at an edge, so a counter armed by that edge measures
   // them directly. Whether a timing parameter can be measured with an armed counter is
   // decided by which end of it the reference edge sits at.
   //
   // PASSIVE: observes the two wires and drives nothing.
   module i2c_start_margin_checker #(
       parameter int TICK_W = 16,
       // Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60 ticks for both.
       parameter int T_SU_STA_MIN = 60,
       parameter int T_HD_STA_MIN = 60
   )(
       input  logic clk,
       input  logic rst_n,
       input  logic sda_in,
       input  logic scl_in,

       // ---- framing detected here, so the block is self-contained on a capture ----
       output logic start_det,        // pulse: SDA fell while SCL was high
       output logic restart_det,      // pulse: ... and a transfer was already open
       output logic stop_det,         // pulse: SDA rose while SCL was high
       output logic in_transfer,

       // ---- measured, reported at the events that close each interval ----
       output logic              su_valid,      // pulse: a tSU;STA has been measured
       output logic [TICK_W-1:0] t_su_sta,
       output logic              hd_valid,      // pulse: a tHD;STA has been measured
       output logic [TICK_W-1:0] t_hd_sta,

       // ---- verdicts ----
       output logic viol_su_sta,
       output logic viol_hd_sta,

       // ---- totals and worst cases ----
       output logic [TICK_W-1:0] n_su_sta,
       output logic [TICK_W-1:0] n_hd_sta,
       output logic [TICK_W-1:0] n_viol,
       output logic [TICK_W-1:0] min_su_sta_seen,
       output logic [TICK_W-1:0] min_hd_sta_seen
   );
       logic sda_q, scl_q;
       logic scl_rise, scl_fall, sda_fall, sda_rise, scl_stable_high;
       assign scl_rise        = !scl_q &&  scl_in;
       assign scl_fall        =  scl_q && !scl_in;
       assign sda_fall        =  sda_q && !sda_in;
       assign sda_rise        = !sda_q &&  sda_in;
       // SCL high ACROSS the SDA edge, not merely after it -- the detector discipline
       // Module 4's mutation suite established and Chapter 10.2 reuses.
       assign scl_stable_high = scl_q && scl_in;

       // tSU;STA: armed by SCL rising, stopped by the SDA fall that makes the START.
       logic              su_arm;
       logic [TICK_W-1:0] su_ticks;
       logic [TICK_W-1:0] su_now;
       assign su_now = su_ticks + 1'b1;

       // tHD;STA: armed by that same SDA fall, stopped by the next SCL fall.
       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;
               su_arm          <= 1'b0;
               su_ticks        <= '0;
               hd_arm          <= 1'b0;
               hd_ticks        <= '0;
               start_det       <= 1'b0;
               restart_det     <= 1'b0;
               stop_det        <= 1'b0;
               in_transfer     <= 1'b0;
               su_valid        <= 1'b0;
               t_su_sta        <= '0;
               hd_valid        <= 1'b0;
               t_hd_sta        <= '0;
               viol_su_sta     <= 1'b0;
               viol_hd_sta     <= 1'b0;
               n_su_sta        <= '0;
               n_hd_sta        <= '0;
               n_viol          <= '0;
               min_su_sta_seen <= {TICK_W{1'b1}};
               min_hd_sta_seen <= {TICK_W{1'b1}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               start_det   <= 1'b0;
               restart_det <= 1'b0;
               stop_det    <= 1'b0;
               su_valid    <= 1'b0;
               hd_valid    <= 1'b0;

               // ---- arm the setup timer whenever SCL is released ----
               if (scl_rise) begin
                   su_arm   <= 1'b1;
                   su_ticks <= '0;
               end else if (su_arm && !(sda_fall && scl_stable_high)) begin
                   su_ticks <= su_now;
               end

               // ---- framing, and the measurements the framing events close ----
               if (sda_fall && scl_stable_high) begin
                   start_det <= 1'b1;
                   if (in_transfer) begin
                       restart_det <= 1'b1;
                       // tSU;STA is a REPEATED-start parameter. Measuring it on a plain START
                       // would report the idle time since the bus was last released, which is
                       // a true number and not this parameter -- and would make every first
                       // transfer look like it had an enormous margin, hiding the fact that
                       // the block never checked the case the spec cares about.
                       if (su_arm) begin
                           su_valid    <= 1'b1;
                           t_su_sta    <= su_now;
                           viol_su_sta <= (su_now < T_SU_STA_MIN[TICK_W-1:0]);
                           n_su_sta    <= n_su_sta + 1'b1;
                           if (su_now < T_SU_STA_MIN[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
                           if (su_now < min_su_sta_seen) min_su_sta_seen <= su_now;
                       end
                   end
                   in_transfer <= 1'b1;
                   su_arm      <= 1'b0;
                   // The same edge arms the hold timer. tHD;STA runs from the START to the
                   // first clock pulse, so its two ends are this edge and the next SCL fall.
                   hd_arm      <= 1'b1;
                   hd_ticks    <= '0;
               end else if (sda_rise && scl_stable_high) begin
                   stop_det    <= 1'b1;
                   in_transfer <= 1'b0;
                   // A STOP cancels any hold measurement in flight: there will be no first
                   // clock pulse, so the interval has no end.
                   hd_arm      <= 1'b0;
               end else if (hd_arm && scl_fall) begin
                   // The first clock pulse after the START. This closes tHD;STA.
                   hd_arm      <= 1'b0;
                   hd_valid    <= 1'b1;
                   t_hd_sta    <= hd_now;
                   viol_hd_sta <= (hd_now < T_HD_STA_MIN[TICK_W-1:0]);
                   n_hd_sta    <= n_hd_sta + 1'b1;
                   if (hd_now < T_HD_STA_MIN[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
                   if (hd_now < min_hd_sta_seen) min_hd_sta_seen <= hd_now;
               end else if (hd_arm) begin
                   hd_ticks <= hd_now;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_margin_checker_tb.sv — eleven scenarios, the two limits deliberately different
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tSU;STA = tHD;STA = 0.6 us = 60 ticks.
   module i2c_start_margin_checker_tb;
       localparam int TICK_W       = 16;
       localparam int T_SU_STA_MIN = 60;
       localparam int T_HD_STA_MIN = 60;

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

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

       logic start_det, restart_det, stop_det, in_transfer;
       logic su_valid, hd_valid, viol_su_sta, viol_hd_sta;
       logic [TICK_W-1:0] t_su_sta, t_hd_sta, n_su_sta, n_hd_sta, n_viol;
       logic [TICK_W-1:0] min_su_sta_seen, min_hd_sta_seen;

       int errors = 0;
       int base_su, base_hd;
       logic [TICK_W-1:0] nsu_before, viol_before;

       i2c_start_margin_checker #(.TICK_W(TICK_W), .T_SU_STA_MIN(T_SU_STA_MIN),
           .T_HD_STA_MIN(T_HD_STA_MIN)) dut (.*);

       initial begin #2000000; $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_sta; vsu_log[n_sulog] = viol_su_sta; 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_sta; vhd_log[n_hdlog] = viol_hd_sta; n_hdlog++;
       end

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

       // A plain START from an idle bus: SDA falls while SCL is high, then SCL falls after
       // `hd` ticks. tSU;STA does not apply here -- see the design header.
       task automatic plain_start(input int hd);
           begin
               sda_in = 1'b1; scl_in = 1'b1; tick(40);
               sda_in = 1'b0;                tick(hd);
               scl_in = 1'b0;                tick(20);
           end
       endtask

       // A REPEATED START, with both margins settable. SCL is released, `su` ticks pass, SDA
       // falls, `hd` ticks pass, SCL falls to give the first clock pulse.
       task automatic repeated_start(input int su, input int hd);
           begin
               sda_in = 1'b1; scl_in = 1'b0; tick(20);   // release SDA while SCL is low
               scl_in = 1'b1;                tick(su);   // release SCL: tSU;STA starts here
               sda_in = 1'b0;                tick(hd);   // the Sr: tHD;STA starts here
               scl_in = 1'b0;                tick(20);   // the first clock pulse
           end
       endtask

       // One ordinary data bit, so the bus does something between framing events.
       task automatic data_bit(input logic v);
           begin
               scl_in = 1'b0; sda_in = v; tick(30);
               scl_in = 1'b1;             tick(80);
               scl_in = 1'b0;             tick(30);
           end
       endtask

       task automatic bus_stop();
           begin
               sda_in = 1'b0; scl_in = 1'b0; tick(20);
               scl_in = 1'b1;                tick(60);
               sda_in = 1'b1;                tick(40);
           end
       endtask

       initial begin
           tick(3);
           if (min_su_sta_seen !== {TICK_W{1'b1}} || min_hd_sta_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);

           // ---- 1: a PLAIN START with a generous hold. tHD;STA is measured and passes;
           //      tSU;STA must NOT be measured at all, because it is a repeated-start
           //      parameter and a plain START begins from an idle bus.
           plain_start(100);
           data_bit(1'b1);
           if (n_hdlog !== 1) begin
               $display("FAIL: %0d tHD;STA measurements from one plain START, expected 1",
                        n_hdlog); errors++; end
           if (n_su_sta !== '0) begin
               $display("FAIL: a plain START produced %0d tSU;STA measurements -- it is a repeated-start parameter",
                        n_su_sta); errors++; end
           if (vhd_log[0] !== 1'b0) begin
               $display("FAIL: a 100-tick tHD;STA was flagged (min %0d)", T_HD_STA_MIN);
               errors++; end
           if (hd_log[0] < 16'd95 || hd_log[0] > 16'd105) begin
               $display("FAIL: a 100-tick tHD;STA measured %0d", hd_log[0]); errors++; end

           // ---- 2: a REPEATED START with both margins generous. Now BOTH are measured.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               repeated_start(120, 120);
               data_bit(1'b1);
               if (n_sulog !== base_su + 1) begin
                   $display("FAIL: a repeated START produced no tSU;STA measurement"); errors++; end
               if (vsu_log[base_su] !== 1'b0 || vhd_log[base_hd] !== 1'b0) begin
                   $display("FAIL: generous margins were flagged -- su=%b hd=%b",
                            vsu_log[base_su], vhd_log[base_hd]); errors++; end
               if (su_log[base_su] < 16'd115 || su_log[base_su] > 16'd125) begin
                   $display("FAIL: a 120-tick tSU;STA measured %0d", su_log[base_su]); errors++; end
           end
           bus_stop();

           // ---- 3: tSU;STA too SHORT, hold fine. Only the setup verdict may fire. This is
           //      the case that matters at speed: SCL is released and SDA pulled low almost
           //      immediately, so a receiver cannot tell the START from a data bit.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(15, 120);
               data_bit(1'b1);
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: a 15-tick tSU;STA was not flagged (min %0d)", T_SU_STA_MIN);
                   errors++; end
               if (vhd_log[base_hd + 1] !== 1'b0) begin
                   $display("FAIL: a short tSU;STA also flagged tHD;STA"); errors++; end
           end
           bus_stop();

           // ---- 4: tHD;STA too SHORT, setup fine. The mirror, and the one that proves the
           //      two intervals are measured against different pairs of edges.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(120, 20);
               data_bit(1'b1);
               if (vhd_log[base_hd + 1] !== 1'b1) begin
                   $display("FAIL: a 20-tick tHD;STA was not flagged"); errors++; end
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: a short tHD;STA also flagged tSU;STA"); errors++; end
           end
           bus_stop();

           // ---- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(T_SU_STA_MIN, T_HD_STA_MIN);
               data_bit(1'b1);
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: tSU;STA exactly at the minimum was rejected"); errors++; end
               if (vhd_log[base_hd + 1] !== 1'b0) begin
                   $display("FAIL: tHD;STA exactly at the minimum was rejected"); errors++; end
           end
           bus_stop();
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(T_SU_STA_MIN - 1, T_HD_STA_MIN - 1);
               data_bit(1'b1);
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: tSU;STA one tick below the minimum was accepted"); errors++; end
               if (vhd_log[base_hd + 1] !== 1'b1) begin
                   $display("FAIL: tHD;STA one tick below the minimum was accepted"); errors++; end
           end
           bus_stop();

           // ---- 6: a STOP cancels a hold measurement in flight. After a START with no clock
           //      pulse and then a STOP, tHD;STA has no end -- the first clock pulse never
           //      came -- so no measurement may be reported. A block that reported one would
           //      be timing an interval that did not happen.
           begin
               base_hd = n_hdlog;
               sda_in = 1'b1; scl_in = 1'b1; tick(40);
               sda_in = 1'b0;                tick(40);   // a START ...
               sda_in = 1'b1;                tick(40);   // ... then straight to a STOP
               if (n_hdlog !== base_hd) begin
                   $display("FAIL: a START followed by a STOP produced a tHD;STA measurement");
                   errors++; end
               // And it must STAY cancelled. A clock pulse after the STOP must not close the
               // abandoned interval -- if the arm survived the STOP, this falling edge would
               // report a tHD;STA spanning the STOP itself, which is not an interval at all.
               scl_in = 1'b1; tick(30);
               scl_in = 1'b0; tick(30);
               if (n_hdlog !== base_hd) begin
                   $display("FAIL: a clock pulse after the STOP closed the abandoned tHD;STA interval");
                   errors++; end
           end

           // ---- 7: the worst cases are TRACKED. A clean run after the violations of tests
           //      3 to 5 must not erase them.
           begin
               repeat (3) begin
                   plain_start(300);
                   data_bit(1'b1);
                   repeated_start(300, 300);
                   data_bit(1'b1);
                   bus_stop();
               end
               if (min_su_sta_seen > 16'd59) begin
                   $display("FAIL: min_su_sta_seen = %0d after short margins were seen",
                            min_su_sta_seen); errors++; end
               if (min_hd_sta_seen > 16'd59) begin
                   $display("FAIL: min_hd_sta_seen = %0d after short margins were seen",
                            min_hd_sta_seen); errors++; end
           end

           // ---- 8: an SDA edge while SCL is LOW is DATA, not framing, so it must produce
           //      neither a framing event nor a margin measurement. This is the check that
           //      stops the block from inventing STARTs inside a byte.
           begin
               nsu_before = n_su_sta; viol_before = n_viol;
               plain_start(200);
               data_bit(1'b0); data_bit(1'b1); data_bit(1'b0); data_bit(1'b1);
               bus_stop();
               if (n_su_sta !== nsu_before) begin
                   $display("FAIL: data bits produced %0d tSU;STA measurements",
                            n_su_sta - nsu_before); errors++; end
               if (n_viol !== viol_before) begin
                   $display("FAIL: data bits produced %0d violations", n_viol - viol_before);
                   errors++; end
           end

           // ---- 9: an IDLE bus measures nothing at all.
           begin
               nsu_before = n_su_sta;
               sda_in = 1'b1; scl_in = 1'b1; tick(600);
               if (n_su_sta !== nsu_before) begin
                   $display("FAIL: an idle bus produced measurements"); errors++; end
           end

           if (errors == 0)
               $display("PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge independently, both boundaries exact, a cancelled interval is not measured");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_margin_checker.v — the same checker in Verilog-2001
   // START AND REPEATED-START MARGINS: tSU;STA and tHD;STA. Together they bracket the SDA
   // falling edge that makes a START, and between them they are what keeps a repeated START
   // distinguishable from a data bit at speed.
   //
   //     SCL rises --[ tSU;STA ]--> SDA FALLS (the START) --[ tHD;STA ]--> SCL falls
   //                                                                      (first clock pulse)
   //
   // Table 10 names them:
   //     tHD;STA  "hold time (repeated) START condition. After this period, the first clock
   //               pulse is generated."     min 4.0 / 0.6 / 0.26 us
   //     tSU;STA  "set-up time for a repeated START condition"   min 4.7 / 0.6 / 0.26 us
   //
   // TWO THINGS ABOUT THE PAIR ARE WORTH NOTICING BEFORE THE CODE.
   //
   // First, tSU;STA is specified for a REPEATED start only, and that is not an oversight. A
   // plain START begins from an idle bus, where SCL has been released high for at least tBUF
   // and usually far longer -- so the setup is satisfied by the bus having been idle, and
   // there is nothing for a parameter to constrain. A repeated START is the case where SCL has
   // only just been released, and the margin has to be stated because it can be short.
   //
   // Second, these two are measured completely differently from tSU;DAT, and the reason is
   // structural rather than a matter of taste. tSU;DAT's interval ENDS at its reference edge,
   // so it cannot be timed by a counter armed at the start -- by the time the rising edge
   // arrives the interval is over, which is why Chapter 11.3 needs a continuously running
   // timer. Both of these intervals BEGIN at an edge, so a counter armed by that edge measures
   // them directly. Whether a timing parameter can be measured with an armed counter is
   // decided by which end of it the reference edge sits at.
   //
   // PASSIVE: observes the two wires and drives nothing.
   // (Verilog-2001)
   module i2c_start_margin_checker #(
       parameter TICK_W = 16,
       // Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60 ticks for both.
       parameter T_SU_STA_MIN = 60,
       parameter T_HD_STA_MIN = 60
   )(
       input  wire  clk,
       input  wire  rst_n,
       input  wire  sda_in,
       input  wire  scl_in,

       // ---- framing detected here, so the block is self-contained on a capture ----
       output reg   start_det,        // pulse: SDA fell while SCL was high
       output reg   restart_det,      // pulse: ... and a transfer was already open
       output reg   stop_det,         // pulse: SDA rose while SCL was high
       output reg   in_transfer,

       // ---- measured, reported at the events that close each interval ----
       output reg                su_valid,      // pulse: a tSU;STA has been measured
       output reg   [TICK_W-1:0] t_su_sta,
       output reg                hd_valid,      // pulse: a tHD;STA has been measured
       output reg   [TICK_W-1:0] t_hd_sta,

       // ---- verdicts ----
       output reg   viol_su_sta,
       output reg   viol_hd_sta,

       // ---- totals and worst cases ----
       output reg   [TICK_W-1:0] n_su_sta,
       output reg   [TICK_W-1:0] n_hd_sta,
       output reg   [TICK_W-1:0] n_viol,
       output reg   [TICK_W-1:0] min_su_sta_seen,
       output reg   [TICK_W-1:0] min_hd_sta_seen
   );
       reg sda_q, scl_q;
       wire scl_rise, scl_fall, sda_fall, sda_rise, scl_stable_high;
       assign scl_rise        = !scl_q &&  scl_in;
       assign scl_fall        =  scl_q && !scl_in;
       assign sda_fall        =  sda_q && !sda_in;
       assign sda_rise        = !sda_q &&  sda_in;
       // SCL high ACROSS the SDA edge, not merely after it -- the detector discipline
       // Module 4's mutation suite established and Chapter 10.2 reuses.
       assign scl_stable_high = scl_q && scl_in;

       // tSU;STA: armed by SCL rising, stopped by the SDA fall that makes the START.
       reg              su_arm;
       reg [TICK_W-1:0] su_ticks;
       wire [TICK_W-1:0] su_now;
       assign su_now = su_ticks + 1'b1;

       // tHD;STA: armed by that same SDA fall, stopped by the next SCL fall.
       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;
               su_arm          <= 1'b0;
               su_ticks        <= {TICK_W{1'b0}};
               hd_arm          <= 1'b0;
               hd_ticks        <= {TICK_W{1'b0}};
               start_det       <= 1'b0;
               restart_det     <= 1'b0;
               stop_det        <= 1'b0;
               in_transfer     <= 1'b0;
               su_valid        <= 1'b0;
               t_su_sta        <= {TICK_W{1'b0}};
               hd_valid        <= 1'b0;
               t_hd_sta        <= {TICK_W{1'b0}};
               viol_su_sta     <= 1'b0;
               viol_hd_sta     <= 1'b0;
               n_su_sta        <= {TICK_W{1'b0}};
               n_hd_sta        <= {TICK_W{1'b0}};
               n_viol          <= {TICK_W{1'b0}};
               min_su_sta_seen <= {TICK_W{1'b1}};
               min_hd_sta_seen <= {TICK_W{1'b1}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               start_det   <= 1'b0;
               restart_det <= 1'b0;
               stop_det    <= 1'b0;
               su_valid    <= 1'b0;
               hd_valid    <= 1'b0;

               // ---- arm the setup timer whenever SCL is released ----
               if (scl_rise) begin
                   su_arm   <= 1'b1;
                   su_ticks <= {TICK_W{1'b0}};
               end else if (su_arm && !(sda_fall && scl_stable_high)) begin
                   su_ticks <= su_now;
               end

               // ---- framing, and the measurements the framing events close ----
               if (sda_fall && scl_stable_high) begin
                   start_det <= 1'b1;
                   if (in_transfer) begin
                       restart_det <= 1'b1;
                       // tSU;STA is a REPEATED-start parameter. Measuring it on a plain START
                       // would report the idle time since the bus was last released, which is
                       // a true number and not this parameter -- and would make every first
                       // transfer look like it had an enormous margin, hiding the fact that
                       // the block never checked the case the spec cares about.
                       if (su_arm) begin
                           su_valid    <= 1'b1;
                           t_su_sta    <= su_now;
                           viol_su_sta <= (su_now < T_SU_STA_MIN);
                           n_su_sta    <= n_su_sta + 1'b1;
                           if (su_now < T_SU_STA_MIN) n_viol <= n_viol + 1'b1;
                           if (su_now < min_su_sta_seen) min_su_sta_seen <= su_now;
                       end
                   end
                   in_transfer <= 1'b1;
                   su_arm      <= 1'b0;
                   // The same edge arms the hold timer. tHD;STA runs from the START to the
                   // first clock pulse, so its two ends are this edge and the next SCL fall.
                   hd_arm      <= 1'b1;
                   hd_ticks    <= {TICK_W{1'b0}};
               end else if (sda_rise && scl_stable_high) begin
                   stop_det    <= 1'b1;
                   in_transfer <= 1'b0;
                   // A STOP cancels any hold measurement in flight: there will be no first
                   // clock pulse, so the interval has no end.
                   hd_arm      <= 1'b0;
               end else if (hd_arm && scl_fall) begin
                   // The first clock pulse after the START. This closes tHD;STA.
                   hd_arm      <= 1'b0;
                   hd_valid    <= 1'b1;
                   t_hd_sta    <= hd_now;
                   viol_hd_sta <= (hd_now < T_HD_STA_MIN);
                   n_hd_sta    <= n_hd_sta + 1'b1;
                   if (hd_now < T_HD_STA_MIN) n_viol <= n_viol + 1'b1;
                   if (hd_now < min_hd_sta_seen) min_hd_sta_seen <= hd_now;
               end else if (hd_arm) begin
                   hd_ticks <= hd_now;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_margin_checker_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tSU;STA = tHD;STA = 0.6 us = 60 ticks.
   module i2c_start_margin_checker_tb;   // Verilog-2001
       localparam TICK_W       = 16;
       localparam T_SU_STA_MIN = 60;
       localparam T_HD_STA_MIN = 60;

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

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

       wire start_det, restart_det, stop_det, in_transfer;
       wire su_valid, hd_valid, viol_su_sta, viol_hd_sta;
       wire [TICK_W-1:0] t_su_sta, t_hd_sta, n_su_sta, n_hd_sta, n_viol;
       wire [TICK_W-1:0] min_su_sta_seen, min_hd_sta_seen;

       integer errors = 0;
       integer base_su = 0, base_hd = 0;
       reg [TICK_W-1:0] nsu_before, viol_before;

       i2c_start_margin_checker #(.TICK_W(TICK_W), .T_SU_STA_MIN(T_SU_STA_MIN),
           .T_HD_STA_MIN(T_HD_STA_MIN)) dut (
           .clk(clk), .rst_n(rst_n), .sda_in(sda_in), .scl_in(scl_in), .start_det(start_det),
           .restart_det(restart_det), .stop_det(stop_det), .in_transfer(in_transfer),
           .su_valid(su_valid), .t_su_sta(t_su_sta), .hd_valid(hd_valid), .t_hd_sta(t_hd_sta),
           .viol_su_sta(viol_su_sta), .viol_hd_sta(viol_hd_sta), .n_su_sta(n_su_sta),
           .n_hd_sta(n_hd_sta), .n_viol(n_viol), .min_su_sta_seen(min_su_sta_seen),
           .min_hd_sta_seen(min_hd_sta_seen));

       initial begin #2000000; $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_sta; vsu_log[n_sulog] = viol_su_sta; 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_sta; vhd_log[n_hdlog] = viol_hd_sta; n_hdlog = n_hdlog + 1;
       end

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

       // A plain START from an idle bus: SDA falls while SCL is high, then SCL falls after
       // `hd` ticks. tSU;STA does not apply here -- see the design header.
       task plain_start;
           input integer hd;
       begin
               sda_in = 1'b1; scl_in = 1'b1; tick(40);
               sda_in = 1'b0;                tick(hd);
               scl_in = 1'b0;                tick(20);
               end
       endtask

       // A REPEATED START, with both margins settable. SCL is released, `su` ticks pass, SDA
       // falls, `hd` ticks pass, SCL falls to give the first clock pulse.
       task repeated_start;
           input integer su;
           input integer hd;
       begin
               sda_in = 1'b1; scl_in = 1'b0; tick(20);   // release SDA while SCL is low
               scl_in = 1'b1;                tick(su);   // release SCL: tSU;STA starts here
               sda_in = 1'b0;                tick(hd);   // the Sr: tHD;STA starts here
               scl_in = 1'b0;                tick(20);   // the first clock pulse
               end
       endtask

       // One ordinary data bit, so the bus does something between framing events.
       task data_bit;
           input v;
       begin
               scl_in = 1'b0; sda_in = v; tick(30);
               scl_in = 1'b1;             tick(80);
               scl_in = 1'b0;             tick(30);
               end
       endtask

       task bus_stop;
       begin
               sda_in = 1'b0; scl_in = 1'b0; tick(20);
               scl_in = 1'b1;                tick(60);
               sda_in = 1'b1;                tick(40);
               end
       endtask

       initial begin
           tick(3);
           if (min_su_sta_seen !== {TICK_W{1'b1}} || min_hd_sta_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);

           // ---- 1: a PLAIN START with a generous hold. tHD;STA is measured and passes;
           //      tSU;STA must NOT be measured at all, because it is a repeated-start
           //      parameter and a plain START begins from an idle bus.
           plain_start(100);
           data_bit(1'b1);
           if (n_hdlog !== 1) begin
               $display("FAIL: %0d tHD;STA measurements from one plain START, expected 1",
                        n_hdlog); errors = errors + 1; end
           if (n_su_sta !== {TICK_W{1'b0}}) begin
               $display("FAIL: a plain START produced %0d tSU;STA measurements -- it is a repeated-start parameter",
                        n_su_sta); errors = errors + 1; end
           if (vhd_log[0] !== 1'b0) begin
               $display("FAIL: a 100-tick tHD;STA was flagged (min %0d)", T_HD_STA_MIN);
               errors = errors + 1; end
           if (hd_log[0] < 16'd95 || hd_log[0] > 16'd105) begin
               $display("FAIL: a 100-tick tHD;STA measured %0d", hd_log[0]); errors = errors + 1; end

           // ---- 2: a REPEATED START with both margins generous. Now BOTH are measured.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               repeated_start(120, 120);
               data_bit(1'b1);
               if (n_sulog !== base_su + 1) begin
                   $display("FAIL: a repeated START produced no tSU;STA measurement"); errors = errors + 1; end
               if (vsu_log[base_su] !== 1'b0 || vhd_log[base_hd] !== 1'b0) begin
                   $display("FAIL: generous margins were flagged -- su=%b hd=%b",
                            vsu_log[base_su], vhd_log[base_hd]); errors = errors + 1; end
               if (su_log[base_su] < 16'd115 || su_log[base_su] > 16'd125) begin
                   $display("FAIL: a 120-tick tSU;STA measured %0d", su_log[base_su]); errors = errors + 1; end
           end
           bus_stop;

           // ---- 3: tSU;STA too SHORT, hold fine. Only the setup verdict may fire. This is
           //      the case that matters at speed: SCL is released and SDA pulled low almost
           //      immediately, so a receiver cannot tell the START from a data bit.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(15, 120);
               data_bit(1'b1);
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: a 15-tick tSU;STA was not flagged (min %0d)", T_SU_STA_MIN);
                   errors = errors + 1; end
               if (vhd_log[base_hd + 1] !== 1'b0) begin
                   $display("FAIL: a short tSU;STA also flagged tHD;STA"); errors = errors + 1; end
           end
           bus_stop;

           // ---- 4: tHD;STA too SHORT, setup fine. The mirror, and the one that proves the
           //      two intervals are measured against different pairs of edges.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(120, 20);
               data_bit(1'b1);
               if (vhd_log[base_hd + 1] !== 1'b1) begin
                   $display("FAIL: a 20-tick tHD;STA was not flagged"); errors = errors + 1; end
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: a short tHD;STA also flagged tSU;STA"); errors = errors + 1; end
           end
           bus_stop;

           // ---- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(T_SU_STA_MIN, T_HD_STA_MIN);
               data_bit(1'b1);
               if (vsu_log[base_su] !== 1'b0) begin
                   $display("FAIL: tSU;STA exactly at the minimum was rejected"); errors = errors + 1; end
               if (vhd_log[base_hd + 1] !== 1'b0) begin
                   $display("FAIL: tHD;STA exactly at the minimum was rejected"); errors = errors + 1; end
           end
           bus_stop;
           begin
               base_su = n_sulog; base_hd = n_hdlog;
               plain_start(100);
               data_bit(1'b1);
               repeated_start(T_SU_STA_MIN - 1, T_HD_STA_MIN - 1);
               data_bit(1'b1);
               if (vsu_log[base_su] !== 1'b1) begin
                   $display("FAIL: tSU;STA one tick below the minimum was accepted"); errors = errors + 1; end
               if (vhd_log[base_hd + 1] !== 1'b1) begin
                   $display("FAIL: tHD;STA one tick below the minimum was accepted"); errors = errors + 1; end
           end
           bus_stop;

           // ---- 6: a STOP cancels a hold measurement in flight. After a START with no clock
           //      pulse and then a STOP, tHD;STA has no end -- the first clock pulse never
           //      came -- so no measurement may be reported. A block that reported one would
           //      be timing an interval that did not happen.
           begin
               base_hd = n_hdlog;
               sda_in = 1'b1; scl_in = 1'b1; tick(40);
               sda_in = 1'b0;                tick(40);   // a START ...
               sda_in = 1'b1;                tick(40);   // ... then straight to a STOP
               if (n_hdlog !== base_hd) begin
                   $display("FAIL: a START followed by a STOP produced a tHD;STA measurement");
                   errors = errors + 1; end
               // And it must STAY cancelled. A clock pulse after the STOP must not close the
               // abandoned interval -- if the arm survived the STOP, this falling edge would
               // report a tHD;STA spanning the STOP itself, which is not an interval at all.
               scl_in = 1'b1; tick(30);
               scl_in = 1'b0; tick(30);
               if (n_hdlog !== base_hd) begin
                   $display("FAIL: a clock pulse after the STOP closed the abandoned tHD;STA interval");
                   errors = errors + 1; end
           end

           // ---- 7: the worst cases are TRACKED. A clean run after the violations of tests
           //      3 to 5 must not erase them.
           begin
               repeat (3) begin
                   plain_start(300);
                   data_bit(1'b1);
                   repeated_start(300, 300);
                   data_bit(1'b1);
                   bus_stop;
               end
               if (min_su_sta_seen > 16'd59) begin
                   $display("FAIL: min_su_sta_seen = %0d after short margins were seen",
                            min_su_sta_seen); errors = errors + 1; end
               if (min_hd_sta_seen > 16'd59) begin
                   $display("FAIL: min_hd_sta_seen = %0d after short margins were seen",
                            min_hd_sta_seen); errors = errors + 1; end
           end

           // ---- 8: an SDA edge while SCL is LOW is DATA, not framing, so it must produce
           //      neither a framing event nor a margin measurement. This is the check that
           //      stops the block from inventing STARTs inside a byte.
           begin
               nsu_before = n_su_sta; viol_before = n_viol;
               plain_start(200);
               data_bit(1'b0); data_bit(1'b1); data_bit(1'b0); data_bit(1'b1);
               bus_stop;
               if (n_su_sta !== nsu_before) begin
                   $display("FAIL: data bits produced %0d tSU;STA measurements",
                            n_su_sta - nsu_before); errors = errors + 1; end
               if (n_viol !== viol_before) begin
                   $display("FAIL: data bits produced %0d violations", n_viol - viol_before);
                   errors = errors + 1; end
           end

           // ---- 9: an IDLE bus measures nothing at all.
           begin
               nsu_before = n_su_sta;
               sda_in = 1'b1; scl_in = 1'b1; tick(600);
               if (n_su_sta !== nsu_before) begin
                   $display("FAIL: an idle bus produced measurements"); errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge independently, both boundaries exact, a cancelled interval is not measured");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_margin_checker.vhd — the same checker in VHDL
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- START AND REPEATED-START MARGINS: tSU;STA and tHD;STA. Together they bracket the SDA falling
   -- edge that makes a START, and between them they keep a repeated START distinguishable from a
   -- data bit at speed.
   --
   --     SCL rises --[ tSU;STA ]--> SDA FALLS (the START) --[ tHD;STA ]--> SCL falls
   --                                                                     (first clock pulse)
   --
   -- Table 10 names them:
   --     tHD;STA  "hold time (repeated) START condition. After this period, the first clock pulse
   --               is generated."              min 4.0 / 0.6 / 0.26 us
   --     tSU;STA  "set-up time for a repeated START condition"   min 4.7 / 0.6 / 0.26 us
   --
   -- TWO THINGS ABOUT THE PAIR ARE WORTH NOTICING.
   --
   -- First, tSU;STA is specified for a REPEATED start only, and that is not an oversight. A plain
   -- START begins from an idle bus, where SCL has been released high for at least tBUF and usually
   -- far longer -- so the setup is satisfied by the bus having been idle, and there is nothing for
   -- a parameter to constrain. A repeated START is the case where SCL has only just been released.
   --
   -- Second, these two are measured completely differently from tSU;DAT, for a structural reason.
   -- tSU;DAT's interval ENDS at its reference edge, so it cannot be timed by a counter armed at
   -- the start -- by the time the rising edge arrives the interval is over, which is why Chapter
   -- 11.3 needs a continuously running timer. Both of these intervals BEGIN at an edge, so a
   -- counter armed by that edge measures them directly. Whether a timing parameter can be
   -- measured with an armed counter is decided by which end of it the reference edge sits at.
   entity i2c_start_margin_checker is
       generic (
           TICK_W : positive := 16;
           -- Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60 ticks for both.
           T_SU_STA_MIN : natural := 60;
           T_HD_STA_MIN : natural := 60
       );
       port (
           clk    : in std_logic;
           rst_n  : in std_logic;
           sda_in : in std_logic;
           scl_in : in std_logic;

           start_det   : out std_logic;
           restart_det : out std_logic;
           stop_det    : out std_logic;
           in_transfer : out std_logic;

           su_valid : out std_logic;
           t_su_sta : out unsigned(TICK_W - 1 downto 0);
           hd_valid : out std_logic;
           t_hd_sta : out unsigned(TICK_W - 1 downto 0);

           viol_su_sta : out std_logic;
           viol_hd_sta : out std_logic;

           n_su_sta : out unsigned(TICK_W - 1 downto 0);
           n_hd_sta : out unsigned(TICK_W - 1 downto 0);
           n_viol   : out unsigned(TICK_W - 1 downto 0);
           min_su_sta_seen : out unsigned(TICK_W - 1 downto 0);
           min_hd_sta_seen : out unsigned(TICK_W - 1 downto 0)
       );
   end entity;

   architecture rtl of i2c_start_margin_checker is
       signal sda_q, scl_q : std_logic := '1';
       signal scl_rise, scl_fall, sda_fall, sda_rise, scl_stable_high : std_logic;

       -- tSU;STA: armed by SCL rising, stopped by the SDA fall that makes the START.
       signal su_arm   : std_logic := '0';
       signal su_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal su_now   : unsigned(TICK_W - 1 downto 0);

       -- tHD;STA: armed by that same SDA fall, stopped by the next SCL fall.
       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);

       signal xfer : std_logic := '0';
       signal is_start_edge : std_logic;
   begin
       scl_rise <= (not scl_q) and scl_in;
       scl_fall <= scl_q and (not scl_in);
       sda_fall <= sda_q and (not sda_in);
       sda_rise <= (not sda_q) and sda_in;
       -- SCL high ACROSS the SDA edge, not merely after it -- the detector discipline Module 4's
       -- mutation suite established and Chapter 10.2 reuses.
       scl_stable_high <= scl_q and scl_in;

       is_start_edge <= sda_fall and scl_stable_high;
       su_now <= su_ticks + 1;
       hd_now <= hd_ticks + 1;
       in_transfer <= xfer;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   sda_q       <= '1';
                   scl_q       <= '1';
                   su_arm      <= '0';
                   su_ticks    <= (others => '0');
                   hd_arm      <= '0';
                   hd_ticks    <= (others => '0');
                   start_det   <= '0';
                   restart_det <= '0';
                   stop_det    <= '0';
                   xfer        <= '0';
                   su_valid    <= '0';
                   t_su_sta    <= (others => '0');
                   hd_valid    <= '0';
                   t_hd_sta    <= (others => '0');
                   viol_su_sta <= '0';
                   viol_hd_sta <= '0';
                   n_su_sta    <= (others => '0');
                   n_hd_sta    <= (others => '0');
                   n_viol      <= (others => '0');
                   min_su_sta_seen <= (others => '1');
                   min_hd_sta_seen <= (others => '1');
               else
                   sda_q <= sda_in;
                   scl_q <= scl_in;

                   start_det   <= '0';
                   restart_det <= '0';
                   stop_det    <= '0';
                   su_valid    <= '0';
                   hd_valid    <= '0';

                   -- arm the setup timer whenever SCL is released
                   if scl_rise = '1' then
                       su_arm   <= '1';
                       su_ticks <= (others => '0');
                   elsif su_arm = '1' and is_start_edge = '0' then
                       su_ticks <= su_now;
                   end if;

                   if is_start_edge = '1' then
                       start_det <= '1';
                       if xfer = '1' then
                           restart_det <= '1';
                           -- tSU;STA is a REPEATED-start parameter. Measuring it on a plain START
                           -- would report the idle time since the bus was last released, which is
                           -- a true number and not this parameter -- and would make every first
                           -- transfer look like it had an enormous margin, hiding the fact that
                           -- the block never checked the case the spec cares about.
                           if su_arm = '1' then
                               su_valid <= '1';
                               t_su_sta <= su_now;
                               if su_now < to_unsigned(T_SU_STA_MIN, TICK_W) then
                                   viol_su_sta <= '1';
                                   n_viol      <= n_viol + 1;
                               else
                                   viol_su_sta <= '0';
                               end if;
                               n_su_sta <= n_su_sta + 1;
                               if su_now < min_su_sta_seen then min_su_sta_seen <= su_now; end if;
                           end if;
                       end if;
                       xfer     <= '1';
                       su_arm   <= '0';
                       -- The same edge arms the hold timer: tHD;STA runs from the START to the
                       -- first clock pulse, so its two ends are this edge and the next SCL fall.
                       hd_arm   <= '1';
                       hd_ticks <= (others => '0');
                   elsif sda_rise = '1' and scl_stable_high = '1' then
                       stop_det <= '1';
                       xfer     <= '0';
                       -- A STOP cancels any hold measurement in flight: there will be no first
                       -- clock pulse, so the interval has no end.
                       hd_arm   <= '0';
                   elsif hd_arm = '1' and scl_fall = '1' then
                       -- The first clock pulse after the START. This closes tHD;STA.
                       hd_arm   <= '0';
                       hd_valid <= '1';
                       t_hd_sta <= hd_now;
                       if hd_now < to_unsigned(T_HD_STA_MIN, TICK_W) then
                           viol_hd_sta <= '1';
                           n_viol      <= n_viol + 1;
                       else
                           viol_hd_sta <= '0';
                       end if;
                       n_hd_sta <= n_hd_sta + 1;
                       if hd_now < min_hd_sta_seen then min_hd_sta_seen <= hd_now; end if;
                   elsif hd_arm = '1' then
                       hd_ticks <= hd_now;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_margin_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;STA = tHD;STA = 0.6 us = 60 ticks.
   entity i2c_start_margin_checker_tb is
   end entity;

   architecture sim of i2c_start_margin_checker_tb is
       constant TICK_W       : positive := 16;
       constant T_SU_STA_MIN : natural  := 60;
       constant T_HD_STA_MIN : natural  := 60;

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

       signal start_det, restart_det, stop_det, in_transfer : std_logic;
       signal su_valid, hd_valid, viol_su_sta, viol_hd_sta : std_logic;
       signal t_su_sta, t_hd_sta : unsigned(TICK_W - 1 downto 0);
       signal n_su_sta, n_hd_sta, n_viol : unsigned(TICK_W - 1 downto 0);
       signal min_su_sta_seen, min_hd_sta_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, 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_start_margin_checker
           generic map (TICK_W => TICK_W, T_SU_STA_MIN => T_SU_STA_MIN,
                        T_HD_STA_MIN => T_HD_STA_MIN)
           port map (clk => clk, rst_n => rst_n, sda_in => sda_in, scl_in => scl_in,
                     start_det => start_det, restart_det => restart_det, stop_det => stop_det,
                     in_transfer => in_transfer, su_valid => su_valid, t_su_sta => t_su_sta,
                     hd_valid => hd_valid, t_hd_sta => t_hd_sta, viol_su_sta => viol_su_sta,
                     viol_hd_sta => viol_hd_sta, n_su_sta => n_su_sta, n_hd_sta => n_hd_sta,
                     n_viol => n_viol, min_su_sta_seen => min_su_sta_seen,
                     min_hd_sta_seen => min_hd_sta_seen);

       clk <= not clk after 5 ns;

       watchdog : process
       begin
           wait for 2 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_sta; vsu_log(n_sulog) <= viol_su_sta;
               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_sta; vhd_log(n_hdlog) <= viol_hd_sta;
               n_hdlog <= n_hdlog + 1;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable base_su, base_hd : natural;
           variable nsu_before, viol_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;

           -- A plain START from an idle bus: SDA falls while SCL is high, then SCL falls after
           -- `hd` ticks. tSU;STA does not apply here -- see the design header.
           procedure plain_start (hd : in positive) is
           begin
               sda_in <= '1'; scl_in <= '1'; tick(40);
               sda_in <= '0';               tick(hd);
               scl_in <= '0';               tick(20);
           end procedure;

           -- A REPEATED START, with both margins settable.
           procedure repeated_start (su, hd : in positive) is
           begin
               sda_in <= '1'; scl_in <= '0'; tick(20);   -- release SDA while SCL is low
               scl_in <= '1';                tick(su);   -- release SCL: tSU;STA starts here
               sda_in <= '0';                tick(hd);   -- the Sr: tHD;STA starts here
               scl_in <= '0';                tick(20);   -- the first clock pulse
           end procedure;

           procedure data_bit (v : in std_logic) is
           begin
               scl_in <= '0'; sda_in <= v; tick(30);
               scl_in <= '1';              tick(80);
               scl_in <= '0';              tick(30);
           end procedure;

           procedure bus_stop is
           begin
               sda_in <= '0'; scl_in <= '0'; tick(20);
               scl_in <= '1';                tick(60);
               sda_in <= '1';                tick(40);
           end procedure;
       begin
           tick(3);
           if min_su_sta_seen /= (min_su_sta_seen'range => '1')
              or min_hd_sta_seen /= (min_hd_sta_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);

           -- 1: a PLAIN START with a generous hold. tHD;STA is measured and passes; tSU;STA must
           -- NOT be measured at all, because it is a repeated-start parameter.
           plain_start(100);
           data_bit('1');
           if n_hdlog /= 1 then
               report "wrong number of tHD;STA measurements from one plain START" severity error;
               errs := errs + 1; end if;
           if n_su_sta /= to_unsigned(0, TICK_W) then
               report "a plain START produced tSU;STA measurements -- it is a repeated-start parameter"
                   severity error; errs := errs + 1; end if;
           if vhd_log(0) /= '0' then
               report "a 100-tick tHD;STA was flagged" severity error; errs := errs + 1; end if;
           if hd_log(0) < to_unsigned(95, TICK_W) or hd_log(0) > to_unsigned(105, TICK_W) then
               report "a 100-tick tHD;STA measured out of range" severity error;
               errs := errs + 1; end if;

           -- 2: a REPEATED START with both margins generous. Now BOTH are measured.
           base_su := n_sulog; base_hd := n_hdlog;
           repeated_start(120, 120);
           data_bit('1');
           if n_sulog /= base_su + 1 then
               report "a repeated START produced no tSU;STA measurement" severity error;
               errs := errs + 1; end if;
           if vsu_log(base_su) /= '0' or vhd_log(base_hd) /= '0' then
               report "generous margins were flagged" severity error; errs := errs + 1; end if;
           if su_log(base_su) < to_unsigned(115, TICK_W)
              or su_log(base_su) > to_unsigned(125, TICK_W) then
               report "a 120-tick tSU;STA measured out of range" severity error;
               errs := errs + 1; end if;
           bus_stop;

           -- 3: tSU;STA too SHORT, hold fine. Only the setup verdict may fire. This is the case
           -- that matters at speed: SCL released and SDA pulled low almost immediately, so a
           -- receiver cannot tell the START from a data bit.
           base_su := n_sulog; base_hd := n_hdlog;
           plain_start(100);
           data_bit('1');
           repeated_start(15, 120);
           data_bit('1');
           if vsu_log(base_su) /= '1' then
               report "a 15-tick tSU;STA was not flagged" severity error; errs := errs + 1; end if;
           if vhd_log(base_hd + 1) /= '0' then
               report "a short tSU;STA also flagged tHD;STA" severity error; errs := errs + 1; end if;
           bus_stop;

           -- 4: tHD;STA too SHORT, setup fine. The mirror, and the one that proves the two
           -- intervals are measured against different pairs of edges.
           base_su := n_sulog; base_hd := n_hdlog;
           plain_start(100);
           data_bit('1');
           repeated_start(120, 20);
           data_bit('1');
           if vhd_log(base_hd + 1) /= '1' then
               report "a 20-tick tHD;STA was not flagged" severity error; errs := errs + 1; end if;
           if vsu_log(base_su) /= '0' then
               report "a short tHD;STA also flagged tSU;STA" severity error; errs := errs + 1; end if;
           bus_stop;

           -- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
           base_su := n_sulog; base_hd := n_hdlog;
           plain_start(100);
           data_bit('1');
           repeated_start(T_SU_STA_MIN, T_HD_STA_MIN);
           data_bit('1');
           if vsu_log(base_su) /= '0' then
               report "tSU;STA exactly at the minimum was rejected" severity error;
               errs := errs + 1; end if;
           if vhd_log(base_hd + 1) /= '0' then
               report "tHD;STA exactly at the minimum was rejected" severity error;
               errs := errs + 1; end if;
           bus_stop;

           base_su := n_sulog; base_hd := n_hdlog;
           plain_start(100);
           data_bit('1');
           repeated_start(T_SU_STA_MIN - 1, T_HD_STA_MIN - 1);
           data_bit('1');
           if vsu_log(base_su) /= '1' then
               report "tSU;STA one tick below the minimum was accepted" severity error;
               errs := errs + 1; end if;
           if vhd_log(base_hd + 1) /= '1' then
               report "tHD;STA one tick below the minimum was accepted" severity error;
               errs := errs + 1; end if;
           bus_stop;

           -- 6: a STOP cancels a hold measurement in flight. After a START with no clock pulse and
           -- then a STOP, tHD;STA has no end -- the first clock pulse never came -- so no
           -- measurement may be reported. A block reporting one would be timing an interval that
           -- did not happen.
           base_hd := n_hdlog;
           sda_in <= '1'; scl_in <= '1'; tick(40);
           sda_in <= '0';               tick(40);   -- a START ...
           sda_in <= '1';               tick(40);   -- ... then straight to a STOP
           if n_hdlog /= base_hd then
               report "a START followed by a STOP produced a tHD;STA measurement" severity error;
               errs := errs + 1; end if;
           -- And it must STAY cancelled. A clock pulse after the STOP must not close the abandoned
           -- interval -- if the arm survived the STOP, this falling edge would report a tHD;STA
           -- spanning the STOP itself, which is not an interval at all.
           scl_in <= '1'; tick(30);
           scl_in <= '0'; tick(30);
           if n_hdlog /= base_hd then
               report "a clock pulse after the STOP closed the abandoned tHD;STA interval"
                   severity error; errs := errs + 1; end if;

           -- 7: the worst cases are TRACKED. A clean run must not erase the earlier violations.
           for i in 1 to 3 loop
               plain_start(300);
               data_bit('1');
               repeated_start(300, 300);
               data_bit('1');
               bus_stop;
           end loop;
           if min_su_sta_seen > to_unsigned(59, TICK_W) then
               report "the tSU;STA worst case was erased" severity error; errs := errs + 1; end if;
           if min_hd_sta_seen > to_unsigned(59, TICK_W) then
               report "the tHD;STA worst case was erased" severity error; errs := errs + 1; end if;

           -- 8: an SDA edge while SCL is LOW is DATA, not framing, so it must produce neither a
           -- framing event nor a margin measurement. This stops the block inventing STARTs inside
           -- a byte.
           nsu_before := n_su_sta; viol_before := n_viol;
           plain_start(200);
           data_bit('0'); data_bit('1'); data_bit('0'); data_bit('1');
           bus_stop;
           if n_su_sta /= nsu_before then
               report "data bits produced tSU;STA measurements" severity error;
               errs := errs + 1; end if;
           if n_viol /= viol_before then
               report "data bits produced violations" severity error; errs := errs + 1; end if;

           -- 9: an IDLE bus measures nothing at all.
           nsu_before := n_su_sta;
           sda_in <= '1'; scl_in <= '1'; tick(600);
           if n_su_sta /= nsu_before then
               report "an idle bus produced measurements" severity error; errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_start_margin_checker self-check complete: tSU;STA is a repeated-start "
                    & "parameter only, the two margins bracket the SDA edge independently, both "
                    & "boundaries exact, a cancelled interval is not measured" severity note;
           else
               report "i2c_start_margin_checker self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

5a. Five Decisions Worth Defending

The setup timer is free-running from SCL's rise; the hold timer is armed by SDA's fall. The reference-edge rule of Chapter 11.3 §2, applied to a pair whose reference edges are on different signals. The setup interval ends at SDA's fall, so it cannot be timed by a counter armed there; the hold interval begins at that same fall, so it can.

One event closes one measurement and opens the other. SDA's fall is the shared endpoint of §3, so the cycle it occurs must both sample the setup counter and arm the hold counter. Doing them in separate cycles loses a tick from one and adds one to the other — invisible except at the boundary, which is why §6's tests 8 and 9 assert the exact minimum and one tick below it.

A START is recognised by its definition, not assumed. SDA must fall while SCL is high. An SDA fall during a low phase is a data bit and must produce no margin measurement at all — §6's test 12, and mutation E4 removes the SCL-high qualification.

tSU;STA is measured only for a REPEATED START. Table 10's row says "repeated START condition" and the design honours it: start_det, restart_det and in_transfer are separate outputs, and a plain START produces a hold measurement with no setup measurement. §2 is the reasoning — a first START's entry requirement is tBUF — and mutation E1 is the version that measures both.

The hold measurement is closed by SCL's fall, and it is cancelled if SDA rises first. An SDA rise while SCL is still high, after a START, is a STOP (Chapter 5.3) — a legal event meaning the transfer was abandoned before its first clock pulse, so no tHD;STA exists to report. The cancellation must also survive what comes next: a clock pulse arriving later must not close the interval retrospectively. §6's test 10 asserts both halves, and §7's E3 is why the second half is needed.

5b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o d5 i2c_start_margin_checker.sv i2c_start_margin_checker_tb.sv && ./d5
   PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge
   independently, both boundaries exact, a cancelled interval is not measured
   i2c_start_margin_checker_tb.sv:247: $finish called at 96780000 (1ps)

   $ iverilog -g2005 -o v5 i2c_start_margin_checker.v i2c_start_margin_checker_tb.v && ./v5
   PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge
   independently, both boundaries exact, a cancelled interval is not measured
   i2c_start_margin_checker_tb.v:258: $finish called at 96780000 (1ps)

   $ nvc -a i2c_start_margin_checker.vhd i2c_start_margin_checker_tb.vhd
   $ nvc -e i2c_start_margin_checker_tb && nvc -r i2c_start_margin_checker_tb --stop-time=2000us
   ** Note: 96780ns+0: i2c_start_margin_checker self-check complete: tSU;STA is a
      repeated-start parameter only, the two margins bracket the SDA edge independently, both
      boundaries exact, a cancelled interval is not measured

All three at 96780 ns.

6. What the Testbench Proves

The block is configured with Fast-mode's values, where the two minima are equal: T_SU_STA_MIN = T_HD_STA_MIN = 60.

#stimuluswhat it establishes
1resetboth worst-case trackers read their maximum
2a plain STARTproduces one tHD;STA and zero tSU;STA — §2's asymmetry
3a 100-tick hold on that plain STARTnot flagged, and measured as 100
4a repeated STARTdoes produce a tSU;STA measurement
5generous margins on a repeated STARTneither verdict fires; the setup measures 120
6a 15-tick setupviolates tSU;STA, and does not flag the hold
7a 20-tick holdviolates tHD;STA, and does not flag the setup
8each margin exactly at the minimumaccepted
9each margin one tick belowrejected — both boundaries pinned from both sides
10a START followed by a STOPproduces no tHD;STA, and a later clock pulse does not close the abandoned interval
11a clean START after short marginsworst cases not erased
12ordinary data bitsproduce no tSU;STA measurements and no violations
13an idle busproduces no measurements

Test 2 and test 4 are the pair that carries §2, and they are the most important tests here. Table 10 calls tSU;STA the set-up time for a repeated START condition, and the design honours that literally: a plain START — one that opens a transfer on a free bus — produces a hold measurement and no setup measurement at all, because the interval before it is tBUF, not tSU;STA. A checker that measured a setup margin for a plain START would be reporting how long the bus happened to be idle, dressed up as a compliance number.

Test 10 is the abandoned START, and it needs two assertions rather than one. An SDA rise while SCL is still high, after a START, is a STOP — the transfer was given up before its first clock pulse, so tHD;STA's closing edge never arrives and there is nothing to report. The second assertion is the one that matters: a clock pulse arriving later must not close the interval retrospectively. Mutation E3 is exactly that, and the first assertion alone does not catch it.

Test 12 is the definition check. Every data bit on the bus involves SDA moving while SCL is low, so a detector missing the SCL-high qualification would announce framing constantly. It is cheap to test and it is the failure that makes a checker useless rather than merely wrong.

7. Mutation Testing

Five defects injected into the SystemVerilog checker.

#injected defectoutcome
E1tSU;STA is measured on a plain START too, reporting idle timekilled — test 2
E2tHD;STA is closed by the next SCL rise rather than the fallkilled — test 3
E3a STOP does not cancel a hold measurement in flightkilled — test 10
E4framing is detected without requiring SCL high across the edgekilled — test 4
E5the setup timer is not armed by SCL being releasedkilled — test 4

Five injected, five killed. Three notes.

E1 is the mutation this chapter's §2 exists for. Measuring tSU;STA on a plain START yields a number — the time since SCL last rose on an idle bus — and that number is usually large, so it passes the minimum and looks like a clean result. Nothing about it is wrong except that the parameter does not apply. It is caught only by a test that asserts a plain START produces zero setup measurements, which is a test you write only if you have read the word "repeated" in the table row.

E2 is the reference-edge mutation, and it inflates rather than deflates. Closing the hold interval at the next rising edge instead of the falling edge measures the hold plus the whole rest of the high phase, so a 100-tick hold reports 150. That direction matters: the mutant never produces a false violation, only false passes, so a suite checking violations alone would miss it entirely. What kills it is test 3 asserting the measured value, not the verdict.

That generalises past this chapter. A checker's measurements must be asserted, not only its verdicts — a mutation that shifts a measurement in the safe direction is invisible to any test that reads only the pass/fail bit.

E3 was a real gap, and it took the second assertion in test 10. The original test checked that an abandoned START produced no hold measurement, and the mutant passed it: at the moment of the STOP, nothing is reported either way. The defect shows up only when a clock pulse arrives afterwards and closes the interval that should have been cancelled. This is the same deferred-effect shape as Chapter 11.3 §8's C2 — when a mutation removes a cancellation, the test has to run past it to the event that would wrongly consume the stale state.

8. Verification Connection — Properties Between Two Signals

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_props.sv — both endpoints are specific edges on specific lines
   // A START is a definition before it is a timing question, so the property begins with the
   // definition. Every framing property in this module has this shape: recognise the event by
   // its transition-while-level signature, then constrain the intervals around it.
   wire start_event = $fell(sda) && scl;

   // SETUP: from SCL's rise to SDA's fall. The interval ENDS at the SDA edge, so -- exactly as
   // in Chapter 11.3 -- it needs a free-running timer sampled there, not a timer armed there.
   int since_scl_rise;
   always_ff @(posedge clk)
      since_scl_rise <= $rose(scl) ? 0 : since_scl_rise + 1;

   property p_su_sta;
      @(posedge clk) start_event |-> (since_scl_rise >= T_SU_STA_MIN);
   endproperty
   assert property (p_su_sta)
      else $error("tSU;STA violated: %0d ticks since SCL rose, minimum %0d",
                  since_scl_rise, T_SU_STA_MIN);

   // HOLD: from SDA's fall to SCL's fall. The interval BEGINS at the SDA edge, so a bounded
   // window works -- and the `or $rose(sda)` disjunct is section 5a's abandoned-START case,
   // which is legal and must not fail the property.
   property p_hd_sta;
      @(posedge clk) start_event |-> (scl [*T_HD_STA_MIN]) or ($rose(sda) throughout scl [*1:$]);
   endproperty
   assert property (p_hd_sta)
      else $error("tHD;STA violated: SCL fell within %0d ticks of the START", T_HD_STA_MIN);

   // The NEGATIVE property, and for this chapter it is the important one: an SDA fall during a
   // LOW phase is a data bit, and reporting a START for it would flag every zero on the bus.
   // Section 6's test 12 is the simulation form of this; mutation E4 is what it catches.
   property p_data_fall_is_not_a_start;
      @(posedge clk) ($fell(sda) && !scl) |-> (!viol_su_sta && !viol_hd_sta);
   endproperty
   assert property (p_data_fall_is_not_a_start)
      else $error("an SDA fall during a LOW phase was treated as a START");
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_start_cov.sv — margins crossed with SPEED MODE, which is the point
   covergroup i2c_start_cg with function sample(int su, int hd, int su_min, int hd_min,
                                               int mode, bit repeated);
      // Margins relative to their own minima, so the bins are reusable across speed grades --
      // the same reasoning as Chapter 11.1 section 9.
      su_margin: coverpoint (su - su_min) {
         bins violation = {[$:-1]}; bins exact = {0};
         bins tight = {[1:10]};     bins ample = {[11:$]};
      }
      hd_margin: coverpoint (hd - hd_min) {
         bins violation = {[$:-1]}; bins exact = {0};
         bins tight = {[1:10]};     bins ample = {[11:$]};
      }

      // THE coverpoint for this chapter. tSU;STA and tHD;STA are equal in Fast and Fast-mode
      // Plus and differ only in Standard-mode, so a suite that never ran Standard-mode cannot
      // have distinguished them -- and this cross is what makes that gap visible in a report
      // instead of invisible in a pass.
      speed: coverpoint mode {
         bins standard = {0};       // the ONLY mode where the two minima differ
         bins fast     = {1};
         bins fm_plus  = {2};
      }
      su_x_speed: cross su_margin, speed;
      hd_x_speed: cross hd_margin, speed;

      // First versus repeated START, because their PRECEDING requirement is a different
      // parameter -- tBUF against tSU;STA, section 2 -- even though the hold is identical.
      kind: coverpoint repeated { bins first = {0}; bins repeated_start = {1}; }
      su_x_kind: cross su_margin, kind;

      // And the two events that must produce NO measurement, each reached by its own path.
      data_phase_fall: coverpoint (su == 0 && hd == 0);
      abandoned:       coverpoint (su > 0 && hd == 0);
   endgroup

9. FPGA and ASIC Implications

Two counters and a small amount of edge-detection logic — around 60 flops at TICK_W = 16. The only subtlety is that the shared endpoint of §3 makes one cycle do two things, which is a fan-out concern rather than a timing one.

On the generating side, a repeated START costs SCL high time. §3's arithmetic: tSU;STA + tHD;STA is 1.2 µs in Fast-mode against a 0.6 µs minimum high phase, so the high phase containing a repeated START is at least twice the normal minimum. A master's state machine therefore cannot treat a repeated START as "a START issued during the transfer" — it has to lengthen the high phase, which means the SCL generator needs a mode where its high-phase terminal count is overridden.

That is a real and commonly missed requirement. A master built with the two terminal counts of Chapter 11.2 §10 still needs a third path for this case, and a master that issues a repeated START inside a normal high phase violates one margin or the other depending on where in the phase it drops SDA.

A repeated START is still much cheaper than a STOP-then-START. tSU;STA at 0.6 µs against tBUF at 1.3 µs in Fast-mode, and the STOP itself costs tSU;STO. §2's table is the comparison; Chapter 10.1 §5 is the protocol consequence.

Sizing note: both parameters are minima with no maxima, so the counters need to tolerate an arbitrarily long interval without wrapping. A master that stretches the high phase around a repeated START — which §9's first point says it must — produces intervals far longer than a data bit's, and a counter sized for a data bit will wrap and report a small number.

10. Debugging — The Repeated START That Worked at 400 kHz

Pitfall — a parameter pair that is only distinguishable in the slowest speed mode
Buggy Code
// A master's framing timer, used for both START margins. One constant, because at the
// design's target speed the two parameters have the same value:
//
//     localparam T_STA = 60;               // 0.6 us at 100 MHz -- Fast-mode
//                                          // tSU;STA(min) = 0.6 us  and  tHD;STA(min) = 0.6 us
//
//     // repeated START: hold SDA high T_STA after SCL rises, drop it, hold SCL high T_STA
//     REP_START_SETUP: if (tick == T_STA) begin sda_drive_low <= 1'b1; state <= REP_START_HOLD; end
//     REP_START_HOLD:  if (tick == T_STA) begin scl_drive_low <= 1'b1; state <= BIT0;          end
//
// At Fast-mode this is correct. Both margins are 0.6 us and both are met exactly.
//
// The design was verified at 400 kHz, and at 1 MHz where the two are also equal (0.26 us).
// Standard-mode was regarded as the easy case and regressed only for functional correctness,
// not for timing -- "if it works at 400 kHz it works at 100".
Symptom

Two years later the part was designed into a system with a long backplane that required Standard-mode. Writes worked. Write-then-read sequences using a repeated START failed on about one device in three -- and on those devices, they failed every time, which is a much stranger pattern than intermittent failure.

The first reading of the capture was that the slave ignored the repeated START: it continued to treat the following byte as write data. That pointed squarely at the slaves, and two different vendors' parts were swapped out with no change. A third vendor's part worked perfectly, which was taken as evidence that the first two were non-compliant.

The tell was that the failing slaves were the OLDER parts, and older parts tend to be more conservative about framing recognition, not less. That inverted the suspicion.

Measuring the master's repeated START in Standard-mode gave 4.0 us of setup and 4.0 us of hold. The hold is correct -- tHD;STA(min) is 4.0 us. The setup is 0.7 us SHORT, because tSU;STA(min) in Standard-mode is 4.7 us, not 4.0.

One constant had been used for two parameters whose values are equal in Fast-mode and Fast-mode Plus and DIFFERENT in Standard-mode. Every test the design had ever passed was run in a mode where the two cannot be told apart.

Root Cause

tSU;STA(min) and tHD;STA(min) are 0.6 us and 0.6 us in Fast-mode, 0.26 and 0.26 in Fast-mode Plus, and 4.7 and 4.0 in Standard-mode. The design used one constant for both, which is correct in two of the three modes and wrong in the third.

The verification gap is the deeper cause. Standard-mode was treated as the easy case on the grounds that everything is slower, and for most parameters that reasoning holds. For a parameter PAIR it inverts: the slowest mode was the only one in which the two parameters were separable, so it was the only mode whose test could have found this.

The three-vendor pattern was a red herring in the usual way -- the slaves that worked had more tolerant framing detectors, which made a compliant master look like the wrong hypothesis.

11. Common Misconceptions

"A repeated START is just a START issued during a transfer." Its setup requirement is a different parameter — tSU;STA rather than tBUF — because the bus was active rather than idle. §2 is the comparison, and in Fast-mode it is 0.6 µs against 1.3 µs.

"tSU;STA and tHD;STA are effectively one parameter." They are equal in Fast-mode and Fast-mode Plus and differ in Standard-mode, 4.7 against 4.0. §10 is two years of that assumption.

"Standard-mode is the easy case to verify." For a parameter pair it is the only mode in which these two are separable, which makes it the mode that must be run — and §6's callout is this chapter's own suite admitting it does not.

"Slower-mode timings can be derived by scaling." Standard-mode's ratio to Fast-mode is 7.83 for tSU;STA and 6.67 for tHD;STA. Use a per-mode table.

"Any SDA fall is a START." Only a fall while SCL is high. A fall during a low phase is a data bit, and a checker missing that qualification reports a START for every zero on the bus.

"A repeated START is free because it avoids the bus-free time." It is cheaper, not free: the two margins together exceed a minimum high phase, so the high phase containing it is at least twice the normal minimum. A master needs a way to lengthen that phase.

"tHD;STA is unrelated to the clock's parameters." It equals tHIGH(min) in all three modes, because recognising a START uses the same input stage for the same length of time as sampling a bit.

12. Reason It Through

Why does tHD;STA(min) equal tHIGH(min) in all three speed modes?

Because after SDA falls, SCL is still high, and the interval before SCL may fall is the remainder of a high phase. Recognising a START takes the same input stage the same time as sampling a data bit, so the specification asks for a full high phase's worth — which is also why the table can say the first clock pulse follows this period.

A design uses one constant for both START margins and passes every Fast-mode and Fm+ test. What has it proved?

Nothing about the pair. The two minima are equal in both those modes, so no stimulus in either can distinguish a correct implementation from a merged one. It has proved the margins are long enough; it has not proved they are the right two margins.

Why is an SDA rise while SCL is high, immediately after a START, legal and yet productive of no tHD;STA measurement?

Because it is a STOP — the transfer was abandoned before its first clock pulse. tHD;STA's interval ends at SCL's fall, which never comes, and the table's boundary ("after this period, the first clock pulse is generated") never arrives. There is nothing to measure, and reporting a violation would flag a legal abort.

A repeated START in Fast-mode needs 0.6 µs of setup and 0.6 µs of hold, both with SCL high. What does that imply for the SCL generator?

That the high phase containing a repeated START must be at least 1.2 µs — twice tHIGH(min). So a master cannot issue a repeated START inside a normal high phase; its SCL generator needs a path that overrides the high-phase terminal count, in addition to the two counts a compliant clock already requires.

Several vendors' slaves fail with one master and one vendor's works. What should you check before concluding the failing parts are non-compliant?

Whether the working part is the more tolerant one. A framing detector with looser margins accepts a marginally non-compliant master, so the part that works may be the lenient one rather than the correct one — and the majority failing is weak evidence against the majority.

13. Understanding Check

14. Summary

These are the first parameters measured between two different signals. tSU;STA runs from SCL's rise to SDA's fall; tHD;STA from SDA's fall to SCL's fall. Neither has a clock edge at both ends, and SDA's fall is the shared endpoint that closes one and opens the other.

A framing event is recognised by a transition, a data bit by a level. That is why the framing margins bracket an edge while the data parameters bracket a level with a gap between them — and why the SCL-high qualification is part of the definition rather than a detail.

tHD;STA(min) equals tHIGH(min) in all three modes, because recognising a START occupies the same input stage for the same time as sampling a bit. The table's note makes the boundary explicit: the first clock pulse follows.

A repeated START's setup requirement is a different parameter from a first START's. tSU;STA against tBUF — 0.6 µs against 1.3 µs in Fast-mode — which is the timing-level form of Chapter 10.2's protocol argument for keeping the bus.

The two margins are equal in Fast-mode and Fast-mode Plus and differ only in Standard-mode. So the slowest grade is the only one in which they are separable, and it is therefore the one that must be regressed — the opposite of the usual instinct. Never use one constant for both, and never scale slower-mode timings from a faster base.

A repeated START costs SCL high time. The two margins exceed twice the minimum high phase, so a master's clock generator needs a path to lengthen it — a requirement beyond the two terminal counts a compliant clock already needs.

A measurement must be asserted, not only a verdict. Mutation E2 shifts the hold measurement in the safe direction — it inflates it — so it never produces a false violation and no verdict-only test can see it.

15. What Comes Next

Chapter 11.6 completes the framing with tSU;STO and tBUF — the STOP margin and the bus-free time that §2 identified as a first START's entry requirement.

tBUF is unlike everything so far in one respect: it is the only parameter in Table 10 measured between two transactions rather than inside one. That makes it the only parameter a single transfer cannot violate, and it creates a measurement problem the design phase had to solve explicitly — the interval is reported by the START that closes it, so the value arrives indexed against the wrong event.

It is also the parameter a busy multi-master bus is most likely to violate, for a reason that has nothing to do with any single master's design.

Continue learning