Skip to content
VLSI Mentor

I²C · Module 12

Where an I²C Target May Stretch, and What It Costs

Stretching is free in correctness and expensive in throughput. Establishes exactly where it is legal — including the one speed mode that forbids half of it — and builds the hardware that prices it per byte and per transaction.

Chapter 12.2 showed that a correctly built master is unharmed by stretching: the high phase survives, tLOW cannot be violated, and no stretch-specific logic is needed. That is a statement about correctness, and it is complete.

It says nothing about cost. And the cost is the reason stretching is a design decision rather than a free lunch:

A stretch buys the slave time by spending the master's. There is no third party to pay.

This chapter establishes where a target is permitted to spend it, and how much.

1. Where a Target May Stretch

Three positions matter, and only two of them are legal.

The third row is not a prohibition so much as an impossibility, and it is worth seeing why: framing events happen while SCL is HIGH. A START is SDA falling with SCL high; a STOP is SDA rising with SCL high (Chapter 5.2, Chapter 5.3). There is no low phase inside them to hold, so there is nothing for a stretching slave to grab.

That has a consequence worth stating plainly, because it bounds the whole problem:

Framing is never stretched. A START always completes, and a STOP always completes.

So however badly a slave misbehaves with the clock, it cannot prevent a master from framing — it can only prevent the master from clocking data between frames. Chapter 12.4 leans on this: a master that has lost the clock mid-byte still owns SDA, which is what makes the nine-clock recovery of §3.1.16 conceivable at all.

And note where bit-level stretching reaches that byte-level stretching does not: the address byte. A slave stretching at byte level must have acknowledged something first, and the first thing it can acknowledge is its own address. A slave stretching at bit level is slowing the clock before anyone knows who is being addressed — so it slows the bus for every device, including transfers directed elsewhere. That asymmetry is §4's argument and it is why the two levels have such different costs.

2. The Positions, Drawn

Bit level may take any low phase; byte level takes one

10 cycles
Ten columns representing the eight data bit slots of a byte, its acknowledge slot, and the first low phase of the following byte. Three rows record where each stretching level is permitted. Bit level is permitted in every slot. Byte level is permitted only in the final column, the low phase following the acknowledge. High-speed mode permits stretching only in that same final column.bit level: any low phasebit level: any low phasebyte level: here onlybytelevel:…the acknowledge decides the bytethe acknowledge decides thebytethe byte-level handshake pointthe byte-level handshakepointbit slot12345678ACKafterbit levelokokokokokokokokokokbyte level000000000okin Hs-modenononononononononookt0t1t2t3t4t5t6t7t8t9
One byte and the low phase that follows it, with the positions each stretching level may occupy. Bit-level stretching may take any low phase; byte-level stretching may take only the one after the acknowledge — and that is the only one Hs-mode permits.

Every row is a bus row, because each carries text rather than a level — and a non-bus lane in this component discards anything that is not a bare 1, x or z (Chapter 11.1 §3 records that constraint). The figure is a table drawn on a time axis, which is the honest shape for a claim about positions.

3. Why Hs-Mode Forbids Half of It

§3.1.9 ends with a single sentence — "In Hs-mode, this handshake feature can only be used on byte level" — and the reason is mechanical rather than arbitrary.

In Hs-mode the master does not use a passive pull-up. It drives SCL high with a current source, which is how Hs-mode reaches 3.4 Mbit/s despite Chapter 11.7's RC problem: an active source charges the line far faster than a resistor.

But an active source is a driver. A slave pulling SCL low while the master's current source is enabled is not holding a released line — it is fighting an active driver, which is Chapter 12.1 §4's push-pull contention appearing inside a single speed mode.

So the master disables the current source at exactly one place: after each acknowledge. That one interval is the only time SCL is genuinely released in Hs-mode, and it is therefore the only time stretching is possible at all. The restriction is not a rule imposed on slaves; it is a description of the only window the electrical arrangement leaves open.

Which makes it a checkable property, and §6's design checks it: a bit-level stretch while hs_mode is asserted is a violation, and §7's tests 6 and 7 are the pair that pins it from both sides.

4. What It Costs

The arithmetic is simple; choosing what to compare against is the part that needs care.

Nominal means the time the same traffic would have taken with no stretching, computed from the phases the master is configured for. Not the specification minimum, and not some other bus's time. Chapter 11.9 §3 records the modelling error of comparing against a spec minimum: it answers a question nobody asked, and it makes the obvious remedy invisible.

At Fast-mode with a 130/60-tick clock, a period is 190 ticks and a byte — eight data bits plus the acknowledge — is nine periods, 1710 ticks, 17.1 µs.

what happensoverheadas a share of the byte
nothing00 %
a 500-tick byte-level stretch50029 %
a 100-tick stretch on every bit90053 %
a 3 ms EEPROM page write30000017500 %

Three things follow, and they are the practical content of the chapter.

Byte-level stretching is a bounded tax. One pause per byte, sized by the slave's per-byte work. A sensor taking 5 µs to prepare a reading adds 5 µs to a 17.1 µs byte — a 29 % cost that is predictable and budgetable.

Bit-level stretching is not a tax, it is a rate change. Nine stretches of 100 ticks cost the same as one stretch of 900, but the shape is different: the bus is now running at the slave's rate for the whole transfer, and it will do so for every transfer. §3.1.9 says as much — "the speed of any master is adapted to the internal operating rate of this device".

And an EEPROM write is not a percentage at all. 3 ms against 17.1 µs is not overhead, it is a different order of magnitude, and no throughput figure describes it usefully. What matters there is latency: a master polling that bus has a worst-case wait of milliseconds, and any software timeout set from bus-rate reasoning will fire.

5. Latency, and Why It Is Unbounded

Throughput has an answer. Latency does not, and the reason is Chapter 12.4's subject arriving early:

§4.2.2: "There is no limit in the I²C-bus protocol as to how long this delay can be."

So the worst-case duration of any transaction on a bus with a stretching device is unbounded by the protocol. Whatever number a design needs, it must come from a device datasheet or a system contract — never from the bus specification, which declines to provide one.

That has a concrete consequence for the accounting in §6: the block reports a per-mille overhead figure, and per-mille rather than per-cent because the interesting range is wide. But it also reports the raw nominal and actual tick counts, because a ratio is meaningless when the numerator is three orders of magnitude larger than the denominator. A percentage is a throughput statement; a tick count is a latency statement, and only one of them survives an EEPROM write.

6. The Stretch Accountant in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_accountant.sv — cost per byte, cost per transaction, and one normative check
   // WHAT STRETCHING COSTS, AND WHERE IT IS ALLOWED. Chapter 12.1 detects a stretch and Chapter 12.2
   // shows why it is harmless to a correctly built master. Neither answers the question a system
   // designer actually has: how much throughput does it cost, and is it even legal here?
   //
   // The second half of that question is normative, and it is the one people miss. UM10204 section
   // 3.1.9 describes TWO features and then restricts one of them:
   //
   //   byte level -- "Slaves can then hold the SCL line LOW after reception and acknowledgment of a
   //                 byte to force the master into a wait state"
   //   bit level  -- "can slow down the bus clock by extending each clock LOW period"
   //   and then:  "In Hs-mode, this handshake feature can only be used on byte level."
   //
   // So a bit-level stretch is legal in Standard, Fast and Fast-mode Plus and ILLEGAL in Hs-mode.
   // The reason is mechanical rather than arbitrary: in Hs-mode the active master drives SCL with a
   // current-source pull-up, and it disables that source only "after each acknowledge (A) or
   // not-acknowledge bit (A)". A slave stretching anywhere else would be fighting an active current
   // source instead of holding a released line. That is why this block takes hs_mode as an input and
   // raises a violation rather than silently accounting for it.
   //
   // The COST half is arithmetic, and the only subtlety is what to compare against. "Nominal" here
   // means the time the same traffic would have taken with no stretching at all, computed from the
   // phases the master is configured for -- not the time some other bus took, and not the
   // specification minimum. A budget compared against a spec minimum answers a question nobody
   // asked, which is the modelling error Chapter 11.9 section 3 records.

   module i2c_stretch_accountant #(
       parameter int TICK_W    = 24,
       parameter int T_LOW_NOM = 130,      // the master's configured phases, Fast-mode
       parameter int T_HIGH_NOM = 60
   )(
       input  logic clk,
       input  logic rst_n,

       input  logic scl_release,
       input  logic scl_in,
       input  logic sda_in,
       input  logic hs_mode,               // 1 = Hs-mode: byte-level stretching ONLY

       // ---- per byte ----
       output logic              byte_valid,
       output logic [TICK_W-1:0] t_byte_nominal,
       output logic [TICK_W-1:0] t_byte_actual,
       output logic [TICK_W-1:0] t_byte_overhead,

       // ---- per transaction, reported at the STOP ----
       output logic              txn_valid,
       output logic [TICK_W-1:0] txn_bytes,
       output logic [TICK_W-1:0] txn_nominal,
       output logic [TICK_W-1:0] txn_actual,
       output logic [TICK_W-1:0] txn_overhead,
       output logic [TICK_W-1:0] txn_overhead_permille,   // overhead as parts per thousand

       // ---- where the stretching happened ----
       output logic [TICK_W-1:0] n_byte_level,
       output logic [TICK_W-1:0] n_bit_level,

       // ---- compliance: section 3.1.9's Hs-mode restriction ----
       output logic              viol_hs_bit_level,
       output logic [TICK_W-1:0] n_hs_viol,

       // ---- worst case ----
       output logic [TICK_W-1:0] max_byte_overhead_seen
   );

       localparam int NOM_PERIOD = T_LOW_NOM + T_HIGH_NOM;
       // Nine periods per byte: eight data bits plus the acknowledge.
       localparam int NOM_BYTE_I = 9 * NOM_PERIOD;
       // Sized ONCE here rather than part-selected at each use. A part-select of a parameter
       // (`NOM_BYTE_I[TICK_W-1:0]`) is not portable -- Icarus evaluates it as zero -- and the symptom
       // is a nominal time of 0, which makes every overhead equal to the whole byte.
       localparam logic [TICK_W-1:0] NOM_BYTE = NOM_BYTE_I;

       logic sda_q, scl_q;
       wire  scl_rise =  scl_in && !scl_q;
       wire  scl_fall = !scl_in &&  scl_q;
       wire  sda_rise =  sda_in && !sda_q;
       wire  sda_fall = !sda_in &&  sda_q;
       wire  start_det = sda_fall && scl_in;
       wire  stop_det  = sda_rise && scl_in;

       wire  stretch_now = scl_release && !scl_in;

       logic              in_transfer;
       logic [3:0]        bit_of_byte;
       logic              post_ack;
       logic              stretching;
       logic [TICK_W-1:0] byte_ticks;      // wall-clock ticks in the current byte
       logic [TICK_W-1:0] acc_bytes, acc_nominal, acc_actual;

       wire [TICK_W-1:0] byte_ticks_now = byte_ticks + 1'b1;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               sda_q                  <= 1'b1;
               scl_q                  <= 1'b1;
               in_transfer            <= 1'b0;
               bit_of_byte            <= 4'd0;
               post_ack               <= 1'b0;
               stretching             <= 1'b0;
               byte_ticks             <= '0;
               acc_bytes              <= '0;
               acc_nominal            <= '0;
               acc_actual             <= '0;
               byte_valid             <= 1'b0;
               t_byte_nominal         <= '0;
               t_byte_actual          <= '0;
               t_byte_overhead        <= '0;
               txn_valid              <= 1'b0;
               txn_bytes              <= '0;
               txn_nominal            <= '0;
               txn_actual             <= '0;
               txn_overhead           <= '0;
               txn_overhead_permille  <= '0;
               n_byte_level           <= '0;
               n_bit_level            <= '0;
               viol_hs_bit_level      <= 1'b0;
               n_hs_viol              <= '0;
               max_byte_overhead_seen <= '0;
           end else begin
               sda_q      <= sda_in;
               scl_q      <= scl_in;
               byte_valid <= 1'b0;
               txn_valid  <= 1'b0;

               // ---- framing ----
               if (start_det) begin
                   in_transfer <= 1'b1;
                   bit_of_byte <= 4'd0;
                   post_ack    <= 1'b0;
                   byte_ticks  <= '0;
                   acc_bytes   <= '0;
                   acc_nominal <= '0;
                   acc_actual  <= '0;
               end else if (stop_det) begin
                   // Report the transaction. A transaction's nominal time is the sum of its bytes'
                   // nominal times, which is why the accumulators exist rather than a single
                   // start-to-stop subtraction: a start-to-stop measurement cannot be decomposed,
                   // and "which byte was slow" is the question a report has to answer.
                   in_transfer <= 1'b0;
                   txn_valid   <= 1'b1;
                   txn_bytes   <= acc_bytes;
                   txn_nominal <= acc_nominal;
                   txn_actual  <= acc_actual;
                   txn_overhead <= (acc_actual > acc_nominal) ? (acc_actual - acc_nominal)
                                                             : {TICK_W{1'b0}};
                   // Guarded divide. This is a MEASUREMENT block, not a datapath -- the quotient is
                   // computed once per transaction and never feeds logic, so the cost is acceptable
                   // where it would not be on a bit path.
                   txn_overhead_permille <= (acc_nominal != 0)
                       ? (((acc_actual > acc_nominal) ? (acc_actual - acc_nominal) : {TICK_W{1'b0}})
                           * 1000) / acc_nominal
                       : {TICK_W{1'b0}};
                   bit_of_byte <= 4'd0;
                   post_ack    <= 1'b0;
               end else if (in_transfer) begin
                   // Only count once the first low phase has begun. The gap between the START
                   // condition and that first falling edge is tHD;STA (Chapter 11.5), which belongs
                   // to the framing rather than to the byte -- charging it to the byte inflates every
                   // first-byte measurement by however long the master's START sequence took.
                   if (!scl_fall && bit_of_byte != 4'd0) byte_ticks <= byte_ticks_now;

                   if (scl_fall) begin
                       if (bit_of_byte == 4'd9) begin
                           // A byte just completed at the ninth fall: report it and start the next.
                           byte_valid      <= 1'b1;
                           t_byte_nominal  <= NOM_BYTE;
                           t_byte_actual   <= byte_ticks_now;
                           t_byte_overhead <= (byte_ticks_now > NOM_BYTE)
                                                ? (byte_ticks_now - NOM_BYTE)
                                                : {TICK_W{1'b0}};
                           if (byte_ticks_now > NOM_BYTE
                               && (byte_ticks_now - NOM_BYTE) > max_byte_overhead_seen)
                               max_byte_overhead_seen <= byte_ticks_now - NOM_BYTE;

                           acc_bytes   <= acc_bytes   + 1'b1;
                           acc_nominal <= acc_nominal + NOM_BYTE;
                           acc_actual  <= acc_actual  + byte_ticks_now;

                           byte_ticks  <= '0;
                           bit_of_byte <= 4'd1;
                           post_ack    <= 1'b1;
                       end else begin
                           bit_of_byte <= bit_of_byte + 4'd1;
                           post_ack    <= 1'b0;
                           // The first fall after a START opens the byte's measurement window.
                           if (bit_of_byte == 4'd0) byte_ticks <= '0;
                           else                     byte_ticks <= byte_ticks_now;
                       end
                   end
               end

               // ---- the stretch, classified and checked against the Hs-mode restriction ----
               viol_hs_bit_level <= 1'b0;
               if (stretch_now && !stretching) begin
                   stretching <= 1'b1;
                   if (post_ack) begin
                       n_byte_level <= n_byte_level + 1'b1;
                   end else begin
                       n_bit_level <= n_bit_level + 1'b1;
                       // Section 3.1.9: Hs-mode permits the handshake on byte level ONLY.
                       if (hs_mode) begin
                           viol_hs_bit_level <= 1'b1;
                           n_hs_viol         <= n_hs_viol + 1'b1;
                       end
                   end
               end else if (!stretch_now) begin
                   stretching <= 1'b0;
               end
           end
       end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_accountant_tb.sv — ten scenarios across both stretching levels and both speed regimes
   `timescale 1ns/1ps
   // 100 MHz sample clock, Fast-mode phases: T_LOW_NOM = 130, T_HIGH_NOM = 60, so a nominal period
   // is 190 ticks and a nominal byte (nine periods) is 1710 ticks.
   //
   // Every expected overhead is the stretch length that was DRIVEN, so the arithmetic under test is
   // the block's and not the testbench's.

   module i2c_stretch_accountant_tb;

       localparam int TICK_W     = 24;
       localparam int T_LOW_NOM  = 130;
       localparam int T_HIGH_NOM = 60;
       localparam int NOM_BYTE   = 9 * (T_LOW_NOM + T_HIGH_NOM);   // 1710

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

       logic m_release = 1'b1, s_hold = 1'b0, sda = 1'b1, hs_mode = 1'b0;
       wire  scl_line  = m_release && !s_hold;

       logic              byte_valid, txn_valid, viol_hs_bit_level;
       logic [TICK_W-1:0] t_byte_nominal, t_byte_actual, t_byte_overhead;
       logic [TICK_W-1:0] txn_bytes, txn_nominal, txn_actual, txn_overhead, txn_overhead_permille;
       logic [TICK_W-1:0] n_byte_level, n_bit_level, n_hs_viol, max_byte_overhead_seen;

       i2c_stretch_accountant #(.TICK_W(TICK_W), .T_LOW_NOM(T_LOW_NOM), .T_HIGH_NOM(T_HIGH_NOM)) dut (
           .clk(clk), .rst_n(rst_n),
           .scl_release(m_release), .scl_in(scl_line), .sda_in(sda), .hs_mode(hs_mode),
           .byte_valid(byte_valid), .t_byte_nominal(t_byte_nominal),
           .t_byte_actual(t_byte_actual), .t_byte_overhead(t_byte_overhead),
           .txn_valid(txn_valid), .txn_bytes(txn_bytes), .txn_nominal(txn_nominal),
           .txn_actual(txn_actual), .txn_overhead(txn_overhead),
           .txn_overhead_permille(txn_overhead_permille),
           .n_byte_level(n_byte_level), .n_bit_level(n_bit_level),
           .viol_hs_bit_level(viol_hs_bit_level), .n_hs_viol(n_hs_viol),
           .max_byte_overhead_seen(max_byte_overhead_seen)
       );

       int errors = 0;

       int n_blog, bnom[0:63], bact[0:63], bovh[0:63];
       always @(posedge clk) if (rst_n && byte_valid && n_blog < 64) begin
           bnom[n_blog] = t_byte_nominal; bact[n_blog] = t_byte_actual;
           bovh[n_blog] = t_byte_overhead; n_blog++;
       end
       int n_tlog, tbytes[0:15], tnom[0:15], tact[0:15], tovh[0:15], tpm[0:15];
       always @(posedge clk) if (rst_n && txn_valid && n_tlog < 16) begin
           tbytes[n_tlog] = txn_bytes; tnom[n_tlog] = txn_nominal; tact[n_tlog] = txn_actual;
           tovh[n_tlog]   = txn_overhead; tpm[n_tlog] = txn_overhead_permille; n_tlog++;
       end

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

       // One bit cell with an optional slave hold on its LOW phase.
       task automatic bit_cell(input int hold);
           begin
               m_release = 1'b0;
               tick(T_LOW_NOM);
               if (hold > 0) begin
                   s_hold = 1'b1; m_release = 1'b1; tick(hold); s_hold = 1'b0;
               end else begin
                   m_release = 1'b1;
               end
               tick(T_HIGH_NOM);
           end
       endtask

       // A byte plus its acknowledge: nine cells. hold_at selects which cell's low phase stretches.
       task automatic do_byte(input int hold_at, input int hold_len);
           int b;
           begin for (b = 1; b <= 9; b++) bit_cell((b == hold_at) ? hold_len : 0); end
       endtask

       task automatic do_start();
           begin sda = 1'b1; m_release = 1'b1; tick(10); sda = 1'b0; tick(10); end
       endtask
       task automatic do_stop();
           begin m_release = 1'b0; tick(5); sda = 1'b0; m_release = 1'b1; tick(10);
                 sda = 1'b1; tick(20); end
       endtask

       // A byte's measurement is closed by the falling edge that BEGINS THE NEXT BYTE -- nine periods
       // are only complete at the tenth fall. So `do_byte` returning does not mean the byte has been
       // reported, and a check placed right after it reads the previous byte's entry or none at all.
       // This is exactly the reporting lag of Chapter 11.6 section 5, and the fix is the same: drive
       // a whole START-bytes-STOP sequence, let the STOP's own falling edge close the final byte,
       // and only then read the log. Every check below is written that way, and the log index is
       // absolute rather than relative for the same reason.
       //
       // n_blog after `do_start(); do_byte(); do_byte(); do_stop();` is exactly 2.

       initial begin
           tick(4); rst_n = 1'b1; tick(4);

           // ---- 1. reset ------------------------------------------------------------------------
           if (max_byte_overhead_seen !== '0 || n_hs_viol !== '0) begin
               $display("FAIL: counters nonzero out of reset"); errors++; end
           if (n_byte_level !== '0 || n_bit_level !== '0) begin
               $display("FAIL: stretch counters nonzero out of reset"); errors++; end

           // ---- 2 and 3. a clean byte, then a 500-tick stretch -----------------------------------
           do_start();
           do_byte(0, 0);          // byte 0: nothing
           do_byte(3, 500);        // byte 1: 500 ticks on bit 3's low phase
           do_stop();
           if (n_blog != 2) begin
               $display("FAIL: %0d bytes reported from two, expected 2", n_blog); errors++; end
           else begin
               // 2: the unstretched byte costs exactly its nominal time
               if (bnom[0] != NOM_BYTE) begin
                   $display("FAIL: nominal byte %0d, expected %0d", bnom[0], NOM_BYTE); errors++; end
               if (bact[0] != NOM_BYTE) begin
                   $display("FAIL: an unstretched byte took %0d, expected %0d", bact[0], NOM_BYTE);
                   errors++; end
               if (bovh[0] != 0) begin
                   $display("FAIL: an unstretched byte reported %0d ticks of overhead", bovh[0]);
                   errors++; end
               // 3: the stretch costs exactly what was driven
               if (bovh[1] != 500) begin
                   $display("FAIL: a 500-tick stretch reported %0d ticks of overhead", bovh[1]);
                   errors++; end
               if (bact[1] != NOM_BYTE + 500) begin
                   $display("FAIL: actual byte time %0d, expected %0d", bact[1], NOM_BYTE + 500);
                   errors++; end
           end

           // ---- 4. the transaction total is the sum, and the per-mille figure is right -----------
           begin
               int exp_nom, exp_ovh, exp_pm;
               exp_nom = 2 * NOM_BYTE;
               exp_ovh = 500;
               exp_pm  = (exp_ovh * 1000) / exp_nom;     // 500 on 3420 -> 146 per mille
               if (n_tlog != 1) begin
                   $display("FAIL: %0d transactions reported, expected 1", n_tlog); errors++; end
               else begin
                   if (tbytes[0] != 2) begin
                       $display("FAIL: %0d bytes in the transaction, expected 2", tbytes[0]);
                       errors++; end
                   if (tnom[0] != exp_nom) begin
                       $display("FAIL: txn nominal %0d, expected %0d", tnom[0], exp_nom); errors++; end
                   if (tact[0] != exp_nom + exp_ovh) begin
                       $display("FAIL: txn actual %0d, expected %0d", tact[0], exp_nom + exp_ovh);
                       errors++; end
                   if (tovh[0] != exp_ovh) begin
                       $display("FAIL: txn overhead %0d, expected %0d", tovh[0], exp_ovh); errors++; end
                   if (tpm[0] != exp_pm) begin
                       $display("FAIL: txn overhead %0d per mille, expected %0d", tpm[0], exp_pm);
                       errors++; end
               end
           end

           // ---- 5. byte-level vs bit-level classification --------------------------------------
           // The low phase immediately following an acknowledge is section 3.1.9's byte-level
           // handshake point; any other low phase is bit level.
           begin
               int by0, bi0;
               by0 = n_byte_level; bi0 = n_bit_level;
               do_start();
               do_byte(0, 0);       // byte A: clean, ends with its ack
               do_byte(1, 200);     // byte B: the stretch sits on the low phase AFTER that ack
               do_stop();
               if (n_byte_level != by0 + 1) begin
                   $display("FAIL: the post-ack stretch was not counted byte-level (%0d)",
                            n_byte_level - by0); errors++; end
               if (n_bit_level != bi0) begin
                   $display("FAIL: a post-ack stretch was ALSO counted bit-level"); errors++; end
           end
           begin
               int by0, bi0;
               by0 = n_byte_level; bi0 = n_bit_level;
               do_start();
               do_byte(0, 0);
               do_byte(5, 200);     // mid-byte -> bit level
               do_stop();
               if (n_bit_level != bi0 + 1) begin
                   $display("FAIL: the mid-byte stretch was not counted bit-level (%0d)",
                            n_bit_level - bi0); errors++; end
               if (n_byte_level != by0) begin
                   $display("FAIL: a mid-byte stretch was ALSO counted byte-level"); errors++; end
           end

           // ---- 6. Hs-mode: a BIT-level stretch is a violation ----------------------------------
           // "In Hs-mode, this handshake feature can only be used on byte level." The only normative
           // check in this module that depends on the speed mode.
           begin
               int v0; v0 = n_hs_viol;
               hs_mode = 1'b1;
               do_start(); do_byte(0, 0); do_byte(4, 150); do_stop();
               if (n_hs_viol != v0 + 1) begin
                   $display("FAIL: a bit-level stretch in Hs-mode was not flagged (%0d)",
                            n_hs_viol - v0); errors++; end
           end

           // ---- 7. Hs-mode: a BYTE-level stretch is legal ---------------------------------------
           // The mirror of test 6, and the test that stops the check degenerating into
           // "hs_mode && any stretch" -- which would pass test 6 and be wrong.
           begin
               int v0; v0 = n_hs_viol;
               do_start(); do_byte(0, 0); do_byte(1, 150); do_stop();
               if (n_hs_viol != v0) begin
                   $display("FAIL: a byte-level stretch in Hs-mode was flagged as a violation");
                   errors++; end
               hs_mode = 1'b0;
           end

           // ---- 8. the worst-case byte overhead is a MAXIMUM ------------------------------------
           begin
               int peak; peak = max_byte_overhead_seen;
               do_start(); do_byte(0, 0); do_byte(2, 100); do_stop();
               if (max_byte_overhead_seen != peak) begin
                   $display("FAIL: max_byte_overhead_seen fell to %0d (peak was %0d)",
                            max_byte_overhead_seen, peak); errors++; end
               do_start(); do_byte(0, 0); do_byte(2, 900); do_stop();
               if (max_byte_overhead_seen != 900) begin
                   $display("FAIL: max_byte_overhead_seen = %0d after a 900-tick stretch",
                            max_byte_overhead_seen); errors++; end
           end

           // ---- 9. bit-level stretching on EVERY bit: section 3.1.9's rate-matching case ---------
           // Nine 100-tick stretches in one byte is 900 ticks of overhead on a 1710-tick byte -- the
           // case where a slow microcontroller drags the whole bus down to roughly two thirds rate.
           begin
               int b, k0;
               k0 = n_tlog;
               do_start();
               for (b = 1; b <= 9; b++) bit_cell(100);
               do_byte(0, 0);          // a second byte so the first is closed inside the transaction
               do_stop();
               if (n_tlog != k0 + 1) begin
                   $display("FAIL: the rate-matched transaction was not reported"); errors++; end
               else if (tovh[k0] != 900) begin
                   $display("FAIL: nine 100-tick stretches gave %0d ticks of overhead, expected 900",
                            tovh[k0]); errors++; end
               else if (tpm[k0] != (900 * 1000) / (2 * NOM_BYTE)) begin
                   $display("FAIL: per-mille %0d, expected %0d", tpm[k0],
                            (900 * 1000) / (2 * NOM_BYTE)); errors++; end
           end

           // ---- 10. a clean transaction reports ZERO overhead -----------------------------------
           // The negative case. An accountant that always found overhead would pass every test above.
           begin
               int k0; k0 = n_tlog;
               do_start(); do_byte(0, 0); do_byte(0, 0); do_stop();
               if (tovh[k0] != 0) begin
                   $display("FAIL: a clean transaction reported %0d ticks of overhead", tovh[k0]);
                   errors++; end
               if (tpm[k0] != 0) begin
                   $display("FAIL: a clean transaction reported %0d per mille", tpm[k0]); errors++; end
               if (tnom[k0] != tact[k0]) begin
                   $display("FAIL: nominal %0d != actual %0d on a clean transaction",
                            tnom[k0], tact[k0]); errors++; end
           end

           if (errors == 0)
               $display("PASS: overhead is measured against the configured phases, byte and transaction totals decompose, Hs-mode permits byte-level stretching only, a clean transaction costs nothing");
           else
               $display("FAIL: %0d error(s)", errors);
           $finish;
       end

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_accountant.v — the same accountant in Verilog-2001
   // WHAT STRETCHING COSTS, AND WHERE IT IS ALLOWED. Chapter 12.1 detects a stretch and Chapter 12.2
   // shows why it is harmless to a correctly built master. Neither answers the question a system
   // designer actually has: how much throughput does it cost, and is it even legal here?
   //
   // The second half of that question is normative, and it is the one people miss. UM10204 section
   // 3.1.9 describes TWO features and then restricts one of them:
   //
   //   byte level -- "Slaves can then hold the SCL line LOW after reception and acknowledgment of a
   //                 byte to force the master into a wait state"
   //   bit level  -- "can slow down the bus clock by extending each clock LOW period"
   //   and then:  "In Hs-mode, this handshake feature can only be used on byte level."
   //
   // So a bit-level stretch is legal in Standard, Fast and Fast-mode Plus and ILLEGAL in Hs-mode.
   // The reason is mechanical rather than arbitrary: in Hs-mode the active master drives SCL with a
   // current-source pull-up, and it disables that source only "after each acknowledge (A) or
   // not-acknowledge bit (A)". A slave stretching anywhere else would be fighting an active current
   // source instead of holding a released line. That is why this block takes hs_mode as an input and
   // raises a violation rather than silently accounting for it.
   //
   // The COST half is arithmetic, and the only subtlety is what to compare against. "Nominal" here
   // means the time the same traffic would have taken with no stretching at all, computed from the
   // phases the master is configured for -- not the time some other bus took, and not the
   // specification minimum. A budget compared against a spec minimum answers a question nobody
   // asked, which is the modelling error Chapter 11.9 section 3 records.

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_stretch_accountant #(
       parameter TICK_W    = 24,
       parameter T_LOW_NOM = 130,      // the master's configured phases, Fast-mode
       parameter T_HIGH_NOM = 60
   )(
       input  wire  clk,
       input  wire  rst_n,

       input  wire  scl_release,
       input  wire  scl_in,
       input  wire  sda_in,
       input  wire  hs_mode,               // 1 = Hs-mode: byte-level stretching ONLY

       // ---- per byte ----
       output reg                byte_valid,
       output reg   [TICK_W-1:0] t_byte_nominal,
       output reg   [TICK_W-1:0] t_byte_actual,
       output reg   [TICK_W-1:0] t_byte_overhead,

       // ---- per transaction, reported at the STOP ----
       output reg                txn_valid,
       output reg   [TICK_W-1:0] txn_bytes,
       output reg   [TICK_W-1:0] txn_nominal,
       output reg   [TICK_W-1:0] txn_actual,
       output reg   [TICK_W-1:0] txn_overhead,
       output reg   [TICK_W-1:0] txn_overhead_permille,   // overhead as parts per thousand

       // ---- where the stretching happened ----
       output reg   [TICK_W-1:0] n_byte_level,
       output reg   [TICK_W-1:0] n_bit_level,

       // ---- compliance: section 3.1.9's Hs-mode restriction ----
       output reg                viol_hs_bit_level,
       output reg   [TICK_W-1:0] n_hs_viol,

       // ---- worst case ----
       output reg   [TICK_W-1:0] max_byte_overhead_seen
   );

       localparam NOM_PERIOD = T_LOW_NOM + T_HIGH_NOM;
       // Nine periods per byte: eight data bits plus the acknowledge.
       localparam NOM_BYTE_I = 9 * NOM_PERIOD;
       // Sized ONCE here rather than part-selected at each use. A part-select of a parameter
       // (`NOM_BYTE_I[TICK_W-1:0]`) is not portable -- Icarus evaluates it as zero -- and the symptom
       // is a nominal time of 0, which makes every overhead equal to the whole byte.
       localparam [TICK_W-1:0] NOM_BYTE = NOM_BYTE_I;

       reg sda_q, scl_q;
       wire  scl_rise =  scl_in && !scl_q;
       wire  scl_fall = !scl_in &&  scl_q;
       wire  sda_rise =  sda_in && !sda_q;
       wire  sda_fall = !sda_in &&  sda_q;
       wire  start_det = sda_fall && scl_in;
       wire  stop_det  = sda_rise && scl_in;

       wire  stretch_now = scl_release && !scl_in;

       reg              in_transfer;
       reg [3:0]        bit_of_byte;
       reg              post_ack;
       reg              stretching;
       reg [TICK_W-1:0] byte_ticks;      // wall-clock ticks in the current byte
       reg [TICK_W-1:0] acc_bytes, acc_nominal, acc_actual;

       wire [TICK_W-1:0] byte_ticks_now = byte_ticks + 1'b1;

       always @(posedge clk) begin
           if (!rst_n) begin
               sda_q                  <= 1'b1;
               scl_q                  <= 1'b1;
               in_transfer            <= 1'b0;
               bit_of_byte            <= 4'd0;
               post_ack               <= 1'b0;
               stretching             <= 1'b0;
               byte_ticks             <= {TICK_W{1'b0}};
               acc_bytes              <= {TICK_W{1'b0}};
               acc_nominal            <= {TICK_W{1'b0}};
               acc_actual             <= {TICK_W{1'b0}};
               byte_valid             <= 1'b0;
               t_byte_nominal         <= {TICK_W{1'b0}};
               t_byte_actual          <= {TICK_W{1'b0}};
               t_byte_overhead        <= {TICK_W{1'b0}};
               txn_valid              <= 1'b0;
               txn_bytes              <= {TICK_W{1'b0}};
               txn_nominal            <= {TICK_W{1'b0}};
               txn_actual             <= {TICK_W{1'b0}};
               txn_overhead           <= {TICK_W{1'b0}};
               txn_overhead_permille  <= {TICK_W{1'b0}};
               n_byte_level           <= {TICK_W{1'b0}};
               n_bit_level            <= {TICK_W{1'b0}};
               viol_hs_bit_level      <= 1'b0;
               n_hs_viol              <= {TICK_W{1'b0}};
               max_byte_overhead_seen <= {TICK_W{1'b0}};
           end else begin
               sda_q      <= sda_in;
               scl_q      <= scl_in;
               byte_valid <= 1'b0;
               txn_valid  <= 1'b0;

               // ---- framing ----
               if (start_det) begin
                   in_transfer <= 1'b1;
                   bit_of_byte <= 4'd0;
                   post_ack    <= 1'b0;
                   byte_ticks  <= {TICK_W{1'b0}};
                   acc_bytes   <= {TICK_W{1'b0}};
                   acc_nominal <= {TICK_W{1'b0}};
                   acc_actual  <= {TICK_W{1'b0}};
               end else if (stop_det) begin
                   // Report the transaction. A transaction's nominal time is the sum of its bytes'
                   // nominal times, which is why the accumulators exist rather than a single
                   // start-to-stop subtraction: a start-to-stop measurement cannot be decomposed,
                   // and "which byte was slow" is the question a report has to answer.
                   in_transfer <= 1'b0;
                   txn_valid   <= 1'b1;
                   txn_bytes   <= acc_bytes;
                   txn_nominal <= acc_nominal;
                   txn_actual  <= acc_actual;
                   txn_overhead <= (acc_actual > acc_nominal) ? (acc_actual - acc_nominal)
                                                             : {TICK_W{1'b0}};
                   // Guarded divide. This is a MEASUREMENT block, not a datapath -- the quotient is
                   // computed once per transaction and never feeds logic, so the cost is acceptable
                   // where it would not be on a bit path.
                   txn_overhead_permille <= (acc_nominal != 0)
                       ? (((acc_actual > acc_nominal) ? (acc_actual - acc_nominal) : {TICK_W{1'b0}})
                           * 1000) / acc_nominal
                       : {TICK_W{1'b0}};
                   bit_of_byte <= 4'd0;
                   post_ack    <= 1'b0;
               end else if (in_transfer) begin
                   // Only count once the first low phase has begun. The gap between the START
                   // condition and that first falling edge is tHD;STA (Chapter 11.5), which belongs
                   // to the framing rather than to the byte -- charging it to the byte inflates every
                   // first-byte measurement by however long the master's START sequence took.
                   if (!scl_fall && bit_of_byte != 4'd0) byte_ticks <= byte_ticks_now;

                   if (scl_fall) begin
                       if (bit_of_byte == 4'd9) begin
                           // A byte just completed at the ninth fall: report it and start the next.
                           byte_valid      <= 1'b1;
                           t_byte_nominal  <= NOM_BYTE;
                           t_byte_actual   <= byte_ticks_now;
                           t_byte_overhead <= (byte_ticks_now > NOM_BYTE)
                                                ? (byte_ticks_now - NOM_BYTE)
                                                : {TICK_W{1'b0}};
                           if (byte_ticks_now > NOM_BYTE
                               && (byte_ticks_now - NOM_BYTE) > max_byte_overhead_seen)
                               max_byte_overhead_seen <= byte_ticks_now - NOM_BYTE;

                           acc_bytes   <= acc_bytes   + 1'b1;
                           acc_nominal <= acc_nominal + NOM_BYTE;
                           acc_actual  <= acc_actual  + byte_ticks_now;

                           byte_ticks  <= {TICK_W{1'b0}};
                           bit_of_byte <= 4'd1;
                           post_ack    <= 1'b1;
                       end else begin
                           bit_of_byte <= bit_of_byte + 4'd1;
                           post_ack    <= 1'b0;
                           // The first fall after a START opens the byte's measurement window.
                           if (bit_of_byte == 4'd0) byte_ticks <= {TICK_W{1'b0}};
                           else                     byte_ticks <= byte_ticks_now;
                       end
                   end
               end

               // ---- the stretch, classified and checked against the Hs-mode restriction ----
               viol_hs_bit_level <= 1'b0;
               if (stretch_now && !stretching) begin
                   stretching <= 1'b1;
                   if (post_ack) begin
                       n_byte_level <= n_byte_level + 1'b1;
                   end else begin
                       n_bit_level <= n_bit_level + 1'b1;
                       // Section 3.1.9: Hs-mode permits the handshake on byte level ONLY.
                       if (hs_mode) begin
                           viol_hs_bit_level <= 1'b1;
                           n_hs_viol         <= n_hs_viol + 1'b1;
                       end
                   end
               end else if (!stretch_now) begin
                   stretching <= 1'b0;
               end
           end
       end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_accountant_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // 100 MHz sample clock, Fast-mode phases: T_LOW_NOM = 130, T_HIGH_NOM = 60, so a nominal period
   // is 190 ticks and a nominal byte (nine periods) is 1710 ticks.
   //
   // Every expected overhead is the stretch length that was DRIVEN, so the arithmetic under test is
   // the block's and not the testbench's.

   // (Verilog-2001 testbench -- same stimulus, same checks.)
   module i2c_stretch_accountant_tb;

       localparam TICK_W     = 24;
       localparam T_LOW_NOM  = 130;
       localparam T_HIGH_NOM = 60;
       localparam NOM_BYTE   = 9 * (T_LOW_NOM + T_HIGH_NOM);   // 1710

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

       reg m_release = 1'b1, s_hold = 1'b0, sda = 1'b1, hs_mode = 1'b0;
       wire  scl_line  = m_release && !s_hold;

       wire              byte_valid, txn_valid, viol_hs_bit_level;
       wire [TICK_W-1:0] t_byte_actual;
       wire [TICK_W-1:0] t_byte_nominal, t_byte_overhead;
       wire [TICK_W-1:0] txn_actual;
       wire [TICK_W-1:0] txn_bytes, txn_nominal, txn_overhead, txn_overhead_permille;
       wire [TICK_W-1:0] n_bit_level;
       wire [TICK_W-1:0] n_byte_level, n_hs_viol, max_byte_overhead_seen;

       i2c_stretch_accountant #(.TICK_W(TICK_W), .T_LOW_NOM(T_LOW_NOM), .T_HIGH_NOM(T_HIGH_NOM)) dut (
           .clk(clk), .rst_n(rst_n),
           .scl_release(m_release), .scl_in(scl_line), .sda_in(sda), .hs_mode(hs_mode),
           .byte_valid(byte_valid), .t_byte_nominal(t_byte_nominal),
           .t_byte_actual(t_byte_actual), .t_byte_overhead(t_byte_overhead),
           .txn_valid(txn_valid), .txn_bytes(txn_bytes), .txn_nominal(txn_nominal),
           .txn_actual(txn_actual), .txn_overhead(txn_overhead),
           .txn_overhead_permille(txn_overhead_permille),
           .n_byte_level(n_byte_level), .n_bit_level(n_bit_level),
           .viol_hs_bit_level(viol_hs_bit_level), .n_hs_viol(n_hs_viol),
           .max_byte_overhead_seen(max_byte_overhead_seen)
       );

       integer errors = 0;
       // Hoisted to module scope: Verilog-2001 permits a variable declaration only at
       // module level or in a NAMED block, and every call site below is sequential.
       integer v0 = 0;
       integer peak = 0;
       integer k0 = 0;
       integer b = 0;
       integer exp_nom = 0;
       integer exp_ovh = 0;
       integer exp_pm = 0;
       integer by0 = 0;
       integer bi0 = 0;


       integer n_blog = 0;

       integer bnom[0:63];

       integer bact[0:63];

       integer bovh[0:63];
       always @(posedge clk) if (rst_n && byte_valid && n_blog < 64) begin
           bnom[n_blog] = t_byte_nominal; bact[n_blog] = t_byte_actual;
           bovh[n_blog] = t_byte_overhead; n_blog = n_blog + 1;
       end
       integer n_tlog = 0;
       integer tbytes[0:15];
       integer tnom[0:15];
       integer tact[0:15];
       integer tovh[0:15];
       integer tpm[0:15];
       always @(posedge clk) if (rst_n && txn_valid && n_tlog < 16) begin
           tbytes[n_tlog] = txn_bytes; tnom[n_tlog] = txn_nominal; tact[n_tlog] = txn_actual;
           tovh[n_tlog]   = txn_overhead; tpm[n_tlog] = txn_overhead_permille; n_tlog = n_tlog + 1;
       end

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

       // One bit cell with an optional slave hold on its LOW phase.
       task bit_cell(input integer hold);
           begin
               m_release = 1'b0;
               tick(T_LOW_NOM);
               if (hold > 0) begin
                   s_hold = 1'b1; m_release = 1'b1; tick(hold); s_hold = 1'b0;
               end else begin
                   m_release = 1'b1;
               end
               tick(T_HIGH_NOM);
           end
       endtask

       // A byte plus its acknowledge: nine cells. hold_at selects which cell's low phase stretches.
       task do_byte(input integer hold_at, input integer hold_len);
           begin for (b = 1; b <= 9; b = b + 1) bit_cell((b == hold_at) ? hold_len : 0); end
       endtask

       task do_start();
           begin sda = 1'b1; m_release = 1'b1; tick(10); sda = 1'b0; tick(10); end
       endtask
       task do_stop();
           begin m_release = 1'b0; tick(5); sda = 1'b0; m_release = 1'b1; tick(10);
                 sda = 1'b1; tick(20); end
       endtask

       // A byte's measurement is closed by the falling edge that BEGINS THE NEXT BYTE -- nine periods
       // are only complete at the tenth fall. So `do_byte` returning does not mean the byte has been
       // reported, and a check placed right after it reads the previous byte's entry or none at all.
       // This is exactly the reporting lag of Chapter 11.6 section 5, and the fix is the same: drive
       // a whole START-bytes-STOP sequence, let the STOP's own falling edge close the final byte,
       // and only then read the log. Every check below is written that way, and the log index is
       // absolute rather than relative for the same reason.
       //
       // n_blog after `do_start(); do_byte(); do_byte(); do_stop();` is exactly 2.

       initial begin
           tick(4); rst_n = 1'b1; tick(4);

           // ---- 1. reset ------------------------------------------------------------------------
           if (max_byte_overhead_seen !== {TICK_W{1'b0}} || n_hs_viol !== {TICK_W{1'b0}}) begin
               $display("FAIL: counters nonzero out of reset"); errors = errors + 1; end
           if (n_byte_level !== {TICK_W{1'b0}} || n_bit_level !== {TICK_W{1'b0}}) begin
               $display("FAIL: stretch counters nonzero out of reset"); errors = errors + 1; end

           // ---- 2 and 3. a clean byte, then a 500-tick stretch -----------------------------------
           do_start();
           do_byte(0, 0);          // byte 0: nothing
           do_byte(3, 500);        // byte 1: 500 ticks on bit 3's low phase
           do_stop();
           if (n_blog != 2) begin
               $display("FAIL: %0d bytes reported from two, expected 2", n_blog); errors = errors + 1; end
           else begin
               // 2: the unstretched byte costs exactly its nominal time
               if (bnom[0] != NOM_BYTE) begin
                   $display("FAIL: nominal byte %0d, expected %0d", bnom[0], NOM_BYTE); errors = errors + 1; end
               if (bact[0] != NOM_BYTE) begin
                   $display("FAIL: an unstretched byte took %0d, expected %0d", bact[0], NOM_BYTE);
                   errors = errors + 1; end
               if (bovh[0] != 0) begin
                   $display("FAIL: an unstretched byte reported %0d ticks of overhead", bovh[0]);
                   errors = errors + 1; end
               // 3: the stretch costs exactly what was driven
               if (bovh[1] != 500) begin
                   $display("FAIL: a 500-tick stretch reported %0d ticks of overhead", bovh[1]);
                   errors = errors + 1; end
               if (bact[1] != NOM_BYTE + 500) begin
                   $display("FAIL: actual byte time %0d, expected %0d", bact[1], NOM_BYTE + 500);
                   errors = errors + 1; end
           end

           // ---- 4. the transaction total is the sum, and the per-mille figure is right -----------
           begin
               exp_nom = 2 * NOM_BYTE;
               exp_ovh = 500;
               exp_pm  = (exp_ovh * 1000) / exp_nom;     // 500 on 3420 -> 146 per mille
               if (n_tlog != 1) begin
                   $display("FAIL: %0d transactions reported, expected 1", n_tlog); errors = errors + 1; end
               else begin
                   if (tbytes[0] != 2) begin
                       $display("FAIL: %0d bytes in the transaction, expected 2", tbytes[0]);
                       errors = errors + 1; end
                   if (tnom[0] != exp_nom) begin
                       $display("FAIL: txn nominal %0d, expected %0d", tnom[0], exp_nom); errors = errors + 1; end
                   if (tact[0] != exp_nom + exp_ovh) begin
                       $display("FAIL: txn actual %0d, expected %0d", tact[0], exp_nom + exp_ovh);
                       errors = errors + 1; end
                   if (tovh[0] != exp_ovh) begin
                       $display("FAIL: txn overhead %0d, expected %0d", tovh[0], exp_ovh); errors = errors + 1; end
                   if (tpm[0] != exp_pm) begin
                       $display("FAIL: txn overhead %0d per mille, expected %0d", tpm[0], exp_pm);
                       errors = errors + 1; end
               end
           end

           // ---- 5. byte-level vs bit-level classification --------------------------------------
           // The low phase immediately following an acknowledge is section 3.1.9's byte-level
           // handshake point; any other low phase is bit level.
           begin
               by0 = n_byte_level; bi0 = n_bit_level;
               do_start();
               do_byte(0, 0);       // byte A: clean, ends with its ack
               do_byte(1, 200);     // byte B: the stretch sits on the low phase AFTER that ack
               do_stop();
               if (n_byte_level != by0 + 1) begin
                   $display("FAIL: the post-ack stretch was not counted byte-level (%0d)",
                            n_byte_level - by0); errors = errors + 1; end
               if (n_bit_level != bi0) begin
                   $display("FAIL: a post-ack stretch was ALSO counted bit-level"); errors = errors + 1; end
           end
           begin
               by0 = n_byte_level; bi0 = n_bit_level;
               do_start();
               do_byte(0, 0);
               do_byte(5, 200);     // mid-byte -> bit level
               do_stop();
               if (n_bit_level != bi0 + 1) begin
                   $display("FAIL: the mid-byte stretch was not counted bit-level (%0d)",
                            n_bit_level - bi0); errors = errors + 1; end
               if (n_byte_level != by0) begin
                   $display("FAIL: a mid-byte stretch was ALSO counted byte-level"); errors = errors + 1; end
           end

           // ---- 6. Hs-mode: a BIT-level stretch is a violation ----------------------------------
           // "In Hs-mode, this handshake feature can only be used on byte level." The only normative
           // check in this module that depends on the speed mode.
           begin
               v0 = n_hs_viol;
               hs_mode = 1'b1;
               do_start(); do_byte(0, 0); do_byte(4, 150); do_stop();
               if (n_hs_viol != v0 + 1) begin
                   $display("FAIL: a bit-level stretch in Hs-mode was not flagged (%0d)",
                            n_hs_viol - v0); errors = errors + 1; end
           end

           // ---- 7. Hs-mode: a BYTE-level stretch is legal ---------------------------------------
           // The mirror of test 6, and the test that stops the check degenerating into
           // "hs_mode && any stretch" -- which would pass test 6 and be wrong.
           begin
               v0 = n_hs_viol;
               do_start(); do_byte(0, 0); do_byte(1, 150); do_stop();
               if (n_hs_viol != v0) begin
                   $display("FAIL: a byte-level stretch in Hs-mode was flagged as a violation");
                   errors = errors + 1; end
               hs_mode = 1'b0;
           end

           // ---- 8. the worst-case byte overhead is a MAXIMUM ------------------------------------
           begin
               peak = max_byte_overhead_seen;
               do_start(); do_byte(0, 0); do_byte(2, 100); do_stop();
               if (max_byte_overhead_seen != peak) begin
                   $display("FAIL: max_byte_overhead_seen fell to %0d (peak was %0d)",
                            max_byte_overhead_seen, peak); errors = errors + 1; end
               do_start(); do_byte(0, 0); do_byte(2, 900); do_stop();
               if (max_byte_overhead_seen != 900) begin
                   $display("FAIL: max_byte_overhead_seen = %0d after a 900-tick stretch",
                            max_byte_overhead_seen); errors = errors + 1; end
           end

           // ---- 9. bit-level stretching on EVERY bit: section 3.1.9's rate-matching case ---------
           // Nine 100-tick stretches in one byte is 900 ticks of overhead on a 1710-tick byte -- the
           // case where a slow microcontroller drags the whole bus down to roughly two thirds rate.
           begin
               k0 = n_tlog;
               do_start();
               for (b = 1; b <= 9; b = b + 1) bit_cell(100);
               do_byte(0, 0);          // a second byte so the first is closed inside the transaction
               do_stop();
               if (n_tlog != k0 + 1) begin
                   $display("FAIL: the rate-matched transaction was not reported"); errors = errors + 1; end
               else if (tovh[k0] != 900) begin
                   $display("FAIL: nine 100-tick stretches gave %0d ticks of overhead, expected 900",
                            tovh[k0]); errors = errors + 1; end
               else if (tpm[k0] != (900 * 1000) / (2 * NOM_BYTE)) begin
                   $display("FAIL: per-mille %0d, expected %0d", tpm[k0],
                            (900 * 1000) / (2 * NOM_BYTE)); errors = errors + 1; end
           end

           // ---- 10. a clean transaction reports ZERO overhead -----------------------------------
           // The negative case. An accountant that always found overhead would pass every test above.
           begin
               k0 = n_tlog;
               do_start(); do_byte(0, 0); do_byte(0, 0); do_stop();
               if (tovh[k0] != 0) begin
                   $display("FAIL: a clean transaction reported %0d ticks of overhead", tovh[k0]);
                   errors = errors + 1; end
               if (tpm[k0] != 0) begin
                   $display("FAIL: a clean transaction reported %0d per mille", tpm[k0]); errors = errors + 1; end
               if (tnom[k0] != tact[k0]) begin
                   $display("FAIL: nominal %0d != actual %0d on a clean transaction",
                            tnom[k0], tact[k0]); errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: overhead is measured against the configured phases, byte and transaction totals decompose, Hs-mode permits byte-level stretching only, a clean transaction costs nothing");
           else
               $display("FAIL: %0d error(s)", errors);
           $finish;
       end

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_accountant.vhd — the same accountant in VHDL
   -- WHAT STRETCHING COSTS, AND WHERE IT IS ALLOWED -- the VHDL form. Same two accounting levels and
   -- the same normative compliance check:
   --
   --   UM10204 section 3.1.9: "In Hs-mode, this handshake feature can only be used on byte level."
   --
   -- So a bit-level stretch is legal in Standard, Fast and Fast-mode Plus and ILLEGAL in Hs-mode, and
   -- the block raises a violation rather than silently accounting for it.

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_stretch_accountant is
       generic (
           TICK_W     : natural := 24;
           T_LOW_NOM  : natural := 130;
           T_HIGH_NOM : natural := 60
       );
       port (
           clk         : in  std_logic;
           rst_n       : in  std_logic;

           scl_release : in  std_logic;
           scl_in      : in  std_logic;
           sda_in      : in  std_logic;
           hs_mode     : in  std_logic;

           byte_valid      : out std_logic;
           t_byte_nominal  : out unsigned(TICK_W-1 downto 0);
           t_byte_actual   : out unsigned(TICK_W-1 downto 0);
           t_byte_overhead : out unsigned(TICK_W-1 downto 0);

           txn_valid             : out std_logic;
           txn_bytes             : out unsigned(TICK_W-1 downto 0);
           txn_nominal           : out unsigned(TICK_W-1 downto 0);
           txn_actual            : out unsigned(TICK_W-1 downto 0);
           txn_overhead          : out unsigned(TICK_W-1 downto 0);
           txn_overhead_permille : out unsigned(TICK_W-1 downto 0);

           n_byte_level : out unsigned(TICK_W-1 downto 0);
           n_bit_level  : out unsigned(TICK_W-1 downto 0);

           viol_hs_bit_level : out std_logic;
           n_hs_viol         : out unsigned(TICK_W-1 downto 0);

           max_byte_overhead_seen : out unsigned(TICK_W-1 downto 0)
       );
   end entity;

   architecture rtl of i2c_stretch_accountant is

       constant NOM_BYTE : unsigned(TICK_W-1 downto 0) :=
           to_unsigned(9 * (T_LOW_NOM + T_HIGH_NOM), TICK_W);

       signal sda_q, scl_q : std_logic := '1';

       signal s_in_transfer : std_logic := '0';
       signal bit_of_byte   : unsigned(3 downto 0) := (others => '0');
       signal post_ack      : std_logic := '0';
       signal stretching    : std_logic := '0';

       signal byte_ticks  : unsigned(TICK_W-1 downto 0) := (others => '0');
       signal acc_bytes   : unsigned(TICK_W-1 downto 0) := (others => '0');
       signal acc_nominal : unsigned(TICK_W-1 downto 0) := (others => '0');
       signal acc_actual  : unsigned(TICK_W-1 downto 0) := (others => '0');

       signal r_nbyte, r_nbit, r_nhs, r_maxovh : unsigned(TICK_W-1 downto 0) := (others => '0');

       signal scl_fall_s, sda_rise_s, sda_fall_s : std_logic;
       signal start_s, stop_s, stretch_now_s     : std_logic;
       signal byte_ticks_now                     : unsigned(TICK_W-1 downto 0);

   begin

       scl_fall_s <= '1' when (scl_in = '0' and scl_q = '1') else '0';
       sda_rise_s <= '1' when (sda_in = '1' and sda_q = '0') else '0';
       sda_fall_s <= '1' when (sda_in = '0' and sda_q = '1') else '0';
       start_s    <= '1' when (sda_fall_s = '1' and scl_in = '1') else '0';
       stop_s     <= '1' when (sda_rise_s = '1' and scl_in = '1') else '0';

       stretch_now_s  <= '1' when (scl_release = '1' and scl_in = '0') else '0';
       byte_ticks_now <= byte_ticks + 1;

       n_byte_level           <= r_nbyte;
       n_bit_level            <= r_nbit;
       n_hs_viol              <= r_nhs;
       max_byte_overhead_seen <= r_maxovh;

       process (clk) is
           -- A wide intermediate for the per-mille figure. The quotient is computed once per
           -- transaction and never feeds logic, so the cost is acceptable in a MEASUREMENT block
           -- where it would not be on a bit path.
           variable ovh  : unsigned(TICK_W-1 downto 0);
           variable wide : unsigned(2*TICK_W-1 downto 0);
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   sda_q                 <= '1';
                   scl_q                 <= '1';
                   s_in_transfer         <= '0';
                   bit_of_byte           <= (others => '0');
                   post_ack              <= '0';
                   stretching            <= '0';
                   byte_ticks            <= (others => '0');
                   acc_bytes             <= (others => '0');
                   acc_nominal           <= (others => '0');
                   acc_actual            <= (others => '0');
                   byte_valid            <= '0';
                   t_byte_nominal        <= (others => '0');
                   t_byte_actual         <= (others => '0');
                   t_byte_overhead       <= (others => '0');
                   txn_valid             <= '0';
                   txn_bytes             <= (others => '0');
                   txn_nominal           <= (others => '0');
                   txn_actual            <= (others => '0');
                   txn_overhead          <= (others => '0');
                   txn_overhead_permille <= (others => '0');
                   r_nbyte               <= (others => '0');
                   r_nbit                <= (others => '0');
                   r_nhs                 <= (others => '0');
                   viol_hs_bit_level     <= '0';
                   r_maxovh              <= (others => '0');
               else
                   sda_q      <= sda_in;
                   scl_q      <= scl_in;
                   byte_valid <= '0';
                   txn_valid  <= '0';

                   if start_s = '1' then
                       s_in_transfer <= '1';
                       bit_of_byte   <= (others => '0');
                       post_ack      <= '0';
                       byte_ticks    <= (others => '0');
                       acc_bytes     <= (others => '0');
                       acc_nominal   <= (others => '0');
                       acc_actual    <= (others => '0');

                   elsif stop_s = '1' then
                       -- A transaction's nominal time is the SUM of its bytes' nominal times. A
                       -- start-to-stop subtraction cannot be decomposed, and "which byte was slow" is
                       -- the question a report has to answer.
                       s_in_transfer <= '0';
                       txn_valid     <= '1';
                       txn_bytes     <= acc_bytes;
                       txn_nominal   <= acc_nominal;
                       txn_actual    <= acc_actual;
                       if acc_actual > acc_nominal then
                           ovh := acc_actual - acc_nominal;
                       else
                           ovh := (others => '0');
                       end if;
                       txn_overhead <= ovh;
                       if acc_nominal /= 0 then
                           -- ovh is TICK_W bits and the literal is TICK_W bits, so the product is
                           -- exactly 2*TICK_W -- VHDL sizes a multiply as the sum of its operands'
                           -- widths, and resizing an operand first makes the product too wide.
                           wide := ovh * to_unsigned(1000, TICK_W);
                           txn_overhead_permille <= resize(wide / resize(acc_nominal, 2*TICK_W), TICK_W);
                       else
                           txn_overhead_permille <= (others => '0');
                       end if;
                       bit_of_byte <= (others => '0');
                       post_ack    <= '0';

                   elsif s_in_transfer = '1' then
                       -- Only count once the first low phase has begun. The gap between the START and
                       -- that first falling edge is tHD;STA and belongs to the framing, not the byte.
                       if scl_fall_s = '0' and bit_of_byte /= to_unsigned(0, 4) then
                           byte_ticks <= byte_ticks_now;
                       end if;

                       if scl_fall_s = '1' then
                           if bit_of_byte = to_unsigned(9, 4) then
                               byte_valid     <= '1';
                               t_byte_nominal <= NOM_BYTE;
                               t_byte_actual  <= byte_ticks_now;
                               if byte_ticks_now > NOM_BYTE then
                                   ovh := byte_ticks_now - NOM_BYTE;
                               else
                                   ovh := (others => '0');
                               end if;
                               t_byte_overhead <= ovh;
                               if ovh > r_maxovh then
                                   r_maxovh <= ovh;
                               end if;

                               acc_bytes   <= acc_bytes   + 1;
                               acc_nominal <= acc_nominal + NOM_BYTE;
                               acc_actual  <= acc_actual  + byte_ticks_now;

                               byte_ticks  <= (others => '0');
                               bit_of_byte <= to_unsigned(1, 4);
                               post_ack    <= '1';
                           else
                               bit_of_byte <= bit_of_byte + 1;
                               post_ack    <= '0';
                               if bit_of_byte = to_unsigned(0, 4) then
                                   byte_ticks <= (others => '0');
                               else
                                   byte_ticks <= byte_ticks_now;
                               end if;
                           end if;
                       end if;
                   end if;

                   -- the stretch, classified and checked against the Hs-mode restriction
                   viol_hs_bit_level <= '0';
                   if stretch_now_s = '1' and stretching = '0' then
                       stretching <= '1';
                       if post_ack = '1' then
                           r_nbyte <= r_nbyte + 1;
                       else
                           r_nbit <= r_nbit + 1;
                           if hs_mode = '1' then
                               viol_hs_bit_level <= '1';
                               r_nhs             <= r_nhs + 1;
                           end if;
                       end if;
                   elsif stretch_now_s = '0' then
                       stretching <= '0';
                   end if;
               end if;
           end if;
       end process;

   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_accountant_tb.vhd — the VHDL testbench, single-writer throughout
   -- The VHDL testbench for the stretch accountant. Same ten checks, same structure -- and in
   -- particular the same handling of the REPORTING LAG: a byte's measurement is closed by the falling
   -- edge that begins the NEXT byte, so every check drives a whole START-bytes-STOP sequence, lets the
   -- STOP's own falling edge close the final byte, and only then reads the log by absolute index.

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_stretch_accountant_tb is
   end entity;

   architecture tb of i2c_stretch_accountant_tb is

       constant TICK_W     : natural := 24;
       constant T_LOW_NOM  : natural := 130;
       constant T_HIGH_NOM : natural := 60;
       constant NOM_BYTE   : integer := 9 * (T_LOW_NOM + T_HIGH_NOM);   -- 1710

       signal clk   : std_logic := '0';
       signal rst_n : std_logic := '0';

       signal m_release : std_logic := '1';
       signal s_hold    : std_logic := '0';
       signal sda       : std_logic := '1';
       signal hs_mode   : std_logic := '0';
       signal scl_line  : std_logic;

       signal byte_valid, txn_valid, viol_hs_bit_level : std_logic;
       signal t_byte_nominal, t_byte_actual, t_byte_overhead : unsigned(TICK_W-1 downto 0);
       signal txn_bytes, txn_nominal, txn_actual : unsigned(TICK_W-1 downto 0);
       signal txn_overhead, txn_overhead_permille : unsigned(TICK_W-1 downto 0);
       signal n_byte_level, n_bit_level, n_hs_viol : unsigned(TICK_W-1 downto 0);
       signal max_byte_overhead_seen : unsigned(TICK_W-1 downto 0);

       signal done   : boolean := false;
       signal errors : integer := 0;

       type int_arr is array (0 to 63) of integer;
       signal bnom, bact, bovh : int_arr := (others => 0);
       signal n_blog : integer := 0;
       type t_arr is array (0 to 15) of integer;
       signal tbytes, tnom, tact, tovh, tpm : t_arr := (others => 0);
       signal n_tlog : integer := 0;

   begin

       scl_line <= m_release and (not s_hold);

       clk_gen : process is
       begin
           while not done loop
               clk <= '0'; wait for 5 ns;
               clk <= '1'; wait for 5 ns;
           end loop;
           wait;
       end process;

       dut : entity work.i2c_stretch_accountant
           generic map (TICK_W => TICK_W, T_LOW_NOM => T_LOW_NOM, T_HIGH_NOM => T_HIGH_NOM)
           port map (
               clk => clk, rst_n => rst_n,
               scl_release => m_release, scl_in => scl_line, sda_in => sda, hs_mode => hs_mode,
               byte_valid => byte_valid, t_byte_nominal => t_byte_nominal,
               t_byte_actual => t_byte_actual, t_byte_overhead => t_byte_overhead,
               txn_valid => txn_valid, txn_bytes => txn_bytes, txn_nominal => txn_nominal,
               txn_actual => txn_actual, txn_overhead => txn_overhead,
               txn_overhead_permille => txn_overhead_permille,
               n_byte_level => n_byte_level, n_bit_level => n_bit_level,
               viol_hs_bit_level => viol_hs_bit_level, n_hs_viol => n_hs_viol,
               max_byte_overhead_seen => max_byte_overhead_seen);

       blogger : process (clk) is
       begin
           if rising_edge(clk) then
               if rst_n = '1' and byte_valid = '1' and n_blog < 64 then
                   bnom(n_blog) <= to_integer(t_byte_nominal);
                   bact(n_blog) <= to_integer(t_byte_actual);
                   bovh(n_blog) <= to_integer(t_byte_overhead);
                   n_blog       <= n_blog + 1;
               end if;
           end if;
       end process;

       tlogger : process (clk) is
       begin
           if rising_edge(clk) then
               if rst_n = '1' and txn_valid = '1' and n_tlog < 16 then
                   tbytes(n_tlog) <= to_integer(txn_bytes);
                   tnom(n_tlog)   <= to_integer(txn_nominal);
                   tact(n_tlog)   <= to_integer(txn_actual);
                   tovh(n_tlog)   <= to_integer(txn_overhead);
                   tpm(n_tlog)    <= to_integer(txn_overhead_permille);
                   n_tlog         <= n_tlog + 1;
               end if;
           end if;
       end process;

       stim : process is

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

           procedure bit_cell(hold : in integer) is
           begin
               m_release <= '0';
               tick(T_LOW_NOM);
               if hold > 0 then
                   s_hold <= '1'; m_release <= '1'; tick(hold); s_hold <= '0';
               else
                   m_release <= '1';
               end if;
               tick(T_HIGH_NOM);
           end procedure;

           procedure do_byte(hold_at, hold_len : in integer) is
           begin
               for b in 1 to 9 loop
                   if b = hold_at then bit_cell(hold_len); else bit_cell(0); end if;
               end loop;
           end procedure;

           procedure do_start is
           begin
               sda <= '1'; m_release <= '1'; tick(10);
               sda <= '0'; tick(10);
           end procedure;

           procedure do_stop is
           begin
               m_release <= '0'; tick(5);
               sda <= '0'; m_release <= '1'; tick(10);
               sda <= '1'; tick(20);
           end procedure;

           procedure chk(cond : in boolean; msg : in string) is
           begin
               if not cond then
                   report "FAIL: " & msg severity error;
                   errors <= errors + 1;
                   wait for 0 ns;
               end if;
           end procedure;

           variable by0, bi0, v0, peak, k0, exp_nom, exp_ovh, exp_pm : integer;

       begin
           tick(4); rst_n <= '1'; tick(4);

           -- 1. reset
           chk(max_byte_overhead_seen = 0 and n_hs_viol = 0, "counters nonzero out of reset");
           chk(n_byte_level = 0 and n_bit_level = 0, "stretch counters nonzero out of reset");

           -- 2 and 3. a clean byte, then a 500-tick stretch
           do_start;
           do_byte(0, 0);
           do_byte(3, 500);
           do_stop;
           chk(n_blog = 2, "the wrong number of bytes was reported");
           chk(bnom(0) = NOM_BYTE, "the nominal byte time is wrong");
           chk(bact(0) = NOM_BYTE, "an unstretched byte did not take its nominal time");
           chk(bovh(0) = 0, "an unstretched byte reported overhead");
           chk(bovh(1) = 500, "a 500-tick stretch reported the wrong overhead");
           chk(bact(1) = NOM_BYTE + 500, "the stretched byte's actual time is wrong");

           -- 4. the transaction total is the sum, and the per-mille figure is right
           exp_nom := 2 * NOM_BYTE;
           exp_ovh := 500;
           exp_pm  := (exp_ovh * 1000) / exp_nom;      -- 500 on 3420 -> 146 per mille
           chk(n_tlog = 1, "the wrong number of transactions was reported");
           chk(tbytes(0) = 2, "the transaction byte count is wrong");
           chk(tnom(0) = exp_nom, "the transaction nominal time is wrong");
           chk(tact(0) = exp_nom + exp_ovh, "the transaction actual time is wrong");
           chk(tovh(0) = exp_ovh, "the transaction overhead is wrong");
           chk(tpm(0) = exp_pm, "the per-mille overhead is wrong");

           -- 5. byte-level vs bit-level classification
           by0 := to_integer(n_byte_level); bi0 := to_integer(n_bit_level);
           do_start; do_byte(0, 0); do_byte(1, 200); do_stop;
           chk(to_integer(n_byte_level) = by0 + 1, "the post-ack stretch was not counted byte-level");
           chk(to_integer(n_bit_level) = bi0, "a post-ack stretch was ALSO counted bit-level");

           by0 := to_integer(n_byte_level); bi0 := to_integer(n_bit_level);
           do_start; do_byte(0, 0); do_byte(5, 200); do_stop;
           chk(to_integer(n_bit_level) = bi0 + 1, "the mid-byte stretch was not counted bit-level");
           chk(to_integer(n_byte_level) = by0, "a mid-byte stretch was ALSO counted byte-level");

           -- 6. Hs-mode: a BIT-level stretch is a violation
           v0 := to_integer(n_hs_viol);
           hs_mode <= '1';
           do_start; do_byte(0, 0); do_byte(4, 150); do_stop;
           chk(to_integer(n_hs_viol) = v0 + 1, "a bit-level stretch in Hs-mode was not flagged");

           -- 7. Hs-mode: a BYTE-level stretch is legal. Stops the check degenerating into
           --    "hs_mode and any stretch", which would pass test 6 and be wrong.
           v0 := to_integer(n_hs_viol);
           do_start; do_byte(0, 0); do_byte(1, 150); do_stop;
           chk(to_integer(n_hs_viol) = v0, "a byte-level stretch in Hs-mode was flagged");
           hs_mode <= '0';

           -- 8. the worst-case byte overhead is a MAXIMUM
           peak := to_integer(max_byte_overhead_seen);
           do_start; do_byte(0, 0); do_byte(2, 100); do_stop;
           chk(to_integer(max_byte_overhead_seen) = peak, "max_byte_overhead_seen fell");
           do_start; do_byte(0, 0); do_byte(2, 900); do_stop;
           chk(to_integer(max_byte_overhead_seen) = 900, "max_byte_overhead_seen wrong after 900");

           -- 9. bit-level stretching on EVERY bit: section 3.1.9's rate-matching case
           k0 := n_tlog;
           do_start;
           for b in 1 to 9 loop bit_cell(100); end loop;
           do_byte(0, 0);
           do_stop;
           chk(n_tlog = k0 + 1, "the rate-matched transaction was not reported");
           chk(tovh(k0) = 900, "nine 100-tick stretches gave the wrong overhead");
           chk(tpm(k0) = (900 * 1000) / (2 * NOM_BYTE), "the rate-matched per-mille figure is wrong");

           -- 10. a clean transaction reports ZERO overhead
           k0 := n_tlog;
           do_start; do_byte(0, 0); do_byte(0, 0); do_stop;
           chk(tovh(k0) = 0, "a clean transaction reported overhead");
           chk(tpm(k0) = 0, "a clean transaction reported a per-mille figure");
           chk(tnom(k0) = tact(k0), "nominal /= actual on a clean transaction");

           if errors = 0 then
               report "i2c_stretch_accountant self-check complete: overhead is measured against the configured phases, byte and transaction totals decompose, Hs-mode permits byte-level stretching only, a clean transaction costs nothing" severity note;
           else
               report "FAILURES in i2c_stretch_accountant" severity error;
           end if;

           done <= true;
           wait;
       end process;

   end architecture;

6a. Five Decisions Worth Defending

Nominal is computed from the master's configured phases, not from a specification minimum. §4's argument and Chapter 11.9 §3's modelling error. The question is "what did this stretch cost this bus", and only the configured phases answer it.

The transaction total is accumulated from per-byte totals, not measured start-to-stop. A start-to-stop subtraction gives the same number and cannot be decomposed — and "which byte was slow" is the question a report has to answer. The accumulators cost three registers and make the per-byte breakdown available for free.

The byte's measurement window opens at the first falling edge, not at the START. The interval between a START and the first low phase is tHD;STA (Chapter 11.5), which belongs to the framing rather than to the byte. Charging it to the byte inflates every first byte by however long the master's START sequence took — mutation L3, and §7's test 2 catches it at a one-tick resolution.

Byte level and bit level are counted separately, and the Hs-mode restriction is checked against the distinction. §3's mechanism means the check cannot be "any stretch in Hs-mode": byte-level stretching is legal there. Mutation L2 collapses the distinction and §7's test 7 kills it.

The per-mille divide is guarded and lives in a measurement block. A divide is acceptable here because it happens once per transaction and never feeds logic — the reasoning Chapter 11.9 §10 applies to its own arithmetic. The guard against a zero nominal matters because a transaction can legitimately contain zero complete bytes.

6b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o d3 i2c_stretch_accountant.sv i2c_stretch_accountant_tb.sv && ./d3
   PASS: overhead is measured against the configured phases, byte and transaction totals
   decompose, Hs-mode permits byte-level stretching only, a clean transaction costs nothing
   i2c_stretch_accountant_tb.sv:258: $finish called at 343830000 (1ps)

   $ iverilog -g2005 -o v3 i2c_stretch_accountant.v i2c_stretch_accountant_tb.v && ./v3
   PASS: overhead is measured against the configured phases, byte and transaction totals
   decompose, Hs-mode permits byte-level stretching only, a clean transaction costs nothing
   i2c_stretch_accountant_tb.v:280: $finish called at 343830000 (1ps)

   $ nvc -a i2c_stretch_accountant.vhd i2c_stretch_accountant_tb.vhd
   $ nvc -e i2c_stretch_accountant_tb && nvc -r i2c_stretch_accountant_tb --stop-time=5000us
   ** Note: 343830ns+1: i2c_stretch_accountant self-check complete: overhead is measured
      against the configured phases, byte and transaction totals decompose, Hs-mode permits
      byte-level stretching only, a clean transaction costs nothing

All three at 343830 ns — the longest run in the module, because pricing stretching requires actually spending the time.

7. What the Testbench Proves

Every expected overhead is the stretch length that was driven, so what is under test is the block's arithmetic rather than the testbench's.

#stimuluswhat it establishes
1resetall counters zero
2a clean bytecosts exactly its nominal 1710 ticks; zero overhead
3a 500-tick stretchoverhead is exactly 500; actual is nominal + 500
4the transactiontotals are the sum; per-mille is 146 on two bytes
5a post-acknowledge stretch, then a mid-byte oneclassified byte then bit, and neither also counts as the other
6a bit-level stretch with hs_mode setviolates §3.1.9's restriction
7a byte-level stretch with hs_mode setlegal — no violation
8a smaller then a larger stretchthe worst case holds, then advances to 900
9a 100-tick stretch on all nine bits900 ticks of overhead; the rate-matching case
10a clean transactionzero overhead, zero per-mille, nominal equals actual

Test 7 is the test that stops the Hs-mode check degenerating. A block that flagged any stretch while hs_mode was set would pass test 6 perfectly and be wrong, because §3 establishes that byte-level stretching is exactly what Hs-mode permits. The pair 6-and-7 is the only thing that distinguishes a correct check from a blanket one — and it is mutation L2's killer.

Test 10 is the negative case, and it is load-bearing. An accountant that always found overhead would pass tests 2 through 9. Requiring a clean transaction to report zero on all three of overhead, per-mille and nominal-equals-actual is what makes the other nine tests mean something.

Test 2 catches a one-tick error, which is why it asserts the exact nominal. Mutation L3 starts the byte timer at the START rather than the first falling edge, and the resulting inflation is exactly the length of the master's START sequence — one tick in this stimulus. A test comparing against a range would miss it entirely; the failure message is an unstretched byte took 1711, expected 1710.

Test 9 is §4's rate-matching case with numbers on it. Nine 100-tick stretches on a 1710-tick byte is 900 ticks of overhead, and the per-mille figure over the two-byte transaction makes the shape visible: this is not a bus with a tax on it, it is a bus running slower.

8. Mutation Testing

Six defects injected into the SystemVerilog accountant.

#injected defectoutcome
L1the Hs-mode byte-level-only restriction is not checkedkilled — test 6
L2the Hs-mode check fires on any stretch, not only bit levelkilled — test 7
L3the byte timer starts at the START, not the first falling edgekilled — test 2
L4the transaction overhead is computed without its underflow guardkilled — test 3
L5the worst-case byte overhead tracks the latest valuekilled — test 8
L6the per-mille figure divides by the actual time, not the nominalkilled — test 4

Six injected, six killed. Three notes.

L1 and L2 are a matched pair and together they define the check. L1 removes the Hs-mode restriction entirely; L2 over-applies it. Test 6 kills the first and test 7 kills the second, and a suite containing only one of those tests would accept one of the two mutations. Whenever a check is conditional, the suite needs a case on each side of the condition — otherwise "always" and "never" are both consistent with the tests.

L6 is the mutation that looks harmless and is not. Dividing the overhead by the actual time instead of the nominal produces a smaller, plausible-looking percentage — 127 per mille instead of 146. Nothing about it is obviously wrong, and it would pass any review that did not recompute the figure by hand. What catches it is test 4 asserting the per-mille value against an expression the testbench evaluates independently, rather than against a constant somebody transcribed.

That is a general point about reported ratios: a ratio has two plausible denominators and the wrong one is never obviously wrong. The fix is for the test to compute the expected value from first principles at the call site.

L4 is the underflow guard, and it matters because the subtraction is unsigned. Without the guard, a transaction whose actual time is somehow below nominal wraps to an enormous positive overhead — the same failure mode Chapter 11.9 §5 designs against with its two-unsigned-outputs structure. Here the injected form adds one tick, which test 3 catches because it asserts an exact value.

9. Verification Connection — Pricing, and a Normative Check

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_cost_props.sv — the Hs-mode restriction as an assertion
   // The only normative property in this module that depends on the speed mode. Section 3.1.9:
   // "In Hs-mode, this handshake feature can only be used on byte level." Note the antecedent
   // carries BOTH conditions -- a property written as `hs_mode |-> !stretching` would forbid the
   // byte-level handshake that Hs-mode explicitly permits, which is mutation L2.
   property p_hs_byte_level_only;
      @(posedge clk) (hs_mode && stretch_begin && !post_ack) |-> 1'b0;
   endproperty
   assert property (p_hs_byte_level_only)
      else $error("a bit-level stretch occurred in Hs-mode -- section 3.1.9 permits byte level only");

   // And the matching POSITIVE property, because a checker that forbade all Hs-mode stretching
   // would satisfy the property above perfectly. Both are needed: one bounds what is illegal, the
   // other protects what is legal.
   property p_hs_byte_level_permitted;
      @(posedge clk) (hs_mode && stretch_begin && post_ack) |-> !viol_hs_bit_level;
   endproperty
   assert property (p_hs_byte_level_permitted)
      else $error("a byte-level stretch in Hs-mode was flagged -- that is the one position Hs-mode allows");

   // Framing is never stretched, which section 1 derives structurally: a START and a STOP are SDA
   // transitions while SCL is HIGH, so there is no low phase to hold. This property states it as a
   // fact a monitor can rely on -- and Chapter 12.4 relies on it.
   property p_framing_never_stretched;
      @(posedge clk) (start_det || stop_det) |-> !stretching;
   endproperty
   assert property (p_framing_never_stretched)
      else $error("a stretch was reported during a framing event -- framing happens with SCL HIGH");

   // A cost property rather than a correctness one, and it belongs in the environment rather than
   // in the DUT: the transaction's accounted time must reconcile. An accountant whose totals do
   // not add up is worse than no accountant, because its numbers get quoted.
   property p_totals_reconcile;
      @(posedge clk) txn_valid |-> (txn_actual == txn_nominal + txn_overhead);
   endproperty
   assert property (p_totals_reconcile)
      else $error("the transaction totals do not reconcile: actual != nominal + overhead");
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_stretch_cost_cov.sv — the cost regimes, which are orders of magnitude apart
   covergroup i2c_cost_cg with function sample(int overhead, int nominal, int permille,
                                              bit byte_lvl, bit bit_lvl, int mode);
      // Overhead as a SHARE of the byte, binned across the regimes section 4 identifies. Linear
      // bins are useless here: a logic handshake and an EEPROM write differ by 10000x.
      cost: coverpoint permille {
         bins free        = {0};                 // no stretching -- must be covered
         bins light       = {[1:99]};            // under 10 %
         bins moderate    = {[100:499]};         // a byte-level handshake
         bins heavy       = {[500:2000]};        // bit-level rate matching
         bins other_scale = {[2001:$]};          // an EEPROM write: not a percentage any more
      }

      // The two levels, and the cross that section 4's callout is about: bit-level stretching is a
      // property of the BUS rather than of a transfer, so a suite that only ever exercises
      // byte-level stretching has not seen the regime where unrelated transfers are slowed.
      level: coverpoint {byte_lvl, bit_lvl} {
         bins none     = {2'b00};
         bins byte_only = {2'b10};
         bins bit_only  = {2'b01};
         bins both      = {2'b11};
      }
      cost_x_level: cross cost, level;

      // Speed mode, because the LEGALITY of one level depends on it. The illegal cell -- bit-level
      // stretching in Hs-mode -- must be reachable in an error-injection run and must stay empty
      // in regression, which is the same discipline Chapter 11.7 section 9 applies to its
      // empty-window cells.
      speed: coverpoint mode {
         bins std_fast_fmplus = {0};
         bins hs              = {1};
      }
      level_x_speed: cross level, speed;
   endgroup

10. FPGA and ASIC Implications

The accountant is four accumulators, a bit-position counter and one divider — around 180 flops at TICK_W = 24, plus the divide. The divider is the only expensive part and it is avoidable: reporting raw nominal and actual ticks and letting software form the ratio costs nothing and loses nothing, because §5's point is that the ratio is the less useful of the two numbers.

Size the accumulators for a whole transaction, not a byte. A 32-byte page write to an EEPROM with a 3 ms per-page programming time accumulates tens of milliseconds — 24 bits covers 168 ms at 100 MHz, which is adequate, but a 16-bit accumulator overflows inside a single page.

This block is a bring-up and characterisation instrument, not a shipping feature. Its value is in answering "what is this bus actually costing us" during integration, and in a regression that has a throughput requirement. On a shipped product the useful residue is usually just two numbers: the maximum per-byte overhead and the total, which is a small fraction of this logic.

The Hs-mode check is the part worth keeping in silicon, because it is a compliance statement rather than a measurement. It costs one comparison and one flag, and it catches a class of slave misbehaviour that is otherwise invisible: an Hs-mode slave stretching mid-byte is fighting the master's current source, and the electrical symptom of that is far harder to diagnose than a flag.

And report the two levels separately. §4's callout is the reason: n_bit_level being non-zero says something about the whole bus that n_byte_level does not, and a merged count hides it.

11. Debugging — The Bus That Was Slower Than the Sum of Its Parts

Pitfall — one bit-level stretching slave taxing every other device's transfers
Buggy Code
// A Fast-mode instrument bus: eight devices, all characterised individually during bring-up.
//
//     four ADCs        measured   ~40 us per 2-byte read   (nominal 34 us + their own handshakes)
//     two DACs         measured   ~19 us per 1-byte write  (nominal 17 us)
//     an EEPROM        measured   ~3 ms  per page write     (a byte-level stretch, expected)
//     a small MCU      measured   ~95 us per 2-byte read   (slow, known, accepted)
//
// The MCU was a software I2C slave -- no hardware I2C peripheral, so its slave behaviour was an
// interrupt handler that extended EVERY clock low period while it ran. Section 3.1.9's bit-level
// case exactly:
//
//     "a device such as a microcontroller with or without limited hardware for the I2C-bus, can
//      slow down the bus clock by extending each clock LOW period. The speed of any master is
//      adapted to the internal operating rate of this device."
//
// Every device was within its own budget. The MCU was slow and everyone knew it, and its slowness
// was accounted for in the schedule as 95 us of its own transfers.
Symptom

The scheduler needed a 1 kHz loop reading all four ADCs. The arithmetic said it fit easily: four reads at 40 us is 160 us out of a 1000 us budget, with the DACs and the occasional MCU poll adding maybe another 150 us. Under a third utilised.

Measured, the loop took 780 us and missed its deadline whenever an EEPROM write overlapped.

The first investigation measured each device again, in isolation, and got the original numbers back. Four ADCs at 40 us. The individual measurements were reproducible and correct.

The second theory was scheduler overhead -- the driver, the interrupt latency, the queueing. All were instrumented and all were small.

What made no sense was that measuring the four ADC reads AS A GROUP gave 620 us rather than 160 us, and the group contained nothing but ADC reads. The same four transfers, four times slower together than apart.

The accountant of section 6, instantiated on the bus, reported it in one run:

n_byte_level = 3 (the EEPROM, as expected) n_bit_level = 2847 (... on a bus whose only bit-level device was the MCU)

2847 bit-level stretches, in transfers the MCU was not party to.

The MCU's interrupt handler ran on every START it saw -- because a slave cannot know whether it is being addressed until it has received the address byte, and to receive it the handler has to run. While it ran, it extended every low period, including the address bits and the payload of transfers addressed to the ADCs.

It was slowing every transfer on the bus, and no per-device measurement could see it, because in isolation there was no other traffic for it to slow.

Root Cause

A bit-level stretching slave extends every low period it observes, and it observes all of them. Byte-level stretching can only occur after an acknowledge, so a byte-level device has already been addressed and only ever taxes its own transfers. Bit-level stretching happens BEFORE the address is decoded, so it taxes everything.

The per-device characterisation was correct and could not have found this: measuring a device in isolation removes exactly the traffic that the slow device was slowing. The cost was a property of the BUS -- an interaction between two devices that never exchanged a byte with each other.

And the schedule's arithmetic was built by summing per-device measurements, which is a valid model only if devices do not interfere. Bit-level stretching is precisely interference.

12. Common Misconceptions

"A slave can stretch anywhere it likes." Byte level is the low phase after an acknowledge; bit level is any low phase — and in Hs-mode only the first is permitted. Framing cannot be stretched at all, because it happens with SCL high.

"A slave could hold the bus during a START." There is no low phase inside a START to hold. Framing always completes, which is what makes Chapter 12.4's recovery reasoning possible.

"Hs-mode forbids bit-level stretching as a rule imposed on slaves." It is a description of the electrical arrangement: the master drives SCL with a current source and disables it only after the acknowledge, so that is the only interval in which SCL is genuinely released.

"Byte-level and bit-level stretching cost the same if the total time is the same." The totals can match while the shapes differ completely: one is a bounded per-byte tax, the other is the bus running at the slave's rate — and only the second slows other devices' transfers.

"Stretching only slows the device that stretches." True of byte level, false of bit level. §11 is 2847 bit-level stretches in transfers the slow device was not addressed by.

"Overhead as a percentage is the useful figure." It is a throughput statement. An EEPROM write at 17500 % is not usefully a percentage; what matters there is the tick count, which is a latency statement.

"Per-device characterisation is sufficient." It cannot see bus-wide interference, because measuring one device removes the traffic it interferes with. A group measurement is a different experiment.

"Nominal time should be the specification minimum." It should be what this master is configured for. Comparing against a spec minimum answers a question nobody asked — Chapter 11.9 §3's modelling error.

13. Reason It Through

Why can a slave not stretch during a START condition?

Because a START is an SDA transition while SCL is high, so there is no low phase to hold. The same is true of a repeated START and a STOP, which is why framing always completes however badly a slave behaves with the clock.

A bus has one slow slave and aggregate throughput far below the sum of its devices, with no device looking slow individually. What is the mechanism?

Bit-level stretching. The slow device extends every low period it observes, and it observes all of them — including the address bits of transfers addressed elsewhere, because it cannot know whether a transfer concerns it until it has received the address. Per-device measurement cannot see this, because in isolation there is no other traffic to slow.

Why does Hs-mode permit byte-level but not bit-level stretching?

Because the Hs-mode master drives SCL high with a current source and disables it only after each acknowledge. That interval is the only time SCL is actually released, so it is the only time a slave can hold it low without fighting an active driver.

A test flags every stretch that occurs while hs_mode is set, and passes the bit-level violation test. What is wrong?

It forbids byte-level stretching, which Hs-mode explicitly permits. The check needs a case on each side of its condition: one stimulus that must be flagged and one that must not. That pair is the only thing distinguishing a correct conditional check from a blanket one.

A reported overhead of 127 per mille is wrong and 146 is right. What kind of error produces that, and how is it caught?

Dividing by the actual time rather than the nominal. It produces a smaller, entirely plausible number that no review would question. It is caught only by a test that computes the expected ratio from first principles at the call site rather than comparing against a transcribed constant — a ratio has two plausible denominators and the wrong one is never obviously wrong.

Why does the byte's measurement window open at the first falling edge rather than at the START?

Because the interval between the START and the first low phase is tHD;STA, which belongs to the framing. Charging it to the byte inflates every first-byte measurement by the length of the master's START sequence — and the error is small enough that only an exact-value assertion finds it.

14. Understanding Check

15. Summary

There are two legal positions and one impossibility. Byte level is the low phase after an acknowledge; bit level is any low phase; framing cannot be stretched at all because it happens with SCL high — so a START and a STOP always complete.

Hs-mode permits byte level only, for a mechanical reason. Its master drives SCL with a current source and disables it only after the acknowledge, so that is the sole interval in which the line is genuinely released.

Byte-level stretching is a bounded tax; bit-level stretching is a rate change. The same total cost can have completely different shapes, and only the second slows transfers the slow device is not part of — because it acts before the address is decoded.

Price against the master's configured phases, never against a specification minimum.

Report ticks as well as ratios. A percentage is a throughput statement and a tick count is a latency statement, and at EEPROM scale only the second means anything.

Latency is unbounded by the protocol, so any bound a design needs comes from a datasheet or a system contract.

A conditional check needs a case on each side of its condition, or "always" and "never" both pass.

And per-device characterisation cannot find bus-wide interference, because measuring a device alone removes the traffic it was slowing.

16. What Comes Next

Every chapter so far has treated a stretch as something that ends. Chapter 12.4 takes the case where it does not — and finds that the specification's position is more uncomfortable than most designs assume.

tLOW has no maximum. §4.2.2 states outright that "there is no limit in the I²C-bus protocol as to how long this delay can be", and contrasts that with SMBus, which bounds it at 35 ms and therefore mandates a 10 kHz minimum clock. So a timeout on I²C is never a compliance check; it is a system policy, and a design that reports one as a protocol violation is claiming a rule the specification declines to state.

The chapter also sets out the asymmetry in §3.1.16 that most engineers know only half of: a stuck SDA can be cleared by nine clock pulses, and a stuck SCL cannot be cleared by anything the protocol offers — because clocking your way out requires the clock you have just lost. An unbounded stretch is a stuck SCL, which is why the specification's own remedy for it is a hardware reset or a power cycle.

And it closes the module on the rule that follows: never assume a target will not stretch unless the device or system contract guarantees it — the rule Chapter 12.1 §4 showed becomes an electrical commitment the moment a master chooses a push-pull SCL.

Continue learning