Skip to content
VLSI Mentor

I²C · Module 11

tSU;STO and tBUF — STOP and Bus-Free Time

The only parameter in Table 10 measured between two transactions rather than inside one — which makes it the only one a single transfer cannot violate, and the one a busy multi-master bus violates most often.

Chapter 11.5 closed with tBUF identified as a first START's entry requirement. This chapter takes it, together with the STOP margin that precedes it — and tBUF turns out to be unlike every other parameter in Table 10 in a way that matters both for how it is measured and for who violates it.

tSU;STO is measured inside a transaction. tBUF is measured between two.

Every other parameter in the module constrains one device's behaviour within one transfer. tBUF constrains the gap between transfers, which means no single transfer can violate it — it takes two, and on a multi-master bus the two may belong to different masters that never coordinated.

The chapter also closes a structural observation that has been accumulating since §1 of Chapter 11.2.

1. The Two Rows

tSU;STO runs from SCL's rising edge to SDA's rising edge — the mirror of tSU;STA, which ran from SCL's rise to SDA's fall. A STOP is SDA rising while SCL is high (Chapter 5.3), so the setup margin is the time the line is held low after the clock has gone high.

Notice what tSU;STO does not have: a hold time. There is no tHD;STO. A START has both a setup and a hold because something follows it — the first clock pulse, which the hold margin protects. Nothing follows a STOP but idleness, and the requirement on that idleness is a separate parameter with a different name: tBUF.

So the STOP has one margin and the START has two, and the asymmetry is structural rather than an omission.

2. The Structural Finding: the Framing Margins Are the Clock Phases

Collect every "SCL is high" requirement in Table 10, and every "the line is at rest" requirement, and put them side by side:

requirementsymbolStdFastFm+
a clock's high phasetHIGH4.00.60.26
a START's holdtHD;STA4.00.60.26
a STOP's setuptSU;STO4.00.60.26
a clock's low phasetLOW4.71.30.5
the bus-free timetBUF4.71.30.5

Three parameters share tHIGH's value in all three modes, and tBUF shares tLOW's in all three modes.

That is five distinct rows of Table 10 carrying two sets of numbers. The framing margins are not independently chosen values — they are the clock's own phase minima, applied to framing events, and the reason is the one Chapter 11.5 §4 gives: recognising a framing event uses the same input stages for the same length of time as sampling a data bit. A START's hold, a STOP's setup and a bit's high phase are all "how long must SCL be high for every device to resolve what it sees", and there is one answer.

tBUF matching tLOW follows the same logic from the other side: a bus-free interval and a low phase are both "how long must the lines be left alone before the next event is unambiguous".

3. tBUF Is the Only Parameter Between Transactions

Every other parameter in Table 10 constrains something within a single transfer, and therefore within one device's control. tBUF does not.

every other parametertBUF
scopeinside one transactionbetween two
can one transfer violate it?yesno — it takes two
who is responsible?the device being measuredwhoever starts second
on a multi-master busone master's probleman emergent property

Three consequences follow, and they are the substance of the chapter.

A single transfer cannot violate tBUF, so no amount of single-transfer verification finds a violation. A directed test that issues one write, checks it, and ends has proved nothing about tBUF. The stimulus has to contain two transactions with a controlled gap, and a suite built around "verify one transaction thoroughly" will not contain one by accident.

The violating master is not necessarily the faulty one. If master A ends a transfer and master B starts one 0.9 µs later in Fast-mode, the violation belongs to B — it started too soon. But B may be perfectly compliant in isolation and simply unaware that A had just finished, which on a bus without arbitration-aware timing is entirely normal. §11 is that case.

It is the parameter a busy bus violates most often. Not because any master is badly designed, but because the probability that two independent masters' idle intervals overlap grows with bus utilisation. A tBUF violation is a traffic phenomenon, and it appears at high load and disappears at low load — which makes it look like a load-dependent electrical problem.

4. The STOP-to-START Sequence, Drawn

tSU;STO inside the transfer, tBUF between two transfers

10 cycles
Ten intervals. SCL is low for the first interval then high for the rest. SDA is low for four intervals, rises during the fifth while SCL is high, stays high for four intervals, then falls again. A region row marks the STOP setup margin from SCL's rise to SDA's rise, the bus-free interval while both lines are released, and the START that closes it.tSU;STO min 0.6 ustSU;STO min 0.6 ustBUF min 1.3 ustBUF min 1.3 usSCL rises: tSU;STO startsSCL rises: tSU;STO startsSDA rises: STOPSDA rises: STOPSDA falls: next STARTSDA falls: next STARTsclsdaregion0STOSTOSTOfreefreefreefreefreeSt0t1t2t3t4t5t6t7t8t9
A STOP and the following START. The STOP margin is measured inside the ending transaction; the bus-free time spans the gap between two transactions and is the only parameter in Table 10 that does.

The figure shows why the two parameters are measured so differently despite sitting next to each other.

tSU;STO's interval is bracketed by two edges that both belong to the ending transaction. It is an ordinary in-transfer margin, measured exactly like Chapter 11.5's.

tBUF's interval is bracketed by the STOP of one transaction and the START of the next. Its closing event is the first event of a different transfer — which creates the measurement problem §5 is about.

5. The Reporting-Lag Problem

Here is a problem tBUF creates that none of the earlier parameters do, and it cost real debugging time in the design phase.

A tBUF measurement is completed by the START that closes the gap. So the value becomes available at the next transaction's first event — not at the STOP that began the interval, and not during the idle time itself.

That is fine for the hardware, which just reports a number when it has one. It is a genuine trap for a testbench, because the natural way to write a self-check is:

Azvya Education Pvt. Ltd.VLSI Mentor
the wrong shape — indexing a measurement against the event that created it
   // Issue a STOP, wait a controlled gap, issue a START. Then check the gap that was measured:
   //
   //     do_stop();
   //     idle(GAP);
   //     do_start();
   //     assert (buf_log[n] == GAP);       // WRONG -- which n?
   //
   // The measurement was produced by do_start(), which also begins the NEXT transaction and
   // may itself log events. Indexing against the STOP's position in the log gives the
   // PREVIOUS gap, or nothing at all on the first iteration.

The fix that worked is a helper whose contract is explicit about where the value lands:

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_checker_tb.sv — the measurement helper, with its contract stated
   // Drive a STOP, an idle gap of `gap` ticks, and the START that closes it. On return the
   // measurement for THIS gap is at buf_log[n_buflog-1], because the closing START is what
   // produced it. Stating that in the helper's contract is the whole fix: every call site then
   // reads the right entry without reasoning about reporting lag.
   //
   // BUF_OVERHEAD is 10 + 30 = 40 ticks: the STOP and START sequences themselves sit inside the
   // measured interval, so the gap the DUT sees is longer than the gap requested. Every boundary
   // test adds it explicitly rather than tolerating a range, because a tolerance would hide the
   // off-by-one this helper exists to prevent.
   task automatic measure_buf(input int gap);
      begin
         do_stop();
         idle_ticks(gap);
         do_start();
         // buf_log[n_buflog-1] now holds this gap's measurement
      end
   endtask

6. The STOP and Bus-Free Checker in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_checker.sv — a margin inside a transaction and an interval between two
   // STOP SETUP AND BUS-FREE TIME: tSU;STO and tBUF.
   //
   //     SCL rises --[ tSU;STO ]--> SDA RISES (the STOP) --[ tBUF ]--> SDA falls (next START)
   //
   // Table 10 names them:
   //     tSU;STO  "set-up time for STOP condition"                min 4.0 / 0.6 / 0.26 us
   //     tBUF     "bus free time between a STOP and START condition"  min 4.7 / 1.3 / 0.5 us
   //
   // tSU;STO is the mirror of tSU;STA from Chapter 11.5: SCL is released, and SDA must wait
   // before moving so that the edge is unambiguously framing rather than a late data bit.
   // Notice it is specified for EVERY stop, not only some -- unlike tSU;STA, which is a
   // repeated-start parameter -- because a STOP always follows a clock pulse, so its setup is
   // never satisfied by the bus having been idle.
   //
   // tBUF IS THE ODD ONE OUT IN THE WHOLE OF TABLE 10, and it is worth being clear about why.
   // Every other parameter constrains something INSIDE a transfer. tBUF constrains the gap
   // BETWEEN two transfers: it is the only entry in the table that spans the interval in which
   // nobody owns the bus. So it is the only parameter a single device can satisfy while still
   // being wrong at the system level -- a master that honours tBUF has waited long enough, and
   // waiting long enough is exactly what gives every other master the opportunity Chapter 10.2
   // measures. tBUF is a floor on the exposure window, not a ceiling.
   //
   // It also has only a MINIMUM. There is no maximum bus-free time, which is why a bus can sit
   // idle for hours and why "the bus is idle" is never by itself a fault.
   //
   // PASSIVE: observes the two wires and drives nothing.
   module i2c_stop_busfree_checker #(
       parameter int TICK_W = 16,
       // Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60, 1.3 us = 130.
       parameter int T_SU_STO_MIN = 60,
       parameter int T_BUF_MIN    = 130
   )(
       input  logic clk,
       input  logic rst_n,
       input  logic sda_in,
       input  logic scl_in,

       // ---- framing, detected here so the block works on a raw capture ----
       output logic start_det,
       output logic stop_det,
       output logic in_transfer,

       // ---- measured ----
       output logic              sto_valid,     // pulse: a tSU;STO has been measured
       output logic [TICK_W-1:0] t_su_sto,
       output logic              buf_valid,     // pulse: a tBUF has been measured
       output logic [TICK_W-1:0] t_buf,

       // ---- verdicts ----
       output logic viol_su_sto,
       output logic viol_buf,

       // ---- totals and worst cases ----
       output logic [TICK_W-1:0] n_su_sto,
       output logic [TICK_W-1:0] n_buf,
       output logic [TICK_W-1:0] n_viol,
       output logic [TICK_W-1:0] min_su_sto_seen,
       output logic [TICK_W-1:0] min_buf_seen
   );
       logic sda_q, scl_q;
       logic scl_rise, sda_fall, sda_rise, scl_stable_high;
       assign scl_rise        = !scl_q &&  scl_in;
       assign sda_fall        =  sda_q && !sda_in;
       assign sda_rise        = !sda_q &&  sda_in;
       assign scl_stable_high = scl_q && scl_in;

       // tSU;STO: armed by SCL rising, stopped by the SDA rise that makes the STOP.
       logic              sto_arm;
       logic [TICK_W-1:0] sto_ticks;
       logic [TICK_W-1:0] sto_now;
       assign sto_now = sto_ticks + 1'b1;

       // tBUF: armed by the STOP, stopped by the next START. This timer runs while the bus
       // belongs to nobody, which is what makes it the only inter-transfer parameter.
       logic              buf_arm;
       logic [TICK_W-1:0] buf_ticks;
       logic [TICK_W-1:0] buf_now;
       assign buf_now = buf_ticks + 1'b1;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               sda_q           <= 1'b1;
               scl_q           <= 1'b1;
               sto_arm         <= 1'b0;
               sto_ticks       <= '0;
               buf_arm         <= 1'b0;
               buf_ticks       <= '0;
               start_det       <= 1'b0;
               stop_det        <= 1'b0;
               in_transfer     <= 1'b0;
               sto_valid       <= 1'b0;
               t_su_sto        <= '0;
               buf_valid       <= 1'b0;
               t_buf           <= '0;
               viol_su_sto     <= 1'b0;
               viol_buf        <= 1'b0;
               n_su_sto        <= '0;
               n_buf           <= '0;
               n_viol          <= '0;
               min_su_sto_seen <= {TICK_W{1'b1}};
               min_buf_seen    <= {TICK_W{1'b1}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               start_det <= 1'b0;
               stop_det  <= 1'b0;
               sto_valid <= 1'b0;
               buf_valid <= 1'b0;

               // ---- the STOP setup timer, armed whenever SCL is released ----
               if (scl_rise) begin
                   sto_arm   <= 1'b1;
                   sto_ticks <= '0;
               end else if (sto_arm && !(sda_rise && scl_stable_high)) begin
                   sto_ticks <= sto_now;
               end

               if (sda_rise && scl_stable_high) begin
                   // A STOP. It closes tSU;STO and opens tBUF.
                   stop_det    <= 1'b1;
                   in_transfer <= 1'b0;
                   sto_arm     <= 1'b0;

                   if (sto_arm) begin
                       sto_valid   <= 1'b1;
                       t_su_sto    <= sto_now;
                       viol_su_sto <= (sto_now < T_SU_STO_MIN[TICK_W-1:0]);
                       n_su_sto    <= n_su_sto + 1'b1;
                       if (sto_now < T_SU_STO_MIN[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
                       if (sto_now < min_su_sto_seen) min_su_sto_seen <= sto_now;
                   end

                   buf_arm   <= 1'b1;
                   buf_ticks <= '0;
               end else if (sda_fall && scl_stable_high) begin
                   // A START. If a bus-free interval was being timed, this closes it.
                   start_det   <= 1'b1;
                   in_transfer <= 1'b1;
                   sto_arm     <= 1'b0;

                   if (buf_arm) begin
                       buf_arm   <= 1'b0;
                       buf_valid <= 1'b1;
                       t_buf     <= buf_now;
                       viol_buf  <= (buf_now < T_BUF_MIN[TICK_W-1:0]);
                       n_buf     <= n_buf + 1'b1;
                       if (buf_now < T_BUF_MIN[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
                       if (buf_now < min_buf_seen) min_buf_seen <= buf_now;
                   end
               end else if (buf_arm) begin
                   // The bus-free timer keeps running for as long as the bus stays free.
                   // There is no maximum, so it must not be capped or cancelled -- a bus that
                   // has been idle for an hour is not in violation of anything, and a design
                   // that stopped timing would report a wrong tBUF for the eventual START.
                   buf_ticks <= buf_now;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_checker_tb.sv — twelve scenarios, including the reporting-lag problem
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tSU;STO = 0.6 us = 60 ticks, tBUF = 1.3 us = 130 ticks.
   module i2c_stop_busfree_checker_tb;
       localparam int TICK_W       = 16;
       localparam int T_SU_STO_MIN = 60;
       localparam int T_BUF_MIN    = 130;

       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, stop_det, in_transfer;
       logic sto_valid, buf_valid, viol_su_sto, viol_buf;
       logic [TICK_W-1:0] t_su_sto, t_buf, n_su_sto, n_buf, n_viol;
       logic [TICK_W-1:0] min_su_sto_seen, min_buf_seen;

       int errors = 0;
       int base_sto, base_buf;
       logic [TICK_W-1:0] nbuf_before, viol_before;

       i2c_stop_busfree_checker #(.TICK_W(TICK_W), .T_SU_STO_MIN(T_SU_STO_MIN),
           .T_BUF_MIN(T_BUF_MIN)) dut (.*);

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

       logic [TICK_W-1:0] sto_log [0:31];
       logic vsto_log [0:31];
       int n_stolog;
       always @(posedge clk) if (rst_n && sto_valid && n_stolog < 32) begin
           sto_log[n_stolog] = t_su_sto; vsto_log[n_stolog] = viol_su_sto; n_stolog++;
       end

       logic [TICK_W-1:0] buf_log [0:31];
       logic vbuf_log [0:31];
       int n_buflog;
       always @(posedge clk) if (rst_n && buf_valid && n_buflog < 32) begin
           buf_log[n_buflog] = t_buf; vbuf_log[n_buflog] = viol_buf; n_buflog++;
       end

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

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

       // A STOP whose SETUP is settable: SCL is released, `su` ticks pass, then SDA rises.
       task automatic bus_stop(input int su);
           begin
               sda_in = 1'b0; scl_in = 1'b0; tick(20);
               scl_in = 1'b1;                tick(su);   // tSU;STO starts here
               sda_in = 1'b1;                tick(10);   // the STOP; tBUF starts here
           end
       endtask

       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

       // A complete transfer with a settable STOP setup, followed by a settable bus-free gap
       // before the next START.
       task automatic transfer(input int su_sto, input int gap);
           begin
               bus_start();
               data_bit(1'b1);
               bus_stop(su_sto);
               tick(gap);                   // the bus-free interval
           end
       endtask

       // Measure EXACTLY one bus-free interval of a known length, leaving its measurement as
       // the LAST entry in the log.
       //
       // This helper exists because tBUF is reported by the START that CLOSES the gap, so a
       // measurement always belongs to the interval BEFORE the transfer that reports it.
       // Indexing it relative to the transfer that created the gap is off by one -- which it
       // was, before these tests were rewritten, and the symptom was a boundary test that
       // measured a previous test's trailing gap instead of its own.
       //
       // The measured interval is `gap` plus the 10 trailing ticks of bus_stop and the 30
       // leading ticks of bus_start, so callers ask for T_BUF - 40 to land on the boundary.
       localparam int BUF_OVERHEAD = 10 + 30;
       task automatic measure_buf(input int gap);
           begin
               bus_start();                  // closes any interval still pending
               data_bit(1'b1);
               bus_stop(120);                // opens the interval under test
               tick(gap);
               bus_start();                  // closes it: this is the measurement we want
               data_bit(1'b1);
               bus_stop(120);
           end
       endtask

       initial begin
           tick(3);
           if (min_su_sto_seen !== {TICK_W{1'b1}} || min_buf_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: two LEGAL transfers with generous margins. The first transfer's tBUF is
           //      closed by the SECOND transfer's START, so two transfers give one tBUF
           //      measurement -- a detail worth stating because an off-by-one here would make
           //      every report miss the last gap.
           transfer(120, 300);
           transfer(120, 300);
           if (n_stolog < 2) begin
               $display("FAIL: %0d tSU;STO measurements from two stops, expected 2", n_stolog);
               errors++; end
           if (n_buflog < 1) begin
               $display("FAIL: %0d tBUF measurements from two transfers, expected at least 1",
                        n_buflog); errors++; end
           if (n_viol !== '0) begin
               $display("FAIL: %0d legal margins flagged", n_viol); errors++; end
           if (sto_log[0] < 16'd115 || sto_log[0] > 16'd125) begin
               $display("FAIL: a 120-tick tSU;STO measured %0d", sto_log[0]); errors++; end

           // ---- 2: tSU;STO too SHORT. SCL released and SDA up almost at once, so the edge
           //      is not unambiguously a STOP. Only the STOP verdict may fire -- checked by a
           //      DELTA on the tBUF count, because tBUF measurements are reported by a later
           //      START and cannot be indexed relative to the transfer that caused them.
           begin
               base_sto = n_stolog; nbuf_before = n_buf;
               transfer(15, 300);
               transfer(120, 300);
               if (vsto_log[base_sto] !== 1'b1) begin
                   $display("FAIL: a 15-tick tSU;STO was not flagged (min %0d)", T_SU_STO_MIN);
                   errors++; end
           end

           // ---- 3: tBUF too SHORT. The next START comes too soon after the STOP. This is
           //      the case a master causes by re-claiming the bus greedily, and it is the one
           //      parameter whose violation harms OTHER masters rather than this transfer.
           //      Measured with the helper so the index is unambiguous.
           begin
               base_sto = n_stolog;
               measure_buf(20);
               if (vbuf_log[n_buflog - 1] !== 1'b1) begin
                   $display("FAIL: a short bus-free gap measured %0d and was not flagged (min %0d)",
                            buf_log[n_buflog - 1], T_BUF_MIN); errors++; end
               if (buf_log[n_buflog - 1] !== 16'd20 + BUF_OVERHEAD[TICK_W-1:0]) begin
                   $display("FAIL: a 20-tick gap measured %0d, expected %0d",
                            buf_log[n_buflog - 1], 20 + BUF_OVERHEAD); errors++; end
               if (vsto_log[base_sto] !== 1'b0) begin
                   $display("FAIL: a short tBUF also flagged tSU;STO"); errors++; end
           end

           // ---- 4: BOUNDARIES. Exactly at each minimum is legal. The stimulus subtracts the
           //      helper's own overhead so the measurement lands exactly on the boundary --
           //      a boundary test that does not actually sit on the boundary proves nothing,
           //      so the measured value is asserted as well as the verdict.
           measure_buf(T_BUF_MIN - BUF_OVERHEAD);
           if (buf_log[n_buflog - 1] !== T_BUF_MIN[TICK_W-1:0]) begin
               $display("FAIL: the boundary stimulus measured tBUF = %0d, wanted exactly %0d",
                        buf_log[n_buflog - 1], T_BUF_MIN); errors++; end
           if (vbuf_log[n_buflog - 1] !== 1'b0) begin
               $display("FAIL: tBUF exactly at the minimum was rejected"); errors++; end
           begin
               base_sto = n_stolog;
               transfer(T_SU_STO_MIN, 300);
               transfer(120, 300);
               if (vsto_log[base_sto] !== 1'b0) begin
                   $display("FAIL: tSU;STO exactly at the minimum was rejected"); errors++; end
           end

           // ---- 5: one tick BELOW each minimum must fail.
           measure_buf(T_BUF_MIN - BUF_OVERHEAD - 1);
           if (buf_log[n_buflog - 1] !== T_BUF_MIN[TICK_W-1:0] - 16'd1) begin
               $display("FAIL: the below-boundary stimulus measured tBUF = %0d, wanted %0d",
                        buf_log[n_buflog - 1], T_BUF_MIN - 1); errors++; end
           if (vbuf_log[n_buflog - 1] !== 1'b1) begin
               $display("FAIL: tBUF one tick below the minimum was accepted"); errors++; end
           begin
               base_sto = n_stolog;
               transfer(T_SU_STO_MIN - 1, 300);
               transfer(120, 300);
               if (vsto_log[base_sto] !== 1'b1) begin
                   $display("FAIL: tSU;STO one tick below the minimum was accepted"); errors++; end
           end

           // ---- 6: tBUF has NO MAXIMUM. A very long idle gap is legal, and the timer must
           //      keep running rather than being capped -- a design that stopped timing would
           //      report a wrong tBUF for the eventual START.
           begin
               viol_before = n_viol;
               measure_buf(5000);
               if (n_viol !== viol_before) begin
                   $display("FAIL: a long idle gap was flagged -- tBUF has no maximum"); errors++; end
               if (buf_log[n_buflog - 1] < 16'd5000) begin
                   $display("FAIL: a 5000-tick gap measured only %0d -- the timer was capped",
                            buf_log[n_buflog - 1]); errors++; end
               // The VERDICT must be clean too, not merely the count. tBUF has no maximum, so a
               // design that added an upper bound would set this bit while leaving the violation
               // total alone -- and a checker whose verdict and count disagree is worse than
               // either being wrong.
               if (vbuf_log[n_buflog - 1] !== 1'b0) begin
                   $display("FAIL: a long idle gap set viol_buf -- tBUF has no MAXIMUM");
                   errors++; end
           end

           // ---- 7: a REPEATED START produces NO tBUF measurement, because the bus was never
           //      free. This is Chapter 10.2's distinction appearing as a timing consequence:
           //      an Sr has no bus-free interval, so a block that measured one would report a
           //      zero-length tBUF and flag every combined transaction.
           begin
               bus_start();                          // closes any pending interval first
               nbuf_before = n_buf; viol_before = n_viol;
               data_bit(1'b1);
               // a repeated START: SDA falls while SCL is high, with no STOP in between
               scl_in = 1'b0; sda_in = 1'b1; tick(30);
               scl_in = 1'b1;                tick(80);
               sda_in = 1'b0;                tick(40);
               scl_in = 1'b0;                tick(20);
               data_bit(1'b1);
               bus_stop(120);
               if (n_buf !== nbuf_before) begin
                   $display("FAIL: a repeated START produced %0d tBUF measurements",
                            n_buf - nbuf_before); errors++; end
               if (n_viol !== viol_before) begin
                   $display("FAIL: a repeated START produced %0d violations",
                            n_viol - viol_before); errors++; end
               tick(300);
           end

           // ---- 8: the worst cases are TRACKED across a clean run.
           begin
               repeat (3) transfer(400, 900);
               if (min_su_sto_seen > 16'd59 || min_buf_seen > 16'd129) begin
                   $display("FAIL: worst cases erased -- min_sto %0d, min_buf %0d",
                            min_su_sto_seen, min_buf_seen); errors++; end
           end

           // ---- 9: an IDLE bus with no framing measures nothing, and in particular must not
           //      report a tBUF for a gap that no START ever closed.
           begin
               nbuf_before = n_buf;
               sda_in = 1'b1; scl_in = 1'b1; tick(1000);
               if (n_buf !== nbuf_before) begin
                   $display("FAIL: an unterminated idle gap produced a tBUF measurement");
                   errors++; end
           end

           if (errors == 0)
               $display("PASS: tSU;STO mirrors tSU;STA, tBUF spans the interval nobody owns and has no maximum, a repeated START measures no bus-free time");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_checker.v — the same checker in Verilog-2001
   // STOP SETUP AND BUS-FREE TIME: tSU;STO and tBUF.
   //
   //     SCL rises --[ tSU;STO ]--> SDA RISES (the STOP) --[ tBUF ]--> SDA falls (next START)
   //
   // Table 10 names them:
   //     tSU;STO  "set-up time for STOP condition"                min 4.0 / 0.6 / 0.26 us
   //     tBUF     "bus free time between a STOP and START condition"  min 4.7 / 1.3 / 0.5 us
   //
   // tSU;STO is the mirror of tSU;STA from Chapter 11.5: SCL is released, and SDA must wait
   // before moving so that the edge is unambiguously framing rather than a late data bit.
   // Notice it is specified for EVERY stop, not only some -- unlike tSU;STA, which is a
   // repeated-start parameter -- because a STOP always follows a clock pulse, so its setup is
   // never satisfied by the bus having been idle.
   //
   // tBUF IS THE ODD ONE OUT IN THE WHOLE OF TABLE 10, and it is worth being clear about why.
   // Every other parameter constrains something INSIDE a transfer. tBUF constrains the gap
   // BETWEEN two transfers: it is the only entry in the table that spans the interval in which
   // nobody owns the bus. So it is the only parameter a single device can satisfy while still
   // being wrong at the system level -- a master that honours tBUF has waited long enough, and
   // waiting long enough is exactly what gives every other master the opportunity Chapter 10.2
   // measures. tBUF is a floor on the exposure window, not a ceiling.
   //
   // It also has only a MINIMUM. There is no maximum bus-free time, which is why a bus can sit
   // idle for hours and why "the bus is idle" is never by itself a fault.
   //
   // PASSIVE: observes the two wires and drives nothing.
   // (Verilog-2001)
   module i2c_stop_busfree_checker #(
       parameter TICK_W = 16,
       // Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60, 1.3 us = 130.
       parameter T_SU_STO_MIN = 60,
       parameter T_BUF_MIN    = 130
   )(
       input  wire  clk,
       input  wire  rst_n,
       input  wire  sda_in,
       input  wire  scl_in,

       // ---- framing, detected here so the block works on a raw capture ----
       output reg   start_det,
       output reg   stop_det,
       output reg   in_transfer,

       // ---- measured ----
       output reg                sto_valid,     // pulse: a tSU;STO has been measured
       output reg   [TICK_W-1:0] t_su_sto,
       output reg                buf_valid,     // pulse: a tBUF has been measured
       output reg   [TICK_W-1:0] t_buf,

       // ---- verdicts ----
       output reg   viol_su_sto,
       output reg   viol_buf,

       // ---- totals and worst cases ----
       output reg   [TICK_W-1:0] n_su_sto,
       output reg   [TICK_W-1:0] n_buf,
       output reg   [TICK_W-1:0] n_viol,
       output reg   [TICK_W-1:0] min_su_sto_seen,
       output reg   [TICK_W-1:0] min_buf_seen
   );
       reg sda_q, scl_q;
       wire scl_rise, sda_fall, sda_rise, scl_stable_high;
       assign scl_rise        = !scl_q &&  scl_in;
       assign sda_fall        =  sda_q && !sda_in;
       assign sda_rise        = !sda_q &&  sda_in;
       assign scl_stable_high = scl_q && scl_in;

       // tSU;STO: armed by SCL rising, stopped by the SDA rise that makes the STOP.
       reg              sto_arm;
       reg [TICK_W-1:0] sto_ticks;
       wire [TICK_W-1:0] sto_now;
       assign sto_now = sto_ticks + 1'b1;

       // tBUF: armed by the STOP, stopped by the next START. This timer runs while the bus
       // belongs to nobody, which is what makes it the only inter-transfer parameter.
       reg              buf_arm;
       reg [TICK_W-1:0] buf_ticks;
       wire [TICK_W-1:0] buf_now;
       assign buf_now = buf_ticks + 1'b1;

       always @(posedge clk) begin
           if (!rst_n) begin
               sda_q           <= 1'b1;
               scl_q           <= 1'b1;
               sto_arm         <= 1'b0;
               sto_ticks       <= {TICK_W{1'b0}};
               buf_arm         <= 1'b0;
               buf_ticks       <= {TICK_W{1'b0}};
               start_det       <= 1'b0;
               stop_det        <= 1'b0;
               in_transfer     <= 1'b0;
               sto_valid       <= 1'b0;
               t_su_sto        <= {TICK_W{1'b0}};
               buf_valid       <= 1'b0;
               t_buf           <= {TICK_W{1'b0}};
               viol_su_sto     <= 1'b0;
               viol_buf        <= 1'b0;
               n_su_sto        <= {TICK_W{1'b0}};
               n_buf           <= {TICK_W{1'b0}};
               n_viol          <= {TICK_W{1'b0}};
               min_su_sto_seen <= {TICK_W{1'b1}};
               min_buf_seen    <= {TICK_W{1'b1}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               start_det <= 1'b0;
               stop_det  <= 1'b0;
               sto_valid <= 1'b0;
               buf_valid <= 1'b0;

               // ---- the STOP setup timer, armed whenever SCL is released ----
               if (scl_rise) begin
                   sto_arm   <= 1'b1;
                   sto_ticks <= {TICK_W{1'b0}};
               end else if (sto_arm && !(sda_rise && scl_stable_high)) begin
                   sto_ticks <= sto_now;
               end

               if (sda_rise && scl_stable_high) begin
                   // A STOP. It closes tSU;STO and opens tBUF.
                   stop_det    <= 1'b1;
                   in_transfer <= 1'b0;
                   sto_arm     <= 1'b0;

                   if (sto_arm) begin
                       sto_valid   <= 1'b1;
                       t_su_sto    <= sto_now;
                       viol_su_sto <= (sto_now < T_SU_STO_MIN);
                       n_su_sto    <= n_su_sto + 1'b1;
                       if (sto_now < T_SU_STO_MIN) n_viol <= n_viol + 1'b1;
                       if (sto_now < min_su_sto_seen) min_su_sto_seen <= sto_now;
                   end

                   buf_arm   <= 1'b1;
                   buf_ticks <= {TICK_W{1'b0}};
               end else if (sda_fall && scl_stable_high) begin
                   // A START. If a bus-free interval was being timed, this closes it.
                   start_det   <= 1'b1;
                   in_transfer <= 1'b1;
                   sto_arm     <= 1'b0;

                   if (buf_arm) begin
                       buf_arm   <= 1'b0;
                       buf_valid <= 1'b1;
                       t_buf     <= buf_now;
                       viol_buf  <= (buf_now < T_BUF_MIN);
                       n_buf     <= n_buf + 1'b1;
                       if (buf_now < T_BUF_MIN) n_viol <= n_viol + 1'b1;
                       if (buf_now < min_buf_seen) min_buf_seen <= buf_now;
                   end
               end else if (buf_arm) begin
                   // The bus-free timer keeps running for as long as the bus stays free.
                   // There is no maximum, so it must not be capped or cancelled -- a bus that
                   // has been idle for an hour is not in violation of anything, and a design
                   // that stopped timing would report a wrong tBUF for the eventual START.
                   buf_ticks <= buf_now;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_checker_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tSU;STO = 0.6 us = 60 ticks, tBUF = 1.3 us = 130 ticks.
   module i2c_stop_busfree_checker_tb;   // Verilog-2001
       localparam TICK_W       = 16;
       localparam T_SU_STO_MIN = 60;
       localparam T_BUF_MIN    = 130;

       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, stop_det, in_transfer;
       wire sto_valid, buf_valid, viol_su_sto, viol_buf;
       wire [TICK_W-1:0] t_su_sto, t_buf, n_su_sto, n_buf, n_viol;
       wire [TICK_W-1:0] min_su_sto_seen, min_buf_seen;

       integer errors = 0;
       integer base_sto = 0, base_buf = 0;
       reg [TICK_W-1:0] nbuf_before, viol_before;

       i2c_stop_busfree_checker #(.TICK_W(TICK_W), .T_SU_STO_MIN(T_SU_STO_MIN),
           .T_BUF_MIN(T_BUF_MIN)) dut (
           .clk(clk), .rst_n(rst_n), .sda_in(sda_in), .scl_in(scl_in), .start_det(start_det),
           .stop_det(stop_det), .in_transfer(in_transfer), .sto_valid(sto_valid),
           .t_su_sto(t_su_sto), .buf_valid(buf_valid), .t_buf(t_buf), .viol_su_sto(viol_su_sto),
           .viol_buf(viol_buf), .n_su_sto(n_su_sto), .n_buf(n_buf), .n_viol(n_viol),
           .min_su_sto_seen(min_su_sto_seen), .min_buf_seen(min_buf_seen));

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

       reg [TICK_W-1:0] sto_log [0:31];
       reg vsto_log [0:31];
       integer n_stolog = 0;
       always @(posedge clk) if (rst_n && sto_valid && n_stolog < 32) begin
           sto_log[n_stolog] = t_su_sto; vsto_log[n_stolog] = viol_su_sto; n_stolog = n_stolog + 1;
       end

       reg [TICK_W-1:0] buf_log [0:31];
       reg vbuf_log [0:31];
       integer n_buflog = 0;
       always @(posedge clk) if (rst_n && buf_valid && n_buflog < 32) begin
           buf_log[n_buflog] = t_buf; vbuf_log[n_buflog] = viol_buf; n_buflog = n_buflog + 1;
       end

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

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

       // A STOP whose SETUP is settable: SCL is released, `su` ticks pass, then SDA rises.
       task bus_stop;
           input integer su;
       begin
               sda_in = 1'b0; scl_in = 1'b0; tick(20);
               scl_in = 1'b1;                tick(su);   // tSU;STO starts here
               sda_in = 1'b1;                tick(10);   // the STOP; tBUF starts here
               end
       endtask

       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

       // A complete transfer with a settable STOP setup, followed by a settable bus-free gap
       // before the next START.
       task transfer;
           input integer su_sto;
           input integer gap;
       begin
               bus_start;
               data_bit(1'b1);
               bus_stop(su_sto);
               tick(gap);                   // the bus-free interval
               end
       endtask

       // Measure EXACTLY one bus-free interval of a known length, leaving its measurement as
       // the LAST entry in the log.
       //
       // This helper exists because tBUF is reported by the START that CLOSES the gap, so a
       // measurement always belongs to the interval BEFORE the transfer that reports it.
       // Indexing it relative to the transfer that created the gap is off by one -- which it
       // was, before these tests were rewritten, and the symptom was a boundary test that
       // measured a previous test's trailing gap instead of its own.
       //
       // The measured interval is `gap` plus the 10 trailing ticks of bus_stop and the 30
       // leading ticks of bus_start, so callers ask for T_BUF - 40 to land on the boundary.
       localparam BUF_OVERHEAD = 10 + 30;
       task measure_buf;
           input integer gap;
       begin
               bus_start;                  // closes any interval still pending
               data_bit(1'b1);
               bus_stop(120);                // opens the interval under test
               tick(gap);
               bus_start;                  // closes it: this is the measurement we want
               data_bit(1'b1);
               bus_stop(120);
               end
       endtask

       initial begin
           tick(3);
           if (min_su_sto_seen !== {TICK_W{1'b1}} || min_buf_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: two LEGAL transfers with generous margins. The first transfer's tBUF is
           //      closed by the SECOND transfer's START, so two transfers give one tBUF
           //      measurement -- a detail worth stating because an off-by-one here would make
           //      every report miss the last gap.
           transfer(120, 300);
           transfer(120, 300);
           if (n_stolog < 2) begin
               $display("FAIL: %0d tSU;STO measurements from two stops, expected 2", n_stolog);
               errors = errors + 1; end
           if (n_buflog < 1) begin
               $display("FAIL: %0d tBUF measurements from two transfers, expected at least 1",
                        n_buflog); errors = errors + 1; end
           if (n_viol !== {TICK_W{1'b0}}) begin
               $display("FAIL: %0d legal margins flagged", n_viol); errors = errors + 1; end
           if (sto_log[0] < 16'd115 || sto_log[0] > 16'd125) begin
               $display("FAIL: a 120-tick tSU;STO measured %0d", sto_log[0]); errors = errors + 1; end

           // ---- 2: tSU;STO too SHORT. SCL released and SDA up almost at once, so the edge
           //      is not unambiguously a STOP. Only the STOP verdict may fire -- checked by a
           //      DELTA on the tBUF count, because tBUF measurements are reported by a later
           //      START and cannot be indexed relative to the transfer that caused them.
           begin
               base_sto = n_stolog; nbuf_before = n_buf;
               transfer(15, 300);
               transfer(120, 300);
               if (vsto_log[base_sto] !== 1'b1) begin
                   $display("FAIL: a 15-tick tSU;STO was not flagged (min %0d)", T_SU_STO_MIN);
                   errors = errors + 1; end
           end

           // ---- 3: tBUF too SHORT. The next START comes too soon after the STOP. This is
           //      the case a master causes by re-claiming the bus greedily, and it is the one
           //      parameter whose violation harms OTHER masters rather than this transfer.
           //      Measured with the helper so the index is unambiguous.
           begin
               base_sto = n_stolog;
               measure_buf(20);
               if (vbuf_log[n_buflog - 1] !== 1'b1) begin
                   $display("FAIL: a short bus-free gap measured %0d and was not flagged (min %0d)",
                            buf_log[n_buflog - 1], T_BUF_MIN); errors = errors + 1; end
               if (buf_log[n_buflog - 1] !== 16'd20 + BUF_OVERHEAD) begin
                   $display("FAIL: a 20-tick gap measured %0d, expected %0d",
                            buf_log[n_buflog - 1], 20 + BUF_OVERHEAD); errors = errors + 1; end
               if (vsto_log[base_sto] !== 1'b0) begin
                   $display("FAIL: a short tBUF also flagged tSU;STO"); errors = errors + 1; end
           end

           // ---- 4: BOUNDARIES. Exactly at each minimum is legal. The stimulus subtracts the
           //      helper's own overhead so the measurement lands exactly on the boundary --
           //      a boundary test that does not actually sit on the boundary proves nothing,
           //      so the measured value is asserted as well as the verdict.
           measure_buf(T_BUF_MIN - BUF_OVERHEAD);
           if (buf_log[n_buflog - 1] !== T_BUF_MIN) begin
               $display("FAIL: the boundary stimulus measured tBUF = %0d, wanted exactly %0d",
                        buf_log[n_buflog - 1], T_BUF_MIN); errors = errors + 1; end
           if (vbuf_log[n_buflog - 1] !== 1'b0) begin
               $display("FAIL: tBUF exactly at the minimum was rejected"); errors = errors + 1; end
           begin
               base_sto = n_stolog;
               transfer(T_SU_STO_MIN, 300);
               transfer(120, 300);
               if (vsto_log[base_sto] !== 1'b0) begin
                   $display("FAIL: tSU;STO exactly at the minimum was rejected"); errors = errors + 1; end
           end

           // ---- 5: one tick BELOW each minimum must fail.
           measure_buf(T_BUF_MIN - BUF_OVERHEAD - 1);
           if (buf_log[n_buflog - 1] !== T_BUF_MIN - 16'd1) begin
               $display("FAIL: the below-boundary stimulus measured tBUF = %0d, wanted %0d",
                        buf_log[n_buflog - 1], T_BUF_MIN - 1); errors = errors + 1; end
           if (vbuf_log[n_buflog - 1] !== 1'b1) begin
               $display("FAIL: tBUF one tick below the minimum was accepted"); errors = errors + 1; end
           begin
               base_sto = n_stolog;
               transfer(T_SU_STO_MIN - 1, 300);
               transfer(120, 300);
               if (vsto_log[base_sto] !== 1'b1) begin
                   $display("FAIL: tSU;STO one tick below the minimum was accepted"); errors = errors + 1; end
           end

           // ---- 6: tBUF has NO MAXIMUM. A very long idle gap is legal, and the timer must
           //      keep running rather than being capped -- a design that stopped timing would
           //      report a wrong tBUF for the eventual START.
           begin
               viol_before = n_viol;
               measure_buf(5000);
               if (n_viol !== viol_before) begin
                   $display("FAIL: a long idle gap was flagged -- tBUF has no maximum"); errors = errors + 1; end
               if (buf_log[n_buflog - 1] < 16'd5000) begin
                   $display("FAIL: a 5000-tick gap measured only %0d -- the timer was capped",
                            buf_log[n_buflog - 1]); errors = errors + 1; end
               // The VERDICT must be clean too, not merely the count. tBUF has no maximum, so a
               // design that added an upper bound would set this bit while leaving the violation
               // total alone -- and a checker whose verdict and count disagree is worse than
               // either being wrong.
               if (vbuf_log[n_buflog - 1] !== 1'b0) begin
                   $display("FAIL: a long idle gap set viol_buf -- tBUF has no MAXIMUM");
                   errors = errors + 1; end
           end

           // ---- 7: a REPEATED START produces NO tBUF measurement, because the bus was never
           //      free. This is Chapter 10.2's distinction appearing as a timing consequence:
           //      an Sr has no bus-free interval, so a block that measured one would report a
           //      zero-length tBUF and flag every combined transaction.
           begin
               bus_start;                          // closes any pending interval first
               nbuf_before = n_buf; viol_before = n_viol;
               data_bit(1'b1);
               // a repeated START: SDA falls while SCL is high, with no STOP in between
               scl_in = 1'b0; sda_in = 1'b1; tick(30);
               scl_in = 1'b1;                tick(80);
               sda_in = 1'b0;                tick(40);
               scl_in = 1'b0;                tick(20);
               data_bit(1'b1);
               bus_stop(120);
               if (n_buf !== nbuf_before) begin
                   $display("FAIL: a repeated START produced %0d tBUF measurements",
                            n_buf - nbuf_before); errors = errors + 1; end
               if (n_viol !== viol_before) begin
                   $display("FAIL: a repeated START produced %0d violations",
                            n_viol - viol_before); errors = errors + 1; end
               tick(300);
           end

           // ---- 8: the worst cases are TRACKED across a clean run.
           begin
               repeat (3) transfer(400, 900);
               if (min_su_sto_seen > 16'd59 || min_buf_seen > 16'd129) begin
                   $display("FAIL: worst cases erased -- min_sto %0d, min_buf %0d",
                            min_su_sto_seen, min_buf_seen); errors = errors + 1; end
           end

           // ---- 9: an IDLE bus with no framing measures nothing, and in particular must not
           //      report a tBUF for a gap that no START ever closed.
           begin
               nbuf_before = n_buf;
               sda_in = 1'b1; scl_in = 1'b1; tick(1000);
               if (n_buf !== nbuf_before) begin
                   $display("FAIL: an unterminated idle gap produced a tBUF measurement");
                   errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: tSU;STO mirrors tSU;STA, tBUF spans the interval nobody owns and has no maximum, a repeated START measures no bus-free time");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_checker.vhd — the same checker in VHDL
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- STOP SETUP AND BUS-FREE TIME: tSU;STO and tBUF.
   --
   --     SCL rises --[ tSU;STO ]--> SDA RISES (the STOP) --[ tBUF ]--> SDA falls (next START)
   --
   -- Table 10 names them:
   --     tSU;STO  "set-up time for STOP condition"                   min 4.0 / 0.6 / 0.26 us
   --     tBUF     "bus free time between a STOP and START condition" min 4.7 / 1.3 / 0.5  us
   --
   -- tSU;STO is the mirror of tSU;STA from Chapter 11.5: SCL is released, and SDA must wait before
   -- moving so the edge is unambiguously framing rather than a late data bit. Notice it is
   -- specified for EVERY stop, not only some -- unlike tSU;STA, which is a repeated-start
   -- parameter -- because a STOP always follows a clock pulse, so its setup is never satisfied by
   -- the bus having been idle.
   --
   -- tBUF IS THE ODD ONE OUT IN THE WHOLE OF TABLE 10. Every other parameter constrains something
   -- INSIDE a transfer. tBUF constrains the gap BETWEEN two transfers: it is the only entry in the
   -- table that spans the interval in which nobody owns the bus. So it is the only parameter a
   -- single device can satisfy while still being wrong at the system level -- a master that honours
   -- tBUF has waited long enough, and waiting long enough is exactly what gives every other master
   -- the opportunity Chapter 10.2 measures. tBUF is a floor on the exposure window, not a ceiling.
   --
   -- It also has only a MINIMUM. There is no maximum bus-free time, which is why a bus can sit idle
   -- for hours and why "the bus is idle" is never by itself a fault.
   entity i2c_stop_busfree_checker is
       generic (
           TICK_W : positive := 16;
           -- Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60, 1.3 us = 130.
           T_SU_STO_MIN : natural := 60;
           T_BUF_MIN    : natural := 130
       );
       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;
           stop_det    : out std_logic;
           in_transfer : out std_logic;

           sto_valid : out std_logic;
           t_su_sto  : out unsigned(TICK_W - 1 downto 0);
           buf_valid : out std_logic;
           t_buf     : out unsigned(TICK_W - 1 downto 0);

           viol_su_sto : out std_logic;
           viol_buf    : out std_logic;

           n_su_sto : out unsigned(TICK_W - 1 downto 0);
           n_buf    : out unsigned(TICK_W - 1 downto 0);
           n_viol   : out unsigned(TICK_W - 1 downto 0);
           min_su_sto_seen : out unsigned(TICK_W - 1 downto 0);
           min_buf_seen    : out unsigned(TICK_W - 1 downto 0)
       );
   end entity;

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

       -- tSU;STO: armed by SCL rising, stopped by the SDA rise that makes the STOP.
       signal sto_arm   : std_logic := '0';
       signal sto_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal sto_now   : unsigned(TICK_W - 1 downto 0);

       -- tBUF: armed by the STOP, stopped by the next START. This timer runs while the bus belongs
       -- to nobody, which is what makes it the only inter-transfer parameter.
       signal buf_arm   : std_logic := '0';
       signal buf_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal buf_now   : unsigned(TICK_W - 1 downto 0);

       signal xfer : std_logic := '0';
       signal is_stop_edge, is_start_edge : std_logic;
   begin
       scl_rise        <= (not scl_q) and scl_in;
       sda_fall        <= sda_q and (not sda_in);
       sda_rise        <= (not sda_q) and sda_in;
       scl_stable_high <= scl_q and scl_in;

       is_stop_edge  <= sda_rise and scl_stable_high;
       is_start_edge <= sda_fall and scl_stable_high;

       sto_now <= sto_ticks + 1;
       buf_now <= buf_ticks + 1;
       in_transfer <= xfer;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   sda_q       <= '1';
                   scl_q       <= '1';
                   sto_arm     <= '0';
                   sto_ticks   <= (others => '0');
                   buf_arm     <= '0';
                   buf_ticks   <= (others => '0');
                   start_det   <= '0';
                   stop_det    <= '0';
                   xfer        <= '0';
                   sto_valid   <= '0';
                   t_su_sto    <= (others => '0');
                   buf_valid   <= '0';
                   t_buf       <= (others => '0');
                   viol_su_sto <= '0';
                   viol_buf    <= '0';
                   n_su_sto    <= (others => '0');
                   n_buf       <= (others => '0');
                   n_viol      <= (others => '0');
                   min_su_sto_seen <= (others => '1');
                   min_buf_seen    <= (others => '1');
               else
                   sda_q <= sda_in;
                   scl_q <= scl_in;

                   start_det <= '0';
                   stop_det  <= '0';
                   sto_valid <= '0';
                   buf_valid <= '0';

                   -- the STOP setup timer, armed whenever SCL is released
                   if scl_rise = '1' then
                       sto_arm   <= '1';
                       sto_ticks <= (others => '0');
                   elsif sto_arm = '1' and is_stop_edge = '0' then
                       sto_ticks <= sto_now;
                   end if;

                   if is_stop_edge = '1' then
                       -- A STOP. It closes tSU;STO and opens tBUF.
                       stop_det <= '1';
                       xfer     <= '0';
                       sto_arm  <= '0';

                       if sto_arm = '1' then
                           sto_valid <= '1';
                           t_su_sto  <= sto_now;
                           if sto_now < to_unsigned(T_SU_STO_MIN, TICK_W) then
                               viol_su_sto <= '1';
                               n_viol      <= n_viol + 1;
                           else
                               viol_su_sto <= '0';
                           end if;
                           n_su_sto <= n_su_sto + 1;
                           if sto_now < min_su_sto_seen then min_su_sto_seen <= sto_now; end if;
                       end if;

                       buf_arm   <= '1';
                       buf_ticks <= (others => '0');
                   elsif is_start_edge = '1' then
                       -- A START. If a bus-free interval was being timed, this closes it.
                       start_det <= '1';
                       xfer      <= '1';
                       sto_arm   <= '0';

                       if buf_arm = '1' then
                           buf_arm   <= '0';
                           buf_valid <= '1';
                           t_buf     <= buf_now;
                           if buf_now < to_unsigned(T_BUF_MIN, TICK_W) then
                               viol_buf <= '1';
                               n_viol   <= n_viol + 1;
                           else
                               viol_buf <= '0';
                           end if;
                           n_buf <= n_buf + 1;
                           if buf_now < min_buf_seen then min_buf_seen <= buf_now; end if;
                       end if;
                   elsif buf_arm = '1' then
                       -- The bus-free timer keeps running for as long as the bus stays free. There
                       -- is no maximum, so it must not be capped or cancelled -- a bus idle for an
                       -- hour violates nothing, and a design that stopped timing would report a
                       -- wrong tBUF for the eventual START.
                       buf_ticks <= buf_now;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stop_busfree_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;STO = 0.6 us = 60 ticks, tBUF = 1.3 us = 130 ticks.
   entity i2c_stop_busfree_checker_tb is
   end entity;

   architecture sim of i2c_stop_busfree_checker_tb is
       constant TICK_W       : positive := 16;
       constant T_SU_STO_MIN : natural  := 60;
       constant T_BUF_MIN    : natural  := 130;

       -- measure_buf's own overhead: the 10 trailing ticks of bus_stop plus the 30 leading ticks
       -- of bus_start are part of the measured interval, so callers ask for T_BUF - 40 to land on
       -- the boundary.
       constant BUF_OVERHEAD : natural := 40;

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

       signal start_det, stop_det, in_transfer : std_logic;
       signal sto_valid, buf_valid, viol_su_sto, viol_buf : std_logic;
       signal t_su_sto, t_buf : unsigned(TICK_W - 1 downto 0);
       signal n_su_sto, n_buf, n_viol : unsigned(TICK_W - 1 downto 0);
       signal min_su_sto_seen, min_buf_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 sto_log, buf_log : tick_arr := (others => (others => '0'));
       signal vsto_log, vbuf_log : bit_arr := (others => '0');
       signal n_stolog, n_buflog : natural := 0;

       signal test_done : std_logic := '0';
   begin
       dut : entity work.i2c_stop_busfree_checker
           generic map (TICK_W => TICK_W, T_SU_STO_MIN => T_SU_STO_MIN, T_BUF_MIN => T_BUF_MIN)
           port map (clk => clk, rst_n => rst_n, sda_in => sda_in, scl_in => scl_in,
                     start_det => start_det, stop_det => stop_det, in_transfer => in_transfer,
                     sto_valid => sto_valid, t_su_sto => t_su_sto, buf_valid => buf_valid,
                     t_buf => t_buf, viol_su_sto => viol_su_sto, viol_buf => viol_buf,
                     n_su_sto => n_su_sto, n_buf => n_buf, n_viol => n_viol,
                     min_su_sto_seen => min_su_sto_seen, min_buf_seen => min_buf_seen);

       clk <= not clk after 5 ns;

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

       obs_sto : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and sto_valid = '1' and n_stolog < 32 then
               sto_log(n_stolog) <= t_su_sto; vsto_log(n_stolog) <= viol_su_sto;
               n_stolog <= n_stolog + 1;
           end if;
       end process;

       obs_buf : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and buf_valid = '1' and n_buflog < 32 then
               buf_log(n_buflog) <= t_buf; vbuf_log(n_buflog) <= viol_buf;
               n_buflog <= n_buflog + 1;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable base_sto : natural;
           variable nbuf_before, viol_before, per_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;

           procedure bus_start is
           begin
               sda_in <= '1'; scl_in <= '1'; tick(30);
               sda_in <= '0';               tick(40);
               scl_in <= '0';               tick(20);
           end procedure;

           -- A STOP whose SETUP is settable: SCL is released, `su` ticks pass, then SDA rises.
           procedure bus_stop (su : in positive) is
           begin
               sda_in <= '0'; scl_in <= '0'; tick(20);
               scl_in <= '1';               tick(su);   -- tSU;STO starts here
               sda_in <= '1';               tick(10);   -- the STOP; tBUF starts here
           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 transfer (su_sto, gap : in positive) is
           begin
               bus_start;
               data_bit('1');
               bus_stop(su_sto);
               tick(gap);                   -- the bus-free interval
           end procedure;

           -- Measure EXACTLY one bus-free interval of a known length, leaving its measurement as
           -- the LAST entry in the log.
           --
           -- This helper exists because tBUF is reported by the START that CLOSES the gap, so a
           -- measurement always belongs to the interval BEFORE the transfer that reports it.
           -- Indexing it relative to the transfer that created the gap is off by one.
           procedure measure_buf (gap : in positive) is
           begin
               bus_start;                   -- closes any interval still pending
               data_bit('1');
               bus_stop(120);               -- opens the interval under test
               tick(gap);
               bus_start;                   -- closes it: this is the measurement we want
               data_bit('1');
               bus_stop(120);
           end procedure;
       begin
           tick(3);
           if min_su_sto_seen /= (min_su_sto_seen'range => '1')
              or min_buf_seen /= (min_buf_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: two LEGAL transfers with generous margins. The first transfer's tBUF is closed by
           -- the SECOND transfer's START, so two transfers give one tBUF measurement.
           transfer(120, 300);
           transfer(120, 300);
           if n_stolog < 2 then
               report "too few tSU;STO measurements from two stops" severity error;
               errs := errs + 1; end if;
           if n_buflog < 1 then
               report "too few tBUF measurements from two transfers" severity error;
               errs := errs + 1; end if;
           if n_viol /= to_unsigned(0, TICK_W) then
               report "legal margins were flagged" severity error; errs := errs + 1; end if;
           if sto_log(0) < to_unsigned(115, TICK_W) or sto_log(0) > to_unsigned(125, TICK_W) then
               report "a 120-tick tSU;STO measured out of range" severity error;
               errs := errs + 1; end if;

           -- 2: tSU;STO too SHORT. Only the STOP verdict may fire -- checked by a DELTA on the
           -- tBUF count, because tBUF measurements are reported by a later START and cannot be
           -- indexed relative to the transfer that caused them.
           base_sto := n_stolog; nbuf_before := n_buf;
           transfer(15, 300);
           transfer(120, 300);
           if vsto_log(base_sto) /= '1' then
               report "a 15-tick tSU;STO was not flagged" severity error; errs := errs + 1; end if;

           -- 3: tBUF too SHORT. The next START comes too soon after the STOP -- the case a master
           -- causes by re-claiming the bus greedily, and the one parameter whose violation harms
           -- OTHER masters rather than this transfer. Measured with the helper so the index is
           -- unambiguous.
           base_sto := n_stolog;
           measure_buf(20);
           if vbuf_log(n_buflog - 1) /= '1' then
               report "a short bus-free gap was not flagged" severity error; errs := errs + 1; end if;
           if buf_log(n_buflog - 1) /= to_unsigned(20 + BUF_OVERHEAD, TICK_W) then
               report "a 20-tick gap measured the wrong value" severity error;
               errs := errs + 1; end if;
           if vsto_log(base_sto) /= '0' then
               report "a short tBUF also flagged tSU;STO" severity error; errs := errs + 1; end if;

           -- 4: BOUNDARIES. Exactly at each minimum is legal. The stimulus subtracts the helper's
           -- own overhead so the measurement lands exactly on the boundary -- a boundary test that
           -- does not sit on the boundary proves nothing, so the measured value is asserted too.
           measure_buf(T_BUF_MIN - BUF_OVERHEAD);
           if buf_log(n_buflog - 1) /= to_unsigned(T_BUF_MIN, TICK_W) then
               report "the boundary stimulus did not land exactly on tBUF(min)" severity error;
               errs := errs + 1; end if;
           if vbuf_log(n_buflog - 1) /= '0' then
               report "tBUF exactly at the minimum was rejected" severity error;
               errs := errs + 1; end if;
           base_sto := n_stolog;
           transfer(T_SU_STO_MIN, 300);
           transfer(120, 300);
           if vsto_log(base_sto) /= '0' then
               report "tSU;STO exactly at the minimum was rejected" severity error;
               errs := errs + 1; end if;

           -- 5: one tick BELOW each minimum must fail.
           measure_buf(T_BUF_MIN - BUF_OVERHEAD - 1);
           if buf_log(n_buflog - 1) /= to_unsigned(T_BUF_MIN - 1, TICK_W) then
               report "the below-boundary stimulus did not land one tick below tBUF(min)"
                   severity error; errs := errs + 1; end if;
           if vbuf_log(n_buflog - 1) /= '1' then
               report "tBUF one tick below the minimum was accepted" severity error;
               errs := errs + 1; end if;
           base_sto := n_stolog;
           transfer(T_SU_STO_MIN - 1, 300);
           transfer(120, 300);
           if vsto_log(base_sto) /= '1' then
               report "tSU;STO one tick below the minimum was accepted" severity error;
               errs := errs + 1; end if;

           -- 6: tBUF has NO MAXIMUM. A long idle gap is legal and the timer must keep running
           -- rather than being capped.
           viol_before := n_viol;
           measure_buf(5000);
           if n_viol /= viol_before then
               report "a long idle gap was flagged -- tBUF has no maximum" severity error;
               errs := errs + 1; end if;
           if buf_log(n_buflog - 1) < to_unsigned(5000, TICK_W) then
               report "a 5000-tick gap measured short -- the timer was capped" severity error;
               errs := errs + 1; end if;
           -- The VERDICT must be clean too, not merely the count. tBUF has no maximum, so a design
           -- that added an upper bound would set this bit while leaving the violation total alone --
           -- and a checker whose verdict and count disagree is worse than either being wrong.
           if vbuf_log(n_buflog - 1) /= '0' then
               report "a long idle gap set viol_buf -- tBUF has no MAXIMUM" severity error;
               errs := errs + 1; end if;

           -- 7: a REPEATED START produces NO tBUF measurement, because the bus was never free.
           -- This is Chapter 10.2's distinction as a timing consequence: an Sr has no bus-free
           -- interval, so a block measuring one would report a zero-length tBUF and flag every
           -- combined transaction.
           bus_start;                        -- closes any pending interval first
           nbuf_before := n_buf; viol_before := n_viol;
           data_bit('1');
           -- a repeated START: SDA falls while SCL is high, with no STOP in between
           scl_in <= '0'; sda_in <= '1'; tick(30);
           scl_in <= '1';               tick(80);
           sda_in <= '0';               tick(40);
           scl_in <= '0';               tick(20);
           data_bit('1');
           bus_stop(120);
           if n_buf /= nbuf_before then
               report "a repeated START produced a tBUF measurement" severity error;
               errs := errs + 1; end if;
           if n_viol /= viol_before then
               report "a repeated START produced violations" severity error; errs := errs + 1; end if;
           tick(300);

           -- 8: the worst cases are TRACKED across a clean run.
           for i in 1 to 3 loop transfer(400, 900); end loop;
           if min_su_sto_seen > to_unsigned(59, TICK_W)
              or min_buf_seen > to_unsigned(129, TICK_W) then
               report "worst cases were erased" severity error; errs := errs + 1; end if;

           -- 9: an IDLE bus with no framing measures nothing, and must not report a tBUF for a gap
           -- that no START ever closed.
           nbuf_before := n_buf;
           sda_in <= '1'; scl_in <= '1'; tick(1000);
           if n_buf /= nbuf_before then
               report "an unterminated idle gap produced a tBUF measurement" severity error;
               errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_stop_busfree_checker self-check complete: tSU;STO mirrors tSU;STA, tBUF "
                    & "spans the interval nobody owns and has no maximum, a repeated START measures "
                    & "no bus-free time" severity note;
           else
               report "i2c_stop_busfree_checker self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

6a. Five Decisions Worth Defending

A STOP is recognised by SDA rising while SCL is high, and a START by SDA falling while SCL is high. Both from the definition, and both qualified by SCL. §7's test 9 drives an SDA rise during a low phase — an ordinary data bit going to one — and nothing may be reported.

The bus-free timer runs from the STOP, not from "the bus looks idle". Both lines high is the idle signature, but the parameter is defined from a STOP condition, and a bus that has been idle since power-up has had no STOP. A checker keyed on the signature would report a tBUF measurement for the very first START after reset, measuring an interval that began when the counter did. §7's test 11 asserts nothing is reported for a first START after reset, and mutation F2 keys the timer on the idle signature.

tBUF is measured with no upper bound and no cap. It is a minimum, so an arbitrarily long idle gap is legal and must be measured in full — which means the counter must be wide enough rather than saturating, and the comparison must be one-sided. Mutations F1 and F5 attack those two properties separately, and §7's test 10 catches both because it asserts the measured value as well as the verdict.

The two parameters share no logic. One is an in-transfer margin measured between two edges of the same transaction; the other spans two transactions. §3 is the argument, and keeping them separate is what makes it visible in the code.

The two limits are separate parameters, and here the specification separates them for us. T_SU_STO_MIN = 60 against T_BUF_MIN = 130 in Fast-mode, and they differ in all three modes. That is why §8's mutation F3 — judging the STOP margin against the bus-free minimum — is observable at all, and it is the contrast with Chapter 11.5 §6, where the equal minima leave the equivalent mutation untestable.

6b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o d6 i2c_stop_busfree_checker.sv i2c_stop_busfree_checker_tb.sv && ./d6
   PASS: tSU;STO mirrors tSU;STA, tBUF spans the interval nobody owns and has no maximum, a
   repeated START measures no bus-free time
   i2c_stop_busfree_checker_tb.sv:257: $finish called at 201280000 (1ps)

   $ iverilog -g2005 -o v6 i2c_stop_busfree_checker.v i2c_stop_busfree_checker_tb.v && ./v6
   PASS: tSU;STO mirrors tSU;STA, tBUF spans the interval nobody owns and has no maximum, a
   repeated START measures no bus-free time
   i2c_stop_busfree_checker_tb.v:268: $finish called at 201280000 (1ps)

   $ nvc -a i2c_stop_busfree_checker.vhd i2c_stop_busfree_checker_tb.vhd
   $ nvc -e i2c_stop_busfree_checker_tb && nvc -r i2c_stop_busfree_checker_tb --stop-time=4000us
   ** Note: 201280ns+0: i2c_stop_busfree_checker self-check complete: tSU;STO mirrors tSU;STA,
      tBUF spans the interval nobody owns and has no maximum, a repeated START measures no
      bus-free time

All three at 201280 ns.

7. What the Testbench Proves

The block runs Fast-mode's values: T_SU_STO_MIN = 60 (0.6 µs) and T_BUF_MIN = 130 (1.3 µs) at a 100 MHz sample clock. Unlike Chapter 11.5's pair, these two genuinely differ in every speed mode, so the configuration separates them without contrivance.

#stimuluswhat it establishes
1resetboth worst-case trackers read their maximum
2two complete transferstwo tSU;STO measurements and at least one tBUF; nothing flagged
3a 120-tick STOP marginmeasured as 120, not flagged
4a 15-tick STOP marginviolates tSU;STO
5a 20-tick gap between two transactionsviolates tBUF, and the measured value is the expected one
6that short gapdoes not also flag tSU;STO
7a gap exactly at tBUF(min)the stimulus measures exactly the minimum, and it is accepted
8a gap one tick belowmeasures the expected value, and is rejected
9tSU;STO exactly at its minimum, then one tick belowboth sides of that boundary pinned
10a 5000-tick idle gapmeasured in full — not capped — and not flagged
11a repeated START instead of a STOP-then-STARTzero tBUF measurements and zero violations
12a clean transfer after violationsworst cases not erased
13an unterminated idle gapproduces no tBUF measurement

Tests 7 and 8 are where §5's reporting lag is actually pinned down, and notice what they assert: not just the verdict but the measured value. The helper drives a STOP, an idle gap and the closing START, then reads buf_log[n_buflog - 1], and the test checks that value equals the gap it asked for plus the known BUF_OVERHEAD of 40 ticks — the framing sequences' own contribution to the measured interval.

That is what makes the boundary meaningful. A test that only checked "was it flagged?" would pass on a design whose measurement was off by ten ticks, because a 20-tick gap is flagged either way. Asserting the number is what turns a boundary test into a boundary test.

Test 10 is the anti-capping test and it is the one that catches a real implementation trap. A bus can be idle for seconds, and a counter that saturates reports a small number — so a 5000-tick gap measuring 201 would look like a violation on the quietest possible bus. Mutation F1 is exactly that, and it is the worst class of false positive because it appears when nothing is happening.

Test 11 is §3 and Chapter 11.5 §2 confirmed on the wire. A repeated START keeps the bus, so no STOP occurred and no bus-free interval exists. A checker measuring "time since the last START" instead would report a tBUF value for every repeated START and flag most of them, because a repeated START is allowed to be much faster than a first one.

Test 13 is the unterminated gap, and it is the counterpart of §5's reporting lag. If a gap is never closed by a START, there is no measurement — the interval is still open. A block that reported the running count as a completed measurement would be publishing a number that is still changing.

8. Mutation Testing

Five defects injected into the SystemVerilog checker.

#injected defectoutcome
F1tBUF is capped, so a long idle gap reports a wrong valuekilled — test 10
F2a repeated START closes a bus-free interval that never openedkilled — test 11
F3tSU;STO is judged against tBUF's minimumkilled — test 4
F4the bus-free timer is armed by a START rather than a STOPkilled — test 2
F5tBUF is treated as having a maximum as well as a minimumkilled — test 10

Five injected, five killed. Three worth recording.

F3 is killed only because the two minima differ. T_SU_STO_MIN is 60 and T_BUF_MIN is 130, so judging the STOP margin against the bus-free minimum rejects every margin between 60 and 129 — including test 4's legitimate ones. Had the specification given these two parameters the same number, this mutation would have been unobservable and the chapter would have needed Chapter 11.5 §6's callout. It does not, and the contrast is the point: whether a parameter pair is testable at all is decided by the specification, not by the testbench.

F1 and F5 are both killed by test 10, and they are different defects with the same symptom. Capping the counter makes a long gap measure wrongly; treating tBUF as having a maximum makes a long gap flag wrongly. One is a measurement bug and one is a comparison bug, and a single 5000-tick idle gap catches both because the test asserts the value and the verdict. A test that checked only one of the two would kill only one of the two mutations.

F5 is the mutation that encodes a plausible wrong belief. "The bus has been idle for 50 µs, something must be stuck" is a reasonable thought, and it is what a bus-recovery timeout is for — but it is not a tBUF violation, because tBUF has no maximum. A design that conflated the two would flag every quiet moment. This is the Chapter 10.3 §8 argument for asserting what must not happen, in its most economical form: one test, one mutation, one wrong belief ruled out.

9. Verification Connection — Asserting Across a Transaction Boundary

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_busfree_props.sv — the only property in the module that spans two transactions
   wire stop_event  = $rose(sda) && scl;
   wire start_event = $fell(sda) && scl;

   // tSU;STO is an ordinary in-transfer margin: a free-running timer from SCL's rise, sampled at
   // SDA's rise. Same shape as Chapter 11.5's setup property, with the SDA edge inverted.
   int since_scl_rise;
   always_ff @(posedge clk)
      since_scl_rise <= $rose(scl) ? 0 : since_scl_rise + 1;

   property p_su_sto;
      @(posedge clk) stop_event |-> (since_scl_rise >= T_SU_STO_MIN);
   endproperty
   assert property (p_su_sto)
      else $error("tSU;STO violated: %0d ticks since SCL rose, minimum %0d",
                  since_scl_rise, T_SU_STO_MIN);

   // tBUF spans two transactions, so the property's antecedent is a STOP and its consequent
   // constrains an event in a DIFFERENT transfer -- the only property in this module that does.
   // The `throughout` states the requirement directly: the bus must stay free for the whole
   // interval, so a gap broken part-way through does not satisfy it.
   property p_buf;
      @(posedge clk) stop_event |=> ((sda && scl) throughout (!start_event)[*T_BUF_MIN-1])
                                    ##1 1;
   endproperty
   assert property (p_buf)
      else $error("tBUF violated: a START followed a STOP within %0d ticks", T_BUF_MIN);

   // The NEGATIVE property that matters most here. A repeated START keeps the bus, so tBUF does
   // NOT apply -- and a property that flagged it would fail on every legal write-then-read.
   // Chapter 11.5 section 2 is the reason; this is the assertion that pins it.
   property p_repeated_start_is_exempt;
      @(posedge clk) (start_event && bus_held) |-> !viol_buf;
   endproperty
   assert property (p_repeated_start_is_exempt)
      else $error("a repeated START was judged against tBUF");
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_busfree_cov.sv — a gap is a property of a PAIR of transactions
   // Sampled at each START, carrying facts about the transaction that ENDED and the one
   // beginning -- because tBUF is the only parameter whose subject is a pair.
   covergroup i2c_busfree_cg with function sample(int gap, int gap_min, bit was_repeated,
                                                 bit same_master, int utilisation_pct);
      gap_margin: coverpoint (gap - gap_min) {
         bins violation = {[$:-1]};
         bins exact     = {0};
         bins tight     = {[1:20]};
         bins ample     = {[21:$]};
         bins no_gap    = {[$:$]} with (item == 0);   // back-to-back, the pathological case
      }

      // Whether the bus was KEPT or RELEASED, because tBUF applies only to the second.
      // A suite whose write-then-reads all use a repeated START has no tBUF coverage at all,
      // however many transactions it ran -- and that is a very common suite shape.
      handover: coverpoint was_repeated {
         bins released = {0};        // STOP then START -- tBUF applies
         bins kept     = {1};        // repeated START  -- tBUF does not
      }
      gap_x_handover: cross gap_margin, handover;

      // SAME master versus a DIFFERENT one. Section 3's point: a tBUF violation between two
      // masters is an emergent property of traffic that neither master's own verification can
      // produce, so the two cases need separate coverage.
      who: coverpoint same_master {
         bins one_master  = {1};
         bins two_masters = {0};     // the case section 11 is about
      }
      gap_x_who: cross gap_margin, who;

      // And utilisation, because section 3's third consequence is that tBUF violations are a
      // LOAD phenomenon. A suite that only ever runs a quiet bus will not produce one.
      load: coverpoint utilisation_pct {
         bins quiet    = {[0:30]};
         bins moderate = {[31:70]};
         bins busy     = {[71:100]};
      }
      gap_x_load: cross gap_margin, load;
   endgroup

10. FPGA and ASIC Implications

Two counters, around 55 flops at TICK_W = 16. The bus-free counter must tolerate arbitrarily long idle intervals — a bus can be quiet for seconds — so it needs either generous width or an explicit saturate that is reported as saturated rather than silently wrapping. A wrapped free-time counter reports a short gap on a bus that has been idle for minutes, which is the worst possible false positive because it appears at the quietest moment.

On the generating side, tBUF is a master's obligation after its own STOP and it is cheap to get right. A counted wait between driving a STOP and being allowed to drive the next START, and the count comes from a per-mode table. What is not cheap, and what §11 is about, is meeting it after somebody else's STOP.

The tSU;STO requirement equalling tHIGH(min) means a STOP costs a high phase. §2's identity has a practical form: a master's STOP sequence holds SCL high for at least tSU;STO before releasing SDA, which is a full high phase's worth. So a STOP followed by a START costs tSU;STO + tBUF — 1.9 µs in Fast-mode — against a repeated START's tSU;STA of 0.6 µs. A repeated START is roughly three times cheaper, which is the number behind Chapter 10.1's preference.

Multi-master designs need a bus-free timer keyed on observed STOPs, not on their own. This is the concrete engineering consequence of §3 and the fix in §11: a master must watch the bus for other devices' STOPs and arm its own tBUF counter from them. A master that counts only from its own STOPs is compliant in isolation and violates tBUF the first time it starts after another master finished.

11. Debugging — The Bus-Free Violation That Belonged to Nobody

Pitfall — a tBUF counter armed only by the master's own STOP
Buggy Code
// A master's bus-free timer. After driving a STOP, wait tBUF before allowing the next START:
//
//     always_ff @(posedge clk) begin
//        if (my_stop_issued)        buf_cnt <= T_BUF;
//        else if (buf_cnt != 0)     buf_cnt <= buf_cnt - 1;
//     end
//
//     wire may_start = (buf_cnt == 0) && bus_idle;
//
// Read on its own this is correct and complete. The master waits the full bus-free time after
// every STOP it issues, and it additionally checks the bus is idle before starting -- which
// looks like belt and braces.
//
// It is not. bus_idle is a LEVEL: both lines high right now. It says nothing about how LONG
// they have been high, and tBUF is entirely a statement about duration.
//
// So after ANOTHER master's STOP, buf_cnt is already zero -- no STOP of this master's issued it
// -- and bus_idle goes true the instant that other master releases the lines. This master may
// start immediately.
Symptom

A two-master system: a host controller and a small housekeeping MCU that polled a temperature sensor. Both masters were separately verified, both compliant, and the bus ran Fast-mode.

At low traffic everything worked. As the host's polling rate was raised for a new feature, sensor reads began returning stale values -- the previous reading rather than a fresh one -- at a rate that tracked traffic almost linearly. Roughly one read in forty at moderate load, one in eight when the host was busy.

Everything about the symptom said electrical. It scaled with activity, it was worse when more devices were transferring, and adding a 100 pF capacitor to the board -- an experiment intended to make things worse -- did make things worse, which felt like confirmation.

Both masters were audited for tBUF compliance and both passed. The host waited 1.4 us after its own STOP; the MCU waited 1.5 us after its own. Neither had a violation to find in its own logic, and each team's simulation showed correct behaviour.

What eventually surfaced it was logging STOP and START timestamps from BOTH masters against one timebase, which nobody had done because each master's own log looked clean. Interleaved, the picture was immediate: 0.4 us between the host's STOP and the MCU's START, repeatedly. Both masters were compliant with respect to their OWN previous transaction, and the gap between DIFFERENT masters' transactions was routinely under half the 1.3 us minimum.

The stale reads followed: the sensor had not yet completed its internal bus-release housekeeping when the next START arrived, so it missed the address and replied from its previous state.

Root Cause

Each master armed its bus-free timer only from its own STOP. tBUF is a property of the gap between ANY STOP and the NEXT START, regardless of which device issued either.

The bus_idle level check looked like it covered the case and did not: it tests whether the lines are high now, while tBUF is a statement about how long they have been high. A level can never substitute for a duration -- the same confusion Chapter 10.3 section 4 records for transaction-active tracking.

And the load dependence is exactly what section 3 predicts. tBUF is the only parameter in Table 10 whose subject is a PAIR of transactions, so it is the only one whose violation probability rises with utilisation. That made an electrical explanation fit the evidence perfectly while being wrong.

12. Common Misconceptions

"tBUF is just the idle time before a START." It is the interval from a STOP to the next START. A bus idle since power-up has had no STOP, so the first START after reset has no tBUF to measure.

"Checking the bus is idle satisfies tBUF." Idle is a level; tBUF is a duration. The level is true the instant the lines release, and the duration requirement has barely begun. §11 is that confusion in a two-master system.

"A master that waits tBUF after its own STOP is compliant." Only on a single-master bus. tBUF applies after any STOP, so a multi-master device must arm its timer from observed STOPs on the pins.

"tBUF applies to a repeated START." It does not — a repeated START keeps the bus and no STOP occurred. Its entry requirement is tSU;STA, which in Fast-mode is 0.6 µs against tBUF's 1.3 µs.

"There is a tHD;STO." There is not. A STOP has a setup margin and nothing follows it but idleness, whose requirement is tBUF under a different name. A START has both because the first clock pulse follows it.

"A single-transaction test can cover tBUF." No single transfer can violate it — it takes two, with a controlled gap between them. A suite built around thorough single-transaction verification contains no tBUF stimulus at all.

"A fault whose rate scales with traffic is electrical." tBUF is the one protocol parameter whose violation probability rises with utilisation, so load scaling is exactly what a tBUF problem looks like.

"The framing margins are independent values." tSU;STO, tHD;STA and tHIGH carry identical numbers in all three modes, and tBUF matches tLOW in all three. §2 is the structure — with tSU;STA as the one exception that makes reading the table non-optional.

13. Reason It Through

Three parameters share tHIGH's value and tBUF shares tLOW's. What does that tell you, and what must you still not do?

That the framing margins are the clock's phase minima applied to framing events — recognising a START, recognising a STOP and sampling a bit all take the same input stages the same time. What you must not do is implement from the pattern: tSU;STA breaks it, matching tLOW in Standard-mode and tHIGH in the faster two, and that single irregularity is what Chapter 11.5 §10's design fell into.

Why can no single transaction violate tBUF?

Because its interval is bounded by the STOP of one transaction and the START of another. One transfer supplies at most one of the two endpoints. That is also why a suite organised around verifying single transactions thoroughly has no tBUF coverage.

A master waits 1.4 µs after every STOP it issues and additionally checks the bus is idle before starting. On a two-master bus, is it compliant?

No. Its timer is armed only by its own STOPs, so after the other master's STOP the count is already zero. The idle check does not help: idle is a level that becomes true the instant the lines release, while tBUF is a duration that has barely started. It must arm from any observed STOP on the pins.

Why is a tBUF measurement's value not available at the STOP that began the interval?

Because the interval is closed by the next START, so the measurement exists only once that START occurs. A testbench that indexes the value against the STOP's position in its log reads the previous gap — and passes, if consecutive gaps are equal. Making them unequal is what turns that into a failure.

Sensor reads return stale values at a rate proportional to bus traffic, and adding board capacitance makes it worse. Why is the second observation not evidence for an electrical cause?

Because added capacitance degrades a real electrical margin that sits on top of whatever else is wrong, so it worsens almost any bus fault. And load proportionality is the signature of the one parameter whose subject is a pair of transactions: tBUF violation probability rises with utilisation by construction. The two observations together fit a tBUF problem as well as an electrical one, and only the first is specific.

14. Understanding Check

15. Summary

A STOP has one margin; a START has two. tSU;STO protects the STOP's recognition, and nothing follows it but idleness — whose requirement is tBUF. There is no tHD;STO, and the asymmetry is structural.

Five rows of Table 10 carry two sets of numbers. tHIGH, tHD;STA and tSU;STO are identical in all three modes, and tBUF matches tLOW in all three — because recognising a framing event uses the same input stages for the same time as sampling a bit. Use the structure for intuition and the table for implementation: tSU;STA breaks the pattern.

tBUF is the only parameter measured between two transactions. So no single transfer can violate it, no single-transaction suite covers it, and on a multi-master bus it is an emergent property of traffic rather than one device's defect.

Arm a bus-free timer from any observed STOP on the pins, not from your own. In a multi-master system the STOP that starts your obligation was probably somebody else's — and "the bus is idle" is a level that cannot substitute for a duration.

A measurement closed by a later event cannot be indexed against the event that opened it. tBUF's value arrives with the START that ends the gap, so a testbench needs a helper that states where the result lands, and consecutive tests need different values so a wrong index fails instead of matching.

A load-proportional failure rate points at tBUF before it points at the board. It is the one protocol parameter whose violations scale with utilisation, and added capacitance worsens almost any bus fault, so that experiment confirms nothing.

Whether a parameter pair is testable at all is decided by the specification. tSU;STO and tBUF differ in every mode, so F3 is observable; Chapter 11.5's two START margins coincide in Fast-mode, so the equivalent mutation there is not.

16. What Comes Next

The module's six parameter chapters so far have all treated the edges as instantaneous — a rise or a fall has been an instant at which something is measured. Chapter 11.7 drops that simplification and takes tr, tf and Cb on their own terms.

It is where the module stops being purely digital. tr is not a property a device can meet by itself — it is set by the pull-up resistance and the bus capacitance together, which means a board can violate it with entirely compliant silicon on it. That makes it the only parameter in Table 10 whose value the RTL designer cannot control and the only one with a minimum as well as a maximum in Fast-mode.

And it closes the loop on two results already established: Chapter 11.2 §4's identity, in which tr and tf sit inside the clock's period budget, and Chapter 11.4 §4's Fast-mode-Plus deficit, which was exactly tr(max) and pointed at the pull-up as the term the specification expects a design to beat.

Continue learning