Skip to content
VLSI Mentor

I²C · Module 17

Bus Feedback — Clock Stretching and Arbitration From One Comparison

Clock stretching and arbitration loss are not two features. They are one comparison — a line this master released that reads back low — applied to two wires, differing only in a timing qualifier and an intent gate. Builds both from a single comparator and shows why a master can only ever lose by trying to send a one.

Every block so far has driven the bus. This one reads it back, and it is where the distinction this module has maintained throughout finally pays for itself completely.

1. The Claim

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
scl_held     = scl_release && !scl_in
arb_loss_now = sda_release && !sda_in && scl_in && tx_active

Two lines of RTL. Everything else in this block is counting and reporting.

1a. On SCL — somebody is holding the clock

Those two sentences describe the same electrical event. Whether the holder is a target stretching or another master with a longer low period is not observable — and this block does not pretend to know. It reports that the clock is held, and the distinction has no consequence for what to do: wait.

That is why Chapter 17.3's generator needed no feature detection. One comparator discharges both obligations because both require the same response.

1b. On SDA — somebody is transmitting a zero where we sent a one

Same shape: released, reads low.

2. The Two Differences, Both in the Specification

The comparisons are not identical, and both differences are normative rather than invented.

1. Timing. Arbitration is checked only while SCL is HIGH — §3.1.8 says "during every bit, while SCL is HIGH" — because SDA is allowed to be changing while SCL is low. Stretching has no such qualifier: a held SCL is a held SCL whenever it is observed.

2. Intent. A master that has released SDA in order to receive is not arbitrating. The electrical condition is identical; only intent separates them, which is why tx_active is an input rather than something this block could infer.

released and low on SCLreleased and low on SDA
qualified by phase?no — any timeyes — only while SCL is high
qualified by intent?noyes — only while transmitting
responsewaitstop driving, restart later

3. The Asymmetry Nobody States

A master can only ever lose arbitration by trying to send a ONE.

Sending a zero and reading a zero is indistinguishable from winning — the wired-AND hides the competitor entirely. So a master transmitting all zeros cannot detect a rival at all, until the rival sends a one.

Which it must: two addresses that differ have to differ somewhere, and at the first bit where one master sends a one and the other a zero, the one sending the one loses.

That is not a limitation to be fixed. It is why bitwise arbitration terminates, and why it needs no tie-breaking rule, no priority scheme and no retry timer.

4. Three Figures, One Comparison

Per the same idea drawn three ways, rather than one composite that would obscure all of it.

SCL released, and still low — a stretch

8 cycles
Eight intervals. The master's SCL drive-low output goes low at interval two, meaning it has released the line. The observed SCL bus line nevertheless stays low through interval five because another device is holding it, then rises at interval six. A stretch-held flag is high from interval three through interval five, and a stretch-cycles counter climbs from zero to three across that span.somebody holds the clocksomebody holds the clockwe released SCL herewe released SCL herereleased and still low: heldreleased and still low:heldthe holder let gothe holder let goscl_drive_lowscl_busscl_heldstretch_cycles00123333t0t1t2t3t4t5t6t7
Figure A — the clock side. This master has released SCL; the line reads low, so somebody else is holding it. Which somebody is not observable and does not matter. Conceptual figure.

SDA released while transmitting a one, and the line reads low — arbitration lost

8 cycles
Eight intervals. The master's SDA drive-low output is low throughout, meaning it has released the line to transmit a one. A transmit-active input is high throughout. The observed SDA bus line is low from interval three onward because another master is driving a zero. The observed SCL line is high during intervals three and four. An arbitration-lost flag rises at interval four, the first interval in which the line is low, SCL is high, and this master is transmitting.the contestthe contestlost — stop drivinglost — stop drivingSCL high — arbitration is judgedSCL high — arbitration isjudgedwe sent a one, the bus reads zerowe sent a one, the busreads zeroarb_lost latches, and is stickyarb_lost latches, and isstickysda_drive_lowtx_activescl_bussda_busarb_lostt0t1t2t3t4t5t6t7
Figure B — the data side. Same comparison, other wire, plus two qualifiers: SCL must be high, and this master must actually be transmitting. Conceptual figure.
A block diagram with two parallel paths sharing one shape. On the left, an SCL release signal and an inverted observed SCL line feed an AND gate labelled released and still low, which produces a clock-held output and feeds a stretch counter. Below it, an SDA release signal, an inverted observed SDA line, the observed SCL line and a transmit-active signal feed a second AND gate of the same shape, which produces an arbitration-lost latch. A caption box in the middle notes that the two gates are the same comparison with two extra qualifiers on the lower path.scl_releaseour intentNOT scl_inthe observed linereleased AND lowthe comparisonscl_heldwait — 17.3Stretch counterscycles, events, longestsda_releaseour intentNOT sda_inthe observed linereleased AND lowthe SAME comparisonAND scl_in AND txthe two qualifiersarb_loststop driving — 17.1112
Figure C — the same comparator, twice. The only structural difference is which line feeds it and which two qualifiers are ANDed in. Drawn as a block diagram because the point is the shared shape, not a sequence in time.

5. The Stretch Numbers, and Why They Are Not a Verdict

The block counts cycles held, events, and the longest stretch — which is the number that distinguishes a bus that occasionally stretches from one that stretches on every byte.

It also takes a STRETCH_LIMIT and raises stretch_timeout when a single stretch exceeds it.

6. The Feedback Block, in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bus_feedback.sv — one comparison, two protocol features
   // -----------------------------------------------------------------------------
   // i2c_bus_feedback.sv
   // Clock stretching and arbitration loss, from one comparison.
   //
   // THE CLAIM THIS BLOCK MAKES. These are not two features. They are one comparison --
   // "I released a line and it did not go high" -- applied to two different wires, and a
   // master that implements them as two mechanisms has written the same logic twice and
   // will get one of the two subtly wrong.
   //
   //   on SCL:  released, reads low  ->  somebody is holding the clock
   //            §3.1.6 "Clock stretching pauses a transaction by holding the SCL line LOW."
   //            §3.1.7 "The SCL line is therefore held LOW by the master with the longest
   //                    LOW period."
   //            Those two sentences describe the same electrical event. Whether the holder
   //            is a target stretching or another master with a longer low period is NOT
   //            OBSERVABLE, and this block does not pretend to know: it reports that the
   //            clock is held, and the distinction has no consequence for what to do.
   //
   //   on SDA:  released, reads low  ->  somebody is transmitting a zero where we sent one
   //            §3.1.8 "The first time a master tries to send a HIGH, but detects that the
   //                    SDA level is LOW, the master knows that it has lost the arbitration"
   //
   // THE TWO DIFFERENCES between the lines, both of which are in the specification:
   //
   //   1. TIMING. Arbitration is checked only while SCL is HIGH -- §3.1.8 says "during
   //      every bit, while SCL is HIGH" -- because SDA is allowed to be changing while SCL
   //      is low. Stretching has no such qualifier: a held SCL is a held SCL whenever it
   //      is observed.
   //
   //   2. INTENT. A master that has released SDA in order to RECEIVE is not arbitrating.
   //      The electrical condition is identical; only intent separates them, which is why
   //      `tx_active` is an input rather than something this block could infer.
   //
   // AND THE ASYMMETRY NOBODY STATES. A master can only ever LOSE arbitration by trying
   // to send a ONE. Sending a zero and reading a zero is indistinguishable from winning,
   // so a master transmitting all zeros cannot detect a competitor at all -- until the
   // competitor sends a one, which it must, because two addresses that differ have to
   // differ somewhere. So arbitration is decided by the FIRST BIT POSITION at which the
   // two masters disagree, and the master sending the zero there wins.
   // -----------------------------------------------------------------------------

   module i2c_bus_feedback #(
      // How long a stretch may last before it is called a hang. §3.1.6 places NO bound on
      // stretching, so this is a policy number exactly as Chapter 12.4 argued: whatever it
      // is, it is the designer's and not a conformance requirement. Zero disables it.
      parameter int STRETCH_LIMIT = 0,
      parameter int CNT_W         = 16
   ) (
      input  logic            clk,
      input  logic            rst_n,

      // What we are doing.
      input  logic            scl_release,  // we have let go of SCL
      input  logic            sda_release,  // we have let go of SDA
      input  logic            tx_active,    // we are transmitting, not receiving

      // What the lines actually say.
      input  logic            scl_in,
      input  logic            sda_in,

      // ---- the clock side ----------------------------------------------------
      output logic           scl_held,        // released, and low. THIS cycle.
      output logic            stretch_seen,    // at least one stretch since reset
      output logic [CNT_W-1:0] stretch_cycles, // total cycles waited
      output logic [CNT_W-1:0] stretch_events, // how many separate stretches
      output logic [CNT_W-1:0] longest_stretch,
      output logic            stretch_timeout, // the policy bound was exceeded

      // ---- the data side -----------------------------------------------------
      output logic           arb_loss_now,
      output logic            arb_lost,
      output logic [CNT_W-1:0] arb_losses,
      input  logic            clear
   );

      // THE ONE COMPARISON, twice.
      assign scl_held     = scl_release && !scl_in;
      assign arb_loss_now = sda_release && !sda_in && scl_in && tx_active;

      logic [CNT_W-1:0] run;
      logic             was_held;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            stretch_seen    <= 1'b0;
            stretch_cycles  <= {CNT_W{1'b0}};
            stretch_events  <= {CNT_W{1'b0}};
            longest_stretch <= {CNT_W{1'b0}};
            stretch_timeout <= 1'b0;
            arb_lost        <= 1'b0;
            arb_losses      <= {CNT_W{1'b0}};
            run             <= {CNT_W{1'b0}};
            was_held        <= 1'b0;
         end else begin
            if (clear) begin
               arb_lost        <= 1'b0;
               stretch_timeout <= 1'b0;
            end

            // ---- the clock side ----
            if (scl_held) begin
               stretch_seen   <= 1'b1;
               stretch_cycles <= stretch_cycles + 1'b1;
               run            <= run + 1'b1;
               // A new stretch, counted once at its start rather than once per cycle.
               if (!was_held) stretch_events <= stretch_events + 1'b1;
               // The policy bound. Note that exceeding it does NOT mean the target is
               // broken -- §3.1.6 gives stretching no bound, so a target that stretches
               // for longer than this master is willing to wait is still conforming. The
               // flag says the master gave up, not that the bus is faulty.
               if (STRETCH_LIMIT != 0 && (run + 1 >= STRETCH_LIMIT[CNT_W-1:0]))
                  stretch_timeout <= 1'b1;
            end else begin
               // The stretch ended. Record its length if it was the longest so far, which
               // is the number that distinguishes a bus that occasionally stretches from
               // one that stretches on every byte.
               if (was_held && run > longest_stretch) longest_stretch <= run;
               run <= {CNT_W{1'b0}};
            end
            was_held <= scl_held;

            // ---- the data side ----
            if (arb_loss_now && !arb_lost && !clear) begin
               arb_lost   <= 1'b1;
               arb_losses <= arb_losses + 1'b1;
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bus_feedback.v — the same design in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_bus_feedback.sv
   // Clock stretching and arbitration loss, from one comparison.
   //
   // THE CLAIM THIS BLOCK MAKES. These are not two features. They are one comparison --
   // "I released a line and it did not go high" -- applied to two different wires, and a
   // master that implements them as two mechanisms has written the same logic twice and
   // will get one of the two subtly wrong.
   //
   //   on SCL:  released, reads low  ->  somebody is holding the clock
   //            §3.1.6 "Clock stretching pauses a transaction by holding the SCL line LOW."
   //            §3.1.7 "The SCL line is therefore held LOW by the master with the longest
   //                    LOW period."
   //            Those two sentences describe the same electrical event. Whether the holder
   //            is a target stretching or another master with a longer low period is NOT
   //            OBSERVABLE, and this block does not pretend to know: it reports that the
   //            clock is held, and the distinction has no consequence for what to do.
   //
   //   on SDA:  released, reads low  ->  somebody is transmitting a zero where we sent one
   //            §3.1.8 "The first time a master tries to send a HIGH, but detects that the
   //                    SDA level is LOW, the master knows that it has lost the arbitration"
   //
   // THE TWO DIFFERENCES between the lines, both of which are in the specification:
   //
   //   1. TIMING. Arbitration is checked only while SCL is HIGH -- §3.1.8 says "during
   //      every bit, while SCL is HIGH" -- because SDA is allowed to be changing while SCL
   //      is low. Stretching has no such qualifier: a held SCL is a held SCL whenever it
   //      is observed.
   //
   //   2. INTENT. A master that has released SDA in order to RECEIVE is not arbitrating.
   //      The electrical condition is identical; only intent separates them, which is why
   //      `tx_active` is an input rather than something this block could infer.
   //
   // AND THE ASYMMETRY NOBODY STATES. A master can only ever LOSE arbitration by trying
   // to send a ONE. Sending a zero and reading a zero is indistinguishable from winning,
   // so a master transmitting all zeros cannot detect a competitor at all -- until the
   // competitor sends a one, which it must, because two addresses that differ have to
   // differ somewhere. So arbitration is decided by the FIRST BIT POSITION at which the
   // two masters disagree, and the master sending the zero there wins.
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_bus_feedback #(
      // How long a stretch may last before it is called a hang. §3.1.6 places NO bound on
      // stretching, so this is a policy number exactly as Chapter 12.4 argued: whatever it
      // is, it is the designer's and not a conformance requirement. Zero disables it.
      parameter STRETCH_LIMIT = 0,
      parameter CNT_W         = 16
   ) (
      input  wire            clk,
      input  wire            rst_n,

      // What we are doing.
      input  wire            scl_release,  // we have let go of SCL
      input  wire            sda_release,  // we have let go of SDA
      input  wire            tx_active,    // we are transmitting, not receiving

      // What the lines actually say.
      input  wire            scl_in,
      input  wire            sda_in,

      // ---- the clock side ----------------------------------------------------
      output wire            scl_held,        // released, and low. THIS cycle.
      output reg             stretch_seen,    // at least one stretch since reset
      output reg  [CNT_W-1:0] stretch_cycles, // total cycles waited
      output reg  [CNT_W-1:0] stretch_events, // how many separate stretches
      output reg  [CNT_W-1:0] longest_stretch,
      output reg             stretch_timeout, // the policy bound was exceeded

      // ---- the data side -----------------------------------------------------
      output wire            arb_loss_now,
      output reg             arb_lost,
      output reg  [CNT_W-1:0] arb_losses,
      input  wire            clear
   );

      // THE ONE COMPARISON, twice.
      assign scl_held     = scl_release && !scl_in;
      assign arb_loss_now = sda_release && !sda_in && scl_in && tx_active;

      reg [CNT_W-1:0] run;
      reg             was_held;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            stretch_seen    <= 1'b0;
            stretch_cycles  <= {CNT_W{1'b0}};
            stretch_events  <= {CNT_W{1'b0}};
            longest_stretch <= {CNT_W{1'b0}};
            stretch_timeout <= 1'b0;
            arb_lost        <= 1'b0;
            arb_losses      <= {CNT_W{1'b0}};
            run             <= {CNT_W{1'b0}};
            was_held        <= 1'b0;
         end else begin
            if (clear) begin
               arb_lost        <= 1'b0;
               stretch_timeout <= 1'b0;
            end

            // ---- the clock side ----
            if (scl_held) begin
               stretch_seen   <= 1'b1;
               stretch_cycles <= stretch_cycles + 1'b1;
               run            <= run + 1'b1;
               // A new stretch, counted once at its start rather than once per cycle.
               if (!was_held) stretch_events <= stretch_events + 1'b1;
               // The policy bound. Note that exceeding it does NOT mean the target is
               // broken -- §3.1.6 gives stretching no bound, so a target that stretches
               // for longer than this master is willing to wait is still conforming. The
               // flag says the master gave up, not that the bus is faulty.
               if (STRETCH_LIMIT != 0 && (run + 1 >= STRETCH_LIMIT[CNT_W-1:0]))
                  stretch_timeout <= 1'b1;
            end else begin
               // The stretch ended. Record its length if it was the longest so far, which
               // is the number that distinguishes a bus that occasionally stretches from
               // one that stretches on every byte.
               if (was_held && run > longest_stretch) longest_stretch <= run;
               run <= {CNT_W{1'b0}};
            end
            was_held <= scl_held;

            // ---- the data side ----
            if (arb_loss_now && !arb_lost && !clear) begin
               arb_lost   <= 1'b1;
               arb_losses <= arb_losses + 1'b1;
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bus_feedback.vhd — the same design in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_bus_feedback.vhd
   -- Clock stretching and arbitration loss, from one comparison.
   -- Behavioural twin of i2c_bus_feedback.sv / .v.
   --
   -- THE CLAIM THIS BLOCK MAKES. These are not two features. They are one comparison --
   -- "I released a line and it did not go high" -- applied to two different wires, and a master
   -- that implements them as two mechanisms has written the same logic twice and will get one
   -- of the two subtly wrong.
   --
   --   on SCL:  released, reads low  ->  somebody is holding the clock (§3.1.6, §3.1.7). Whether
   --            the holder is a target stretching or another master with a longer low period is
   --            NOT OBSERVABLE, and this block does not pretend to know.
   --   on SDA:  released, reads low  ->  somebody is transmitting a zero where we sent a one,
   --            which is arbitration loss (§3.1.8).
   --
   -- THE TWO DIFFERENCES between the lines, both in the specification:
   --   1. TIMING. Arbitration is checked only while SCL is HIGH -- §3.1.8 says "during every
   --      bit, while SCL is HIGH" -- because SDA is allowed to change while SCL is low.
   --      Stretching has no such qualifier.
   --   2. INTENT. A master that released SDA in order to RECEIVE is not arbitrating. The
   --      electrical condition is identical; only intent separates them, which is why
   --      `tx_active` is an input rather than something this block could infer.
   --
   -- AND THE ASYMMETRY NOBODY STATES: a master can only ever LOSE by trying to send a ONE, so
   -- arbitration is decided by the FIRST BIT POSITION at which two masters disagree.
   -- ---------------------------------------------------------------------------

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

   entity i2c_bus_feedback is
      generic (
         -- §3.1.6 places NO bound on stretching, so this is a policy number exactly as
         -- Chapter 12.4 argued: whatever it is, it is the designer's and not a conformance
         -- requirement. Zero disables it.
         STRETCH_LIMIT : integer := 0;
         CNT_W         : integer := 16
      );
      port (
         clk   : in std_logic;
         rst_n : in std_logic;

         scl_release : in std_logic;
         sda_release : in std_logic;
         tx_active   : in std_logic;

         scl_in : in std_logic;
         sda_in : in std_logic;

         scl_held        : out std_logic;
         stretch_seen    : out std_logic;
         stretch_cycles  : out unsigned(CNT_W-1 downto 0);
         stretch_events  : out unsigned(CNT_W-1 downto 0);
         longest_stretch : out unsigned(CNT_W-1 downto 0);
         stretch_timeout : out std_logic;

         arb_loss_now : out std_logic;
         arb_lost     : out std_logic;
         arb_losses   : out unsigned(CNT_W-1 downto 0);
         clear        : in  std_logic
      );
   end entity i2c_bus_feedback;

   architecture rtl of i2c_bus_feedback is
      signal held_i, loss_i : std_logic;
      signal seen, tmo, lost : std_logic := '0';
      signal n_cyc, n_ev, n_long, n_arb : unsigned(CNT_W-1 downto 0) := (others => '0');
      signal run : unsigned(CNT_W-1 downto 0) := (others => '0');
      signal was_held : std_logic := '0';
   begin

      -- THE ONE COMPARISON, twice.
      held_i <= scl_release and (not scl_in);
      loss_i <= sda_release and (not sda_in) and scl_in and tx_active;

      scl_held        <= held_i;
      arb_loss_now    <= loss_i;
      stretch_seen    <= seen;
      stretch_cycles  <= n_cyc;
      stretch_events  <= n_ev;
      longest_stretch <= n_long;
      stretch_timeout <= tmo;
      arb_lost        <= lost;
      arb_losses      <= n_arb;

      process (clk, rst_n)
      begin
         if rst_n = '0' then
            seen     <= '0';
            n_cyc    <= (others => '0');
            n_ev     <= (others => '0');
            n_long   <= (others => '0');
            tmo      <= '0';
            lost     <= '0';
            n_arb    <= (others => '0');
            run      <= (others => '0');
            was_held <= '0';
         elsif rising_edge(clk) then
            if clear = '1' then
               lost <= '0';
               tmo  <= '0';
            end if;

            -- ---- the clock side ----
            if held_i = '1' then
               seen  <= '1';
               n_cyc <= n_cyc + 1;
               run   <= run + 1;
               -- A new stretch, counted once at its start rather than once per cycle.
               if was_held = '0' then n_ev <= n_ev + 1; end if;
               -- The policy bound. Exceeding it does NOT mean the target is broken -- §3.1.6
               -- gives stretching no bound -- so the flag says the master gave up, not that
               -- the bus is faulty.
               if STRETCH_LIMIT /= 0
                  and (run + 1) >= to_unsigned(STRETCH_LIMIT, CNT_W) then
                  tmo <= '1';
               end if;
            else
               -- The stretch ended. Record its length if it was the longest so far, which is
               -- the number that distinguishes a bus that occasionally stretches from one that
               -- stretches on every byte.
               if was_held = '1' and run > n_long then n_long <= run; end if;
               run <= (others => '0');
            end if;
            was_held <= held_i;

            -- ---- the data side ----
            if loss_i = '1' and lost = '0' and clear = '0' then
               lost  <= '1';
               n_arb <= n_arb + 1;
            end if;
         end if;
      end process;

   end architecture rtl;

6a. The testbenches

Twelve tests, and several assert inabilities — that a condition does not fire — which is where this block's defects live.

#TestProperty
T1a healthy bus reports nothingboth lines released, both high
T2the clock side — released and low means held§3.1.6
T3driving it ourselves is not a stretchthe DUT's own low is not a report
T4stretch lengths are measured, and the longest kept
T5the policy bound, at the exact cycle it changessee §7
T6the data side — released, low, SCL high, transmitting§3.1.8
T7difference one: timing — arbitration only while SCL is high
T8difference two: intent — a receiving master is not arbitrating
T9the asymmetry — a master can only lose by sending a one
T10one comparison, two features — both at once, on both lines
T11a loss is counted once per contest, not per cycle
T12reset clears everythingand reports nothing about a bus it cannot see
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bus_feedback_tb.sv — the self-checking testbench
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_bus_feedback_tb.sv
   // Independent oracle for i2c_bus_feedback.
   //
   // The DUT is device 0 on a real wired-AND bus and the bench is device 1, so both
   // conditions the block detects are produced the way the bus produces them: by another
   // device holding a line the DUT has released. Nothing is poked directly into the DUT's
   // inputs, which is the only way to test a block whose entire job is to believe the wire
   // rather than its own intentions.
   // -----------------------------------------------------------------------------
   module i2c_bus_feedback_tb;

      localparam integer SLIMIT = 20;

      logic clk = 1'b0, rst_n = 1'b0;
      logic scl_release = 1'b1, sda_release = 1'b1, tx_active = 1'b1, clear = 1'b0;
      logic oth_scl_low = 1'b0, oth_sda_low = 1'b0;

      // The DUT's own drive is the inverse of its release signals.
      wire dut_scl_low = ~scl_release;
      wire dut_sda_low = ~sda_release;

      logic scl, sda;
      logic [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
      logic [7:0] scl_h, sda_h;

      i2c_line_model #(.N_DEV(2)) bus (
         .scl_drive_low({oth_scl_low, dut_scl_low}),
         .sda_drive_low({oth_sda_low, dut_sda_low}),
         .scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
         .scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
         .scl_holders(scl_h), .sda_holders(sda_h));

      logic scl_held, arb_now, arb_lost, s_seen, s_to;
      logic [15:0] s_cyc, s_ev, s_long, n_arb;

      i2c_bus_feedback #(.STRETCH_LIMIT(SLIMIT), .CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .scl_release(scl_release), .sda_release(sda_release), .tx_active(tx_active),
         .scl_in(scl_in[0]), .sda_in(sda_in[0]),
         .scl_held(scl_held), .stretch_seen(s_seen), .stretch_cycles(s_cyc),
         .stretch_events(s_ev), .longest_stretch(s_long), .stretch_timeout(s_to),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(n_arb), .clear(clear));

      always #5 clk = ~clk;

      integer errors = 0;
      integer n, k;

      task step; begin @(posedge clk); @(negedge clk); end endtask

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0;
            scl_release = 1'b1; sda_release = 1'b1; tx_active = 1'b1; clear = 1'b0;
            oth_scl_low = 1'b0; oth_sda_low = 1'b0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d expected %0d", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      task ck_bit (input [200*8:1] what, input g, input e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0b expected %0b", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      initial begin
         $display("=== i2c_bus_feedback: two protocol features, one comparison ===");

         // ----------------------------------------------------------------
         // T1. A healthy bus reports nothing. Both lines released, both high.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 30; k = k + 1) step;
         $display("T1  a healthy bus reports neither condition");
         ck_bit("T1 the clock is not held", scl_held, 1'b0);
         ck_bit("T1 no arbitration loss", arb_now, 1'b0);
         ck_bit("T1 no stretch seen", s_seen, 1'b0);
         ck_int("T1 no stretch cycles", s_cyc, 0);
         ck_int("T1 no losses", n_arb, 0);

         // ----------------------------------------------------------------
         // T2. THE CLOCK SIDE. Released and low means somebody is holding it. §3.1.6.
         // ----------------------------------------------------------------
         @(negedge clk); oth_scl_low = 1'b1; #1;
         $display("T2  released and low on SCL: the clock is being held");
         ck_bit("T2 the DUT has released SCL", dut_scl_low, 1'b0);
         ck_bit("T2 the line is low", scl, 1'b0);
         ck_bit("T2 so the clock is held", scl_held, 1'b1);
         for (k = 0; k < 10; k = k + 1) step;
         ck_bit("T2 and it is recorded", s_seen, 1'b1);
         ck_int("T2 one stretch event, not one per cycle", s_ev, 1);

         // ----------------------------------------------------------------
         // T3. DRIVING IT OURSELVES IS NOT A STRETCH. The DUT holding SCL low reads it
         //     low too, which is why the primitive is "released AND low" rather than "low".
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); scl_release = 1'b0; #1;   // WE are holding SCL
         $display("T3  holding SCL ourselves is not a stretch");
         ck_bit("T3 the line is low", scl, 1'b0);
         ck_bit("T3 but we are the one holding it", dut_scl_low, 1'b1);
         ck_bit("T3 so the clock is not reported as held", scl_held, 1'b0);
         for (k = 0; k < 15; k = k + 1) step;
         ck_int("T3 no stretch cycles counted", s_cyc, 0);

         // ----------------------------------------------------------------
         // T4. STRETCH LENGTHS ARE MEASURED, and the longest is kept. That is the number
         //     that distinguishes a bus that occasionally stretches from one that
         //     stretches on every byte, which a boolean cannot.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < 8; k = k + 1) step;
         @(negedge clk); oth_scl_low = 1'b0;
         step; step;
         k = s_long;
         @(negedge clk); oth_scl_low = 1'b1;
         for (n = 0; n < 4; n = n + 1) step;
         @(negedge clk); oth_scl_low = 1'b0;
         step; step;
         $display("T4  stretch lengths are measured and the longest is kept");
         ck_int("T4 two separate stretch events", s_ev, 2);
         ck_int("T4 the longest is the first, not the most recent", s_long, k);
         if (s_long < 8) begin
            $display("  FAIL T4 the longest stretch was %0d, expected at least 8", s_long);
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T5. THE POLICY BOUND. §3.1.6 places NO limit on stretching, so exceeding this
         //     one says the master gave up -- not that the target is broken.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < SLIMIT + 4; k = k + 1) step;
         $display("T5  the stretch bound is a policy number, and exceeding it is a decision");
         ck_bit("T5 the bound was exceeded", s_to, 1'b1);
         ck_bit("T5 the clock is still held", scl_held, 1'b1);
         // And a stretch shorter than the bound does NOT trip it.
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < SLIMIT - 5; k = k + 1) step;
         ck_bit("T5 a shorter stretch does not trip it", s_to, 1'b0);
         @(negedge clk); oth_scl_low = 1'b0; step;

         // THE EXACT BOUNDARY. `SLIMIT + 4` and `SLIMIT - 5` leave a nine-cycle gap in
         // which an off-by-one in the comparison is invisible, so the bound is pinned at
         // the cycle it actually changes: measured, the flag trips when the held count
         // reaches SLIMIT, and not at SLIMIT - 1.
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < SLIMIT - 1; k = k + 1) step;
         ck_int("T5 held for exactly SLIMIT-1 cycles", s_cyc, SLIMIT - 1);
         ck_bit("T5 and SLIMIT-1 does NOT trip the bound", s_to, 1'b0);
         step;
         ck_int("T5 held for exactly SLIMIT cycles", s_cyc, SLIMIT);
         ck_bit("T5 and SLIMIT DOES trip it", s_to, 1'b1);
         @(negedge clk); oth_scl_low = 1'b0; step;
         ck_bit("T5 and it ends cleanly", scl_held, 1'b0);

         // ----------------------------------------------------------------
         // T6. THE DATA SIDE. Released, low, SCL high, and transmitting: §3.1.8.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_sda_low = 1'b1; #1;
         $display("T6  released and low on SDA while SCL is high: arbitration lost");
         ck_bit("T6 SCL is high", scl, 1'b1);
         ck_bit("T6 we released SDA, meaning we sent a one", dut_sda_low, 1'b0);
         ck_bit("T6 the line reads zero", sda, 1'b0);
         ck_bit("T6 so arbitration is lost this cycle", arb_now, 1'b1);
         step;
         ck_bit("T6 and latched", arb_lost, 1'b1);
         ck_int("T6 counted once", n_arb, 1);

         // ----------------------------------------------------------------
         // T7. THE FIRST DIFFERENCE BETWEEN THE LINES: timing. Arbitration is checked only
         //     while SCL is HIGH (§3.1.8), because SDA is allowed to change while SCL is
         //     low. Stretching has no such qualifier.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_sda_low = 1'b1; oth_scl_low = 1'b1; #1;
         $display("T7  arbitration is checked only while SCL is high; stretching is not");
         ck_bit("T7 SCL is low", scl, 1'b0);
         ck_bit("T7 the SDA mismatch exists", sda, 1'b0);
         ck_bit("T7 but no loss is declared", arb_now, 1'b0);
         ck_bit("T7 while the clock IS reported as held", scl_held, 1'b1);
         @(negedge clk); oth_scl_low = 1'b0; #1;
         ck_bit("T7 and the same mismatch with SCL high is a loss", arb_now, 1'b1);

         // ----------------------------------------------------------------
         // T8. THE SECOND DIFFERENCE: intent. A receiving master released SDA on purpose,
         //     so a low is data. The electrical condition is identical and only tx_active
         //     separates them, which is why it cannot be inferred here.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); tx_active = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1; #1;
         $display("T8  a receiving master reading a zero has lost nothing");
         ck_bit("T8 the electrical condition is present", sda, 1'b0);
         ck_bit("T8 but no loss is declared", arb_now, 1'b0);
         step;
         ck_bit("T8 and none is latched", arb_lost, 1'b0);
         @(negedge clk); tx_active = 1'b1; #1;
         ck_bit("T8 the identical bus state while transmitting IS a loss", arb_now, 1'b1);

         // ----------------------------------------------------------------
         // T9. THE ASYMMETRY. A master can only lose by sending a ONE. Sending a zero and
         //     reading a zero is indistinguishable from winning, so a master transmitting
         //     zeros cannot see a competitor at all.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); sda_release = 1'b0;      // we are sending a ZERO
         @(negedge clk); oth_sda_low = 1'b1;      // so is the competitor
         #1;
         $display("T9  a master transmitting a zero cannot detect a competitor");
         ck_bit("T9 the line is low, exactly as we intended", sda, 1'b0);
         ck_int("T9 and two devices are holding it", sda_h, 2);
         ck_bit("T9 no loss is detected", arb_now, 1'b0);
         step;
         ck_int("T9 no losses counted", n_arb, 0);

         // ----------------------------------------------------------------
         // T10. ONE COMPARISON, TWO FEATURES. Both conditions at once, on the two lines,
         //      each reported independently -- which is the structural claim of the block.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < 5; k = k + 1) step;
         @(negedge clk); oth_scl_low = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1; #1;
         $display("T10 the same comparison on two lines, reported independently");
         ck_bit("T10 the stretch was recorded", s_seen, 1'b1);
         ck_bit("T10 the clock is free again", scl_held, 1'b0);
         ck_bit("T10 and now arbitration is lost", arb_now, 1'b1);
         step;
         ck_int("T10 one stretch event", s_ev, 1);
         ck_int("T10 one arbitration loss", n_arb, 1);

         // ----------------------------------------------------------------
         // T11. A loss is counted once per contest, not once per cycle of the mismatch,
         //      and it is sticky until the controller clears it.
         // ----------------------------------------------------------------
         for (k = 0; k < 20; k = k + 1) step;
         $display("T11 a loss is counted once per contest and is sticky until cleared");
         ck_int("T11 still one loss", n_arb, 1);
         ck_bit("T11 still latched", arb_lost, 1'b1);
         @(negedge clk); clear = 1'b1; step;
         @(negedge clk); clear = 1'b0; oth_sda_low = 1'b0; #1;
         ck_bit("T11 cleared", arb_lost, 1'b0);
         ck_bit("T11 and the timeout cleared with it", s_to, 1'b0);

         // ----------------------------------------------------------------
         // T12. Reset clears everything, and a reset block reports nothing about a bus it
         //      has not observed yet.
         // ----------------------------------------------------------------
         @(negedge clk); oth_scl_low = 1'b1; oth_sda_low = 1'b1;
         for (k = 0; k < 10; k = k + 1) step;
         @(negedge clk); rst_n = 1'b0; step;
         $display("T12 reset clears every latched report");
         ck_bit("T12 no stretch seen", s_seen, 1'b0);
         ck_int("T12 no stretch cycles", s_cyc, 0);
         ck_int("T12 no stretch events", s_ev, 0);
         ck_int("T12 no longest stretch", s_long, 0);
         ck_bit("T12 no timeout", s_to, 1'b0);
         ck_bit("T12 no arbitration loss", arb_lost, 1'b0);
         ck_int("T12 no losses", n_arb, 0);

         if (errors == 0)
            $display("=== i2c_bus_feedback: ALL CHECKS PASSED ===");
         else
            $display("=== i2c_bus_feedback: %0d CHECK(S) FAILED ===", errors);
         $finish;
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bus_feedback_tb.v — the same tests in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_bus_feedback_tb.sv
   // Independent oracle for i2c_bus_feedback.
   //
   // The DUT is device 0 on a real wired-AND bus and the bench is device 1, so both
   // conditions the block detects are produced the way the bus produces them: by another
   // device holding a line the DUT has released. Nothing is poked directly into the DUT's
   // inputs, which is the only way to test a block whose entire job is to believe the wire
   // rather than its own intentions.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_bus_feedback_tb;

      localparam integer SLIMIT = 20;

      reg clk = 1'b0, rst_n = 1'b0;
      reg scl_release = 1'b1, sda_release = 1'b1, tx_active = 1'b1, clear = 1'b0;
      reg oth_scl_low = 1'b0, oth_sda_low = 1'b0;

      // The DUT's own drive is the inverse of its release signals.
      wire dut_scl_low = ~scl_release;
      wire dut_sda_low = ~sda_release;

      wire scl, sda;
      wire [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
      wire [7:0] scl_h, sda_h;

      i2c_line_model #(.N_DEV(2)) bus (
         .scl_drive_low({oth_scl_low, dut_scl_low}),
         .sda_drive_low({oth_sda_low, dut_sda_low}),
         .scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
         .scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
         .scl_holders(scl_h), .sda_holders(sda_h));

      wire scl_held, arb_now, arb_lost, s_seen, s_to;
      wire [15:0] s_cyc, s_ev, s_long, n_arb;

      i2c_bus_feedback #(.STRETCH_LIMIT(SLIMIT), .CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .scl_release(scl_release), .sda_release(sda_release), .tx_active(tx_active),
         .scl_in(scl_in[0]), .sda_in(sda_in[0]),
         .scl_held(scl_held), .stretch_seen(s_seen), .stretch_cycles(s_cyc),
         .stretch_events(s_ev), .longest_stretch(s_long), .stretch_timeout(s_to),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(n_arb), .clear(clear));

      always #5 clk = ~clk;

      integer errors = 0;
      integer n, k;

      task step; begin @(posedge clk); @(negedge clk); end endtask

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0;
            scl_release = 1'b1; sda_release = 1'b1; tx_active = 1'b1; clear = 1'b0;
            oth_scl_low = 1'b0; oth_sda_low = 1'b0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d expected %0d", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      task ck_bit (input [200*8:1] what, input g, input e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0b expected %0b", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      initial begin
         $display("=== i2c_bus_feedback: two protocol features, one comparison ===");

         // ----------------------------------------------------------------
         // T1. A healthy bus reports nothing. Both lines released, both high.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 30; k = k + 1) step;
         $display("T1  a healthy bus reports neither condition");
         ck_bit("T1 the clock is not held", scl_held, 1'b0);
         ck_bit("T1 no arbitration loss", arb_now, 1'b0);
         ck_bit("T1 no stretch seen", s_seen, 1'b0);
         ck_int("T1 no stretch cycles", s_cyc, 0);
         ck_int("T1 no losses", n_arb, 0);

         // ----------------------------------------------------------------
         // T2. THE CLOCK SIDE. Released and low means somebody is holding it. §3.1.6.
         // ----------------------------------------------------------------
         @(negedge clk); oth_scl_low = 1'b1; #1;
         $display("T2  released and low on SCL: the clock is being held");
         ck_bit("T2 the DUT has released SCL", dut_scl_low, 1'b0);
         ck_bit("T2 the line is low", scl, 1'b0);
         ck_bit("T2 so the clock is held", scl_held, 1'b1);
         for (k = 0; k < 10; k = k + 1) step;
         ck_bit("T2 and it is recorded", s_seen, 1'b1);
         ck_int("T2 one stretch event, not one per cycle", s_ev, 1);

         // ----------------------------------------------------------------
         // T3. DRIVING IT OURSELVES IS NOT A STRETCH. The DUT holding SCL low reads it
         //     low too, which is why the primitive is "released AND low" rather than "low".
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); scl_release = 1'b0; #1;   // WE are holding SCL
         $display("T3  holding SCL ourselves is not a stretch");
         ck_bit("T3 the line is low", scl, 1'b0);
         ck_bit("T3 but we are the one holding it", dut_scl_low, 1'b1);
         ck_bit("T3 so the clock is not reported as held", scl_held, 1'b0);
         for (k = 0; k < 15; k = k + 1) step;
         ck_int("T3 no stretch cycles counted", s_cyc, 0);

         // ----------------------------------------------------------------
         // T4. STRETCH LENGTHS ARE MEASURED, and the longest is kept. That is the number
         //     that distinguishes a bus that occasionally stretches from one that
         //     stretches on every byte, which a boolean cannot.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < 8; k = k + 1) step;
         @(negedge clk); oth_scl_low = 1'b0;
         step; step;
         k = s_long;
         @(negedge clk); oth_scl_low = 1'b1;
         for (n = 0; n < 4; n = n + 1) step;
         @(negedge clk); oth_scl_low = 1'b0;
         step; step;
         $display("T4  stretch lengths are measured and the longest is kept");
         ck_int("T4 two separate stretch events", s_ev, 2);
         ck_int("T4 the longest is the first, not the most recent", s_long, k);
         if (s_long < 8) begin
            $display("  FAIL T4 the longest stretch was %0d, expected at least 8", s_long);
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T5. THE POLICY BOUND. §3.1.6 places NO limit on stretching, so exceeding this
         //     one says the master gave up -- not that the target is broken.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < SLIMIT + 4; k = k + 1) step;
         $display("T5  the stretch bound is a policy number, and exceeding it is a decision");
         ck_bit("T5 the bound was exceeded", s_to, 1'b1);
         ck_bit("T5 the clock is still held", scl_held, 1'b1);
         // And a stretch shorter than the bound does NOT trip it.
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < SLIMIT - 5; k = k + 1) step;
         ck_bit("T5 a shorter stretch does not trip it", s_to, 1'b0);
         @(negedge clk); oth_scl_low = 1'b0; step;

         // THE EXACT BOUNDARY. `SLIMIT + 4` and `SLIMIT - 5` leave a nine-cycle gap in
         // which an off-by-one in the comparison is invisible, so the bound is pinned at
         // the cycle it actually changes: measured, the flag trips when the held count
         // reaches SLIMIT, and not at SLIMIT - 1.
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < SLIMIT - 1; k = k + 1) step;
         ck_int("T5 held for exactly SLIMIT-1 cycles", s_cyc, SLIMIT - 1);
         ck_bit("T5 and SLIMIT-1 does NOT trip the bound", s_to, 1'b0);
         step;
         ck_int("T5 held for exactly SLIMIT cycles", s_cyc, SLIMIT);
         ck_bit("T5 and SLIMIT DOES trip it", s_to, 1'b1);
         @(negedge clk); oth_scl_low = 1'b0; step;
         ck_bit("T5 and it ends cleanly", scl_held, 1'b0);

         // ----------------------------------------------------------------
         // T6. THE DATA SIDE. Released, low, SCL high, and transmitting: §3.1.8.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_sda_low = 1'b1; #1;
         $display("T6  released and low on SDA while SCL is high: arbitration lost");
         ck_bit("T6 SCL is high", scl, 1'b1);
         ck_bit("T6 we released SDA, meaning we sent a one", dut_sda_low, 1'b0);
         ck_bit("T6 the line reads zero", sda, 1'b0);
         ck_bit("T6 so arbitration is lost this cycle", arb_now, 1'b1);
         step;
         ck_bit("T6 and latched", arb_lost, 1'b1);
         ck_int("T6 counted once", n_arb, 1);

         // ----------------------------------------------------------------
         // T7. THE FIRST DIFFERENCE BETWEEN THE LINES: timing. Arbitration is checked only
         //     while SCL is HIGH (§3.1.8), because SDA is allowed to change while SCL is
         //     low. Stretching has no such qualifier.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_sda_low = 1'b1; oth_scl_low = 1'b1; #1;
         $display("T7  arbitration is checked only while SCL is high; stretching is not");
         ck_bit("T7 SCL is low", scl, 1'b0);
         ck_bit("T7 the SDA mismatch exists", sda, 1'b0);
         ck_bit("T7 but no loss is declared", arb_now, 1'b0);
         ck_bit("T7 while the clock IS reported as held", scl_held, 1'b1);
         @(negedge clk); oth_scl_low = 1'b0; #1;
         ck_bit("T7 and the same mismatch with SCL high is a loss", arb_now, 1'b1);

         // ----------------------------------------------------------------
         // T8. THE SECOND DIFFERENCE: intent. A receiving master released SDA on purpose,
         //     so a low is data. The electrical condition is identical and only tx_active
         //     separates them, which is why it cannot be inferred here.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); tx_active = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1; #1;
         $display("T8  a receiving master reading a zero has lost nothing");
         ck_bit("T8 the electrical condition is present", sda, 1'b0);
         ck_bit("T8 but no loss is declared", arb_now, 1'b0);
         step;
         ck_bit("T8 and none is latched", arb_lost, 1'b0);
         @(negedge clk); tx_active = 1'b1; #1;
         ck_bit("T8 the identical bus state while transmitting IS a loss", arb_now, 1'b1);

         // ----------------------------------------------------------------
         // T9. THE ASYMMETRY. A master can only lose by sending a ONE. Sending a zero and
         //     reading a zero is indistinguishable from winning, so a master transmitting
         //     zeros cannot see a competitor at all.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); sda_release = 1'b0;      // we are sending a ZERO
         @(negedge clk); oth_sda_low = 1'b1;      // so is the competitor
         #1;
         $display("T9  a master transmitting a zero cannot detect a competitor");
         ck_bit("T9 the line is low, exactly as we intended", sda, 1'b0);
         ck_int("T9 and two devices are holding it", sda_h, 2);
         ck_bit("T9 no loss is detected", arb_now, 1'b0);
         step;
         ck_int("T9 no losses counted", n_arb, 0);

         // ----------------------------------------------------------------
         // T10. ONE COMPARISON, TWO FEATURES. Both conditions at once, on the two lines,
         //      each reported independently -- which is the structural claim of the block.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); oth_scl_low = 1'b1;
         for (k = 0; k < 5; k = k + 1) step;
         @(negedge clk); oth_scl_low = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1; #1;
         $display("T10 the same comparison on two lines, reported independently");
         ck_bit("T10 the stretch was recorded", s_seen, 1'b1);
         ck_bit("T10 the clock is free again", scl_held, 1'b0);
         ck_bit("T10 and now arbitration is lost", arb_now, 1'b1);
         step;
         ck_int("T10 one stretch event", s_ev, 1);
         ck_int("T10 one arbitration loss", n_arb, 1);

         // ----------------------------------------------------------------
         // T11. A loss is counted once per contest, not once per cycle of the mismatch,
         //      and it is sticky until the controller clears it.
         // ----------------------------------------------------------------
         for (k = 0; k < 20; k = k + 1) step;
         $display("T11 a loss is counted once per contest and is sticky until cleared");
         ck_int("T11 still one loss", n_arb, 1);
         ck_bit("T11 still latched", arb_lost, 1'b1);
         @(negedge clk); clear = 1'b1; step;
         @(negedge clk); clear = 1'b0; oth_sda_low = 1'b0; #1;
         ck_bit("T11 cleared", arb_lost, 1'b0);
         ck_bit("T11 and the timeout cleared with it", s_to, 1'b0);

         // ----------------------------------------------------------------
         // T12. Reset clears everything, and a reset block reports nothing about a bus it
         //      has not observed yet.
         // ----------------------------------------------------------------
         @(negedge clk); oth_scl_low = 1'b1; oth_sda_low = 1'b1;
         for (k = 0; k < 10; k = k + 1) step;
         @(negedge clk); rst_n = 1'b0; step;
         $display("T12 reset clears every latched report");
         ck_bit("T12 no stretch seen", s_seen, 1'b0);
         ck_int("T12 no stretch cycles", s_cyc, 0);
         ck_int("T12 no stretch events", s_ev, 0);
         ck_int("T12 no longest stretch", s_long, 0);
         ck_bit("T12 no timeout", s_to, 1'b0);
         ck_bit("T12 no arbitration loss", arb_lost, 1'b0);
         ck_int("T12 no losses", n_arb, 0);

         if (errors == 0)
            $display("=== i2c_bus_feedback: ALL CHECKS PASSED ===");
         else
            $display("=== i2c_bus_feedback: %0d CHECK(S) FAILED ===", errors);
         $finish;
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bus_feedback_tb.vhd — the same tests in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_bus_feedback_tb.vhd
   -- Independent oracle for i2c_bus_feedback. Behavioural twin of the SV and Verilog benches.
   --
   -- The DUT is device 0 on a real wired-AND bus and the bench is device 1, so both conditions
   -- the block detects are produced the way the bus produces them: by another device holding a
   -- line the DUT has released. Nothing is poked directly into the DUT's inputs, which is the
   -- only way to test a block whose entire job is to believe the wire rather than its own
   -- intentions.
   -- ---------------------------------------------------------------------------

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

   entity i2c_bus_feedback_tb is
   end entity i2c_bus_feedback_tb;

   architecture sim of i2c_bus_feedback_tb is

      constant TCLK   : time := 10 ns;
      constant SLIMIT : integer := 20;

      signal clk, rst_n : std_logic := '0';
      signal scl_release, sda_release, tx_active : std_logic := '1';
      signal clear : std_logic := '0';
      signal oth_scl_low, oth_sda_low : std_logic := '0';

      signal dut_scl_low, dut_sda_low : std_logic;
      signal scl_drv, sda_drv : std_logic_vector(1 downto 0);
      signal scl, sda : std_logic;
      signal scl_in, sda_in, scl_rbl, sda_rbl : std_logic_vector(1 downto 0);
      signal scl_h, sda_h : unsigned(7 downto 0);

      signal scl_held, arb_now, arb_lost, s_seen, s_to : std_logic;
      signal s_cyc, s_ev, s_long, n_arb : unsigned(15 downto 0);

      signal halt : boolean := false;

   begin

      -- The DUT's own drive is the inverse of its release signals.
      dut_scl_low <= not scl_release;
      dut_sda_low <= not sda_release;

      scl_drv <= oth_scl_low & dut_scl_low;
      sda_drv <= oth_sda_low & dut_sda_low;

      bus_m : entity work.i2c_line_model
         generic map (N_DEV => 2)
         port map (scl_drive_low => scl_drv, sda_drive_low => sda_drv,
            scl => scl, sda => sda, scl_in => scl_in, sda_in => sda_in,
            scl_released_but_low => scl_rbl, sda_released_but_low => sda_rbl,
            scl_holders => scl_h, sda_holders => sda_h);

      dut : entity work.i2c_bus_feedback
         generic map (STRETCH_LIMIT => SLIMIT, CNT_W => 16)
         port map (clk => clk, rst_n => rst_n,
            scl_release => scl_release, sda_release => sda_release, tx_active => tx_active,
            scl_in => scl_in(0), sda_in => sda_in(0),
            scl_held => scl_held, stretch_seen => s_seen, stretch_cycles => s_cyc,
            stretch_events => s_ev, longest_stretch => s_long, stretch_timeout => s_to,
            arb_loss_now => arb_now, arb_lost => arb_lost, arb_losses => n_arb,
            clear => clear);

      clkgen : process
      begin
         while not halt loop
            clk <= '0'; wait for TCLK/2;
            clk <= '1'; wait for TCLK/2;
         end loop;
         wait;
      end process;

      stim : process
         variable err : integer := 0;
         variable n, k : integer;

         procedure ck_int (what : string; g : integer; e : integer) is
         begin
            if g /= e then
               report "  FAIL " & what & ": got " & integer'image(g)
                      & " expected " & integer'image(e) severity note;
               err := err + 1;
            end if;
         end procedure;

         procedure ck_bit (what : string; g : std_logic; e : std_logic) is
         begin
            if g /= e then
               report "  FAIL " & what & ": got " & std_logic'image(g)
                      & " expected " & std_logic'image(e) severity note;
               err := err + 1;
            end if;
         end procedure;

         procedure step is
         begin
            wait until rising_edge(clk); wait until falling_edge(clk);
         end procedure;

         procedure do_reset is
         begin
            wait until falling_edge(clk);
            rst_n <= '0';
            scl_release <= '1'; sda_release <= '1'; tx_active <= '1'; clear <= '0';
            oth_scl_low <= '0'; oth_sda_low <= '0';
            for i in 0 to 2 loop wait until rising_edge(clk); end loop;
            wait until falling_edge(clk); rst_n <= '1';
            step;
         end procedure;

      begin
         report "=== i2c_bus_feedback: two protocol features, one comparison ===" severity note;

         -- T1. A healthy bus reports nothing.
         do_reset;
         for j in 0 to 29 loop step; end loop;
         report "T1  a healthy bus reports neither condition" severity note;
         ck_bit("T1 the clock is not held", scl_held, '0');
         ck_bit("T1 no arbitration loss", arb_now, '0');
         ck_bit("T1 no stretch seen", s_seen, '0');
         ck_int("T1 no stretch cycles", to_integer(s_cyc), 0);
         ck_int("T1 no losses", to_integer(n_arb), 0);

         -- T2. THE CLOCK SIDE. Released and low means somebody is holding it. §3.1.6.
         wait until falling_edge(clk); oth_scl_low <= '1'; wait for 1 ns;
         report "T2  released and low on SCL: the clock is being held" severity note;
         ck_bit("T2 the DUT has released SCL", dut_scl_low, '0');
         ck_bit("T2 the line is low", scl, '0');
         ck_bit("T2 so the clock is held", scl_held, '1');
         for j in 0 to 9 loop step; end loop;
         ck_bit("T2 and it is recorded", s_seen, '1');
         ck_int("T2 one stretch event, not one per cycle", to_integer(s_ev), 1);

         -- T3. DRIVING IT OURSELVES IS NOT A STRETCH, which is why the primitive is
         --     "released AND low" rather than "low".
         do_reset;
         wait until falling_edge(clk); scl_release <= '0'; wait for 1 ns;
         report "T3  holding SCL ourselves is not a stretch" severity note;
         ck_bit("T3 the line is low", scl, '0');
         ck_bit("T3 but we are the one holding it", dut_scl_low, '1');
         ck_bit("T3 so the clock is not reported as held", scl_held, '0');
         for j in 0 to 14 loop step; end loop;
         ck_int("T3 no stretch cycles counted", to_integer(s_cyc), 0);

         -- T4. STRETCH LENGTHS ARE MEASURED, and the longest is kept -- the number that
         --     distinguishes a bus that occasionally stretches from one that always does.
         do_reset;
         wait until falling_edge(clk); oth_scl_low <= '1';
         for j in 0 to 7 loop step; end loop;
         wait until falling_edge(clk); oth_scl_low <= '0';
         step; step;
         k := to_integer(s_long);
         wait until falling_edge(clk); oth_scl_low <= '1';
         for j in 0 to 3 loop step; end loop;
         wait until falling_edge(clk); oth_scl_low <= '0';
         step; step;
         report "T4  stretch lengths are measured and the longest is kept" severity note;
         ck_int("T4 two separate stretch events", to_integer(s_ev), 2);
         ck_int("T4 the longest is the first, not the most recent", to_integer(s_long), k);
         if to_integer(s_long) < 8 then
            report "  FAIL T4 the longest stretch was " & integer'image(to_integer(s_long))
                   & ", expected at least 8" severity note;
            err := err + 1;
         end if;

         -- T5. THE POLICY BOUND. §3.1.6 places NO limit on stretching, so exceeding this one
         --     says the master gave up -- not that the target is broken.
         do_reset;
         wait until falling_edge(clk); oth_scl_low <= '1';
         for j in 0 to SLIMIT + 3 loop step; end loop;
         report "T5  the stretch bound is a policy number, and exceeding it is a decision"
                severity note;
         ck_bit("T5 the bound was exceeded", s_to, '1');
         ck_bit("T5 the clock is still held", scl_held, '1');
         do_reset;
         wait until falling_edge(clk); oth_scl_low <= '1';
         for j in 0 to SLIMIT - 6 loop step; end loop;
         ck_bit("T5 a shorter stretch does not trip it", s_to, '0');
         wait until falling_edge(clk); oth_scl_low <= '0'; step;

         -- THE EXACT BOUNDARY. SLIMIT + 4 and SLIMIT - 5 leave a nine-cycle gap in which an
         -- off-by-one in the comparison is invisible, so the bound is pinned at the cycle it
         -- actually changes: measured, the flag trips when the held count reaches SLIMIT,
         -- and not at SLIMIT - 1.
         do_reset;
         wait until falling_edge(clk); oth_scl_low <= '1';
         for j in 0 to SLIMIT - 2 loop step; end loop;
         ck_int("T5 held for exactly SLIMIT-1 cycles", to_integer(s_cyc), SLIMIT - 1);
         ck_bit("T5 and SLIMIT-1 does NOT trip the bound", s_to, '0');
         step;
         ck_int("T5 held for exactly SLIMIT cycles", to_integer(s_cyc), SLIMIT);
         ck_bit("T5 and SLIMIT DOES trip it", s_to, '1');
         wait until falling_edge(clk); oth_scl_low <= '0'; step;
         ck_bit("T5 and it ends cleanly", scl_held, '0');

         -- T6. THE DATA SIDE. §3.1.8.
         do_reset;
         wait until falling_edge(clk); oth_sda_low <= '1'; wait for 1 ns;
         report "T6  released and low on SDA while SCL is high: arbitration lost"
                severity note;
         ck_bit("T6 SCL is high", scl, '1');
         ck_bit("T6 we released SDA, meaning we sent a one", dut_sda_low, '0');
         ck_bit("T6 the line reads zero", sda, '0');
         ck_bit("T6 so arbitration is lost this cycle", arb_now, '1');
         step;
         ck_bit("T6 and latched", arb_lost, '1');
         ck_int("T6 counted once", to_integer(n_arb), 1);

         -- T7. THE FIRST DIFFERENCE: timing. Arbitration is checked only while SCL is HIGH;
         --     stretching is not.
         do_reset;
         wait until falling_edge(clk); oth_sda_low <= '1'; oth_scl_low <= '1'; wait for 1 ns;
         report "T7  arbitration is checked only while SCL is high; stretching is not"
                severity note;
         ck_bit("T7 SCL is low", scl, '0');
         ck_bit("T7 the SDA mismatch exists", sda, '0');
         ck_bit("T7 but no loss is declared", arb_now, '0');
         ck_bit("T7 while the clock IS reported as held", scl_held, '1');
         wait until falling_edge(clk); oth_scl_low <= '0'; wait for 1 ns;
         ck_bit("T7 and the same mismatch with SCL high is a loss", arb_now, '1');

         -- T8. THE SECOND DIFFERENCE: intent. A receiving master released SDA on purpose.
         do_reset;
         wait until falling_edge(clk); tx_active <= '0';
         wait until falling_edge(clk); oth_sda_low <= '1'; wait for 1 ns;
         report "T8  a receiving master reading a zero has lost nothing" severity note;
         ck_bit("T8 the electrical condition is present", sda, '0');
         ck_bit("T8 but no loss is declared", arb_now, '0');
         step;
         ck_bit("T8 and none is latched", arb_lost, '0');
         wait until falling_edge(clk); tx_active <= '1'; wait for 1 ns;
         ck_bit("T8 the identical bus state while transmitting IS a loss", arb_now, '1');

         -- T9. THE ASYMMETRY. A master can only lose by sending a ONE.
         do_reset;
         wait until falling_edge(clk); sda_release <= '0';
         wait until falling_edge(clk); oth_sda_low <= '1';
         wait for 1 ns;
         report "T9  a master transmitting a zero cannot detect a competitor" severity note;
         ck_bit("T9 the line is low, exactly as we intended", sda, '0');
         ck_int("T9 and two devices are holding it", to_integer(sda_h), 2);
         ck_bit("T9 no loss is detected", arb_now, '0');
         step;
         ck_int("T9 no losses counted", to_integer(n_arb), 0);

         -- T10. ONE COMPARISON, TWO FEATURES, reported independently.
         do_reset;
         wait until falling_edge(clk); oth_scl_low <= '1';
         for j in 0 to 4 loop step; end loop;
         wait until falling_edge(clk); oth_scl_low <= '0';
         wait until falling_edge(clk); oth_sda_low <= '1'; wait for 1 ns;
         report "T10 the same comparison on two lines, reported independently" severity note;
         ck_bit("T10 the stretch was recorded", s_seen, '1');
         ck_bit("T10 the clock is free again", scl_held, '0');
         ck_bit("T10 and now arbitration is lost", arb_now, '1');
         step;
         ck_int("T10 one stretch event", to_integer(s_ev), 1);
         ck_int("T10 one arbitration loss", to_integer(n_arb), 1);

         -- T11. A loss is counted once per contest, and is sticky until cleared.
         for j in 0 to 19 loop step; end loop;
         report "T11 a loss is counted once per contest and is sticky until cleared"
                severity note;
         ck_int("T11 still one loss", to_integer(n_arb), 1);
         ck_bit("T11 still latched", arb_lost, '1');
         wait until falling_edge(clk); clear <= '1'; step;
         wait until falling_edge(clk); clear <= '0'; oth_sda_low <= '0'; wait for 1 ns;
         ck_bit("T11 cleared", arb_lost, '0');
         ck_bit("T11 and the timeout cleared with it", s_to, '0');

         -- T12. Reset clears everything.
         wait until falling_edge(clk); oth_scl_low <= '1'; oth_sda_low <= '1';
         for j in 0 to 9 loop step; end loop;
         wait until falling_edge(clk); rst_n <= '0'; step;
         report "T12 reset clears every latched report" severity note;
         ck_bit("T12 no stretch seen", s_seen, '0');
         ck_int("T12 no stretch cycles", to_integer(s_cyc), 0);
         ck_int("T12 no stretch events", to_integer(s_ev), 0);
         ck_int("T12 no longest stretch", to_integer(s_long), 0);
         ck_bit("T12 no timeout", s_to, '0');
         ck_bit("T12 no arbitration loss", arb_lost, '0');
         ck_int("T12 no losses", to_integer(n_arb), 0);

         if err = 0 then
            report "=== i2c_bus_feedback: ALL CHECKS PASSED ===" severity note;
         else
            report "=== i2c_bus_feedback: " & integer'image(err)
                   & " CHECK(S) FAILED ===" severity note;
         end if;
         halt <= true;
         wait;
      end process;

   end architecture sim;

6b. Execution

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_bus_feedbackPASS 12/12PASS 12/12PASS 12/122540 ns, all three

7. Mutation Testing

Ten defects, one per claim.

#Injected defectExpected detectionResult
M1compare against our own intent instead of the observed lineT2, T4KILLED (13)
M2report a stretch while we are holding SCL lowT3KILLED (3)
M3drop the SCL-high qualifier on arbitrationT7KILLED (2)
M4drop the intent gate — every received zero is a lossT8KILLED (3)
M5lose arbitration while transmitting a zero tooT9KILLED (3)
M6swap the two lines — stretch on SDA, arbitration on SCLT2, T6, T10KILLED (20)
M7count a stretch once per cycle instead of once per eventT4KILLED (4)
M8the longest stretch overwritten rather than kept as a maximumT4KILLED (3)
M9the timeout bound off by oneT5 after strengtheningKILLED (2)
M10a loss counted once per cycle of the mismatchT11KILLED (2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
killed: 10   survived: 0   score: 10/10
restored: PASS

M9 survived a test written for exactly that property

T5 was the policy-bound test, and it held the clock for SLIMIT + 4 cycles to check the flag trips, then SLIMIT - 5 cycles to check it does not.

That leaves a nine-cycle gap in which the comparison can be off by one — or two, or four — with nothing to notice. The test was about the right property, used the right signals, and simply never approached the boundary.

The fix was to measure where the boundary actually is rather than assume it, then pin it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
measured with STRETCH_LIMIT = 12:
  held 10 cycles -> stretch_timeout = 0
  held 11 cycles -> stretch_timeout = 0
  held 12 cycles -> stretch_timeout = 1     <- trips at exactly SLIMIT
  held 13 cycles -> stretch_timeout = 1

So the boundary is stretch_cycles == SLIMIT, and T5 now asserts both sides of that single cycle — SLIMIT - 1 does not trip, one more cycle does — with the cycle count itself checked so the test cannot drift.

On the spread: 20 and 13 versus 2

M6 (swapping the lines) and M1 (comparing intent against intent) fail 20 and 13 checks because they break the block's relationship with the bus wholesale. M3, M9, M10 fail two each — each guarded by exactly one test. As in 17.3 §7 and 17.6 §7, the low counts mark single points of failure in the suite, and M3 is the SCL-high qualifier that took the specification's own wording to justify.

8. Verification Connection — Injecting Interference, and the Coverage That Must Exist

Azvya Education Pvt. Ltd.VLSI Mentor
interference_agent.sv — the component that makes this block testable
   // This block's entire subject is what OTHER devices do, so its verification needs a
   // component that interferes on purpose. Three capabilities, and they are separate:
   //
   //   1. HOLD SCL for a configurable number of cycles, at a configurable point in the
   //      transfer. That is a stretching target (§3.1.6) AND a competing master with a
   //      longer low period (§3.1.7) -- the same injection, because §1a says the two are
   //      the same electrical event.
   //
   //   2. DRIVE SDA LOW during a bit slot in which the DUT is transmitting a ONE. That
   //      is arbitration loss, and note the precondition: the agent must know what the
   //      DUT intends to send, which means it has to track the frame. An agent that
   //      drives SDA low at random produces losses the DUT is right to report and also
   //      corrupts bits the DUT was receiving, which looks identical and is not.
   //
   //   3. DO NEITHER, which is the T1 case and the one people forget. A false-positive
   //      test is what keeps a checker switched on: if the block reports a stretch on a
   //      quiet bus, every later stretch result is worthless.
   //
   // WHAT THE AGENT MUST NOT DO: infer the DUT's tx_active from the DUT. Section 2 says
   // intent is not on the wire; an agent that peeks at it can only confirm the DUT
   // agrees with itself. In a multi-master bench the ENVIRONMENT owns both sides, so
   // intent is known because the environment generated it.
   //
   // COVERAGE. The reachable/unreachable split here is not cosmetic -- it is the §3
   // asymmetry, and leaving it implicit guarantees somebody writes stimulus chasing an
   // impossible bin:
   //
   //   cover: lost arbitration while sending a ONE            -- reachable
   //   illegal_bin: lost arbitration while sending a ZERO     -- UNREACHABLE BY DESIGN
   //   cover: SCL held, and released before the bound         -- the ordinary case
   //   cover: SCL held past the bound                         -- the policy decision
   //   cover: a stretch and an arbitration loss in one transfer -- T10, and rare
   //
   // The last bin is worth forcing. A stretch changes WHEN bits are sampled and a loss
   // changes WHETHER the master is driving; a design that handles each alone can still
   // get the interaction wrong, and no random stimulus reliably produces both inside
   // one byte.

9. FPGA and ASIC Implications

On an FPGA, this block is the one most affected by readback latency, because it is entirely a function of the readback. Both scl_in and sda_in arrive through synchronisers, so every report is two cycles late — and the two consequences are opposite in character.

For stretching, late is harmless: the master waits slightly longer than it strictly had to, and tHIGH has only a minimum.

For arbitration, late is a real obligation gap. §3.1.8 requires the driver off "the moment there is a difference", and a synchronised readback means the master drives for two more cycles after the difference appeared. That is unavoidable in any synchronous implementation and is why the specification's "moment" cannot be read as a hard timing requirement — the physical situation is that the winner is already holding the line low, so the loser's continued drive changes nothing electrically. Chapter 17.6 §4's immediate abort release is the best a synchronous design can do, and it is sufficient for exactly that reason.

On an ASIC, the pad's input filter adds to the same latency, and it has a useful side effect here: it prevents a glitch on SDA from being read as an arbitration loss. Without filtering, a spike during SCL-high would satisfy the comparison and latch a sticky arb_lost on a bus with no competitor at all. The filter is what makes a two-line comparator adequate rather than requiring digital debouncing per line.

The counters belong in status registers. longest_stretch in particular is the number that tells firmware whether a slow bus is one device's fault or the whole bus's character, and it cannot be recovered after the fact from anything else.

10. Debugging — The Arbitration Loss That Was One Master Fighting Itself

Symptom

A two-master system works for months. A firmware update adds a second driver thread on the main SoC, which now accesses the bus from two places. The SoC's master begins reporting arbitration lost on roughly one transaction in twenty, always on the address byte, and always retrying successfully. The second master -- a power controller -- reports nothing unusual and its transaction rate has not changed.

Root Cause

Two software threads driving one master caused two internal blocks to request SDA simultaneously. The ownership resolver did its job -- it granted the framer, reported the conflict, and kept the bus in a defined state -- but the byte engine's released SDA then read back low, which is indistinguishable from another master winning arbitration. The feedback block was correct on every input it had: it cannot tell whose zero pulled the line down, because the wired-AND does not carry that information and section 1a already established that the holder's identity is not observable. The defect was upstream, in software sharing one master between two threads with no mutual exclusion, and the arbitration report was a true statement about a contest the master was having with itself.

Fix
Serialise access to the master in software -- one owner per transaction, which is what the single command register already implies and what the refused-while-busy behaviour was telling the driver. Then use the signal that distinguishes the two cases: owner_conflict. An arbitration loss with a coincident owner_conflict is an internal bug; one without it is a real competitor. That pairing is exactly why Chapter 17.4 reports conflicts rather than silently resolving them -- a resolver that only resolved would have produced the identical arbitration report with no way to tell the two causes apart. For the regression: a test that requests SDA from two owners during an active byte and asserts that owner_conflict fires, which is Chapter 17.4's T5, plus a system-level test that two concurrent commands cannot be issued.

Three generalisations.

The report was true and the diagnosis it suggested was wrong. "Arbitration lost" is a correct description of released-and-low-while-transmitting. It does not say who, and §1a established that the bus cannot say who. A report that is accurate about a condition can still point in the wrong direction about a cause.

The distinguishing evidence was a signal that exists only because someone chose to report a non-error. owner_conflict is not a bus condition — it is an internal design error, and Chapter 17.4 §2 argued for reporting it precisely because a silently-resolved conflict ships. Here it is the one signal that separates an internal contest from an external one.

Two correct blocks produced a misleading system-level symptom. The resolver resolved and reported; the feedback block compared and reported. Neither was wrong. The composition was, and it was caused two layers up in software.

11. Common Misconceptions

"Clock stretching and arbitration need two detectors." One comparison — released and low — on two wires, with two qualifiers on the SDA side. §1.

"A master can tell a stretching target from a competing master." It cannot: §3.1.6 and §3.1.7 describe the same electrical event. It also does not need to, because the response is the same. §1a.

"Arbitration can be judged at any time." Only while SCL is high. SDA is allowed to change while SCL is low, so a low there means nothing. §2.

"A receiving master is not arbitrating, so the logic is idle." It has released SDA, which is electrically a transmitted one — so without the intent gate every zero a target returns reads as a lost contest. §2.

"Intent can be inferred from the bus." It cannot. The electrical condition is identical in both cases, which is why tx_active must be an input. §2.

"A master could lose while sending a zero." It cannot detect that — sending a zero and reading a zero is indistinguishable from winning. That asymmetry is why bitwise arbitration terminates. §3.

"Exceeding the stretch limit means the target is faulty." §3.1.6 gives stretching no bound, so a target that stretches longer than this master will wait is still conforming. The flag says the master gave up. §5.

"An N + 4 and N - 5 test verifies a timeout." It proves the flag exists. The boundary is at N, and nothing in that test approaches it. §7.

"Derive the expected boundary from the RTL expression." Then an off-by-one in the test meets an off-by-one in the design and they cancel. Measure it. §7.

"An arbitration loss means another master." It means somebody's zero pulled the line down. On a design with multiple internal SDA owners, that somebody can be this master's own framer. §10.

12. Reason It Through

Why does one comparator implement both §3.1.6 and §3.1.8?

Because both are "I released this line and it reads low." Only the wire differs, plus two qualifiers on SDA. The observation is the same shape and, on SCL, so is the required response. §1.

The master releases SDA for a transmitted one and reads SDA low while SCL is high. What has happened, and what has not been established?

It has lost arbitration — if it was transmitting. What is not established is who won: the wired-AND does not carry identity, and §10 shows the competitor can even be another block inside the same master. §1b and §10.

Why is arbitration qualified by SCL-high when stretching is not?

Because SDA is permitted to change while SCL is low, so a low there carries no information. SCL has no equivalent permission — a held SCL is held whenever observed. §2.

Why can tx_active not be derived inside this block?

Because releasing to receive and releasing to transmit a one are electrically identical. The distinguishing fact exists only in the master's own intent. §2.

Why does bitwise arbitration terminate without a tie-break rule?

Because a master can only lose by sending a one, and two differing addresses must differ somewhere — so at the first differing bit exactly one side sees its one become a zero and drops out. §3.

A synchroniser delays the readback by two cycles. Which of the two features does that threaten, and why is it acceptable?

Arbitration, because §3.1.8 says the driver goes off "the moment there is a difference" and the master now drives two cycles longer. It is acceptable because the winner is already holding the line low, so the loser's extra drive changes nothing electrically. §9.

T5 held the clock for SLIMIT + 4 and SLIMIT - 5 and passed against an off-by-one. What was missing?

Any stimulus at the boundary. The two points differ by nine cycles, so a comparison wrong by one, two or four produces identical results. §7.

An arbitration loss is reported on a bus whose only other master is provably idle. What single signal separates the two possible causes?

owner_conflict. With it, two internal blocks fought for SDA; without it, a real competitor. §10.

13. Understanding Check

14. Summary

Clock stretching and arbitration loss are one comparison, not two features — released and still low — applied to two wires. A master that builds them separately has written the same logic twice.

On SCL the holder's identity is not observable and does not matter. §3.1.6's stretching target and §3.1.7's longer-low-period master are the same electrical event, and both require waiting.

On SDA the same comparison carries two qualifiers, both normative: arbitration is judged only while SCL is high, because SDA may legally change while SCL is low; and only while this master is transmitting, because releasing to receive is electrically a transmitted one.

Intent cannot be inferred from the bus, which is why tx_active is an input. Omitting it makes every zero a target returns look like a lost contest.

A master can only lose by sending a one, and that asymmetry is why bitwise arbitration terminates with no tie-break, priority or retry timer.

The stretch limit is a policy, not a verdict. §3.1.6 gives stretching no bound, so exceeding the limit says the master gave up — not that the bus is faulty.

Ten mutants, ten killed — after the timeout boundary survived a test written for exactly that property.

N + 4 and N - 5 is a smoke test, not a boundary test. It leaves nine cycles in which an off-by-one is invisible; the fix was to measure the trip point and pin both sides of the single cycle where it changes.

Measure expected boundary values, never derive them from the expression under test, or an error in the test cancels an error in the design.

And an accurate report can still mislead. "Arbitration lost" is a true description of released-and-low-while-transmitting; it does not identify the competitor, and on a master with several internal SDA owners the competitor can be the master's own framer — which only owner_conflict distinguishes.

15. What Comes Next

The master now notices when the bus disagrees with it. What it does not yet do is decide what to do about it.

Chapter 17.11 builds the error manager, and its subject is the line between a protocol requirement and an implementation policy. A NACK is a protocol outcome; a timeout is a number somebody chose. The chapter builds the error taxonomy, separates the normal outcomes from the genuine faults, and implements the nine-pulse recovery of §3.1.16 — including the honest account of what that procedure cannot do, because nine pulses on SCL cannot free a bus whose SCL is the line being held.

Continue learning

Related tutorials