Skip to content
VLSI Mentor

I²C · Module 18

START and STOP Detection Inside a Slave

Detection is two AND gates. The substance is what a START resets, and the distinction that is not on the wire at all — a first START and a repeated START are the identical edge, separated only by state the target keeps itself.

Chapter 18.2 gave the target five facts: two synchronised levels and four one-cycle edge pulses. This chapter turns them into the three events that structure every transfer.

The detection is almost nothing. Two lines of RTL:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
start_now = sda_fall && scl_q
stop_now  = sda_rise && scl_q

The chapter is about everything around them.

1. The Rule, and Why It Needs Both Lines

So framing is an SDA edge qualified by an SCL level — which is exactly why 18.2 exports both and not just edges.

2. A Slave's Detector Is Not the Master's Framer Reversed

Chapter 17.5 generated these edges. It knew when they were coming, so it could hold SDA for tHD;STA and count tSU;STO — it chose the instants.

A target has no such knowledge. By the time it observes the edge, the edge has already happened. Its only job is to classify it and reset the right state.

3. The Distinction That Is Not on the Wire

That is why this block produces bus_active as a level, and why every later chapter is gated on it. It is also why the block reports restart_pulse separately: Chapter 18.10 needs to know which kind arrived, because a repeated START must restart the frame while leaving a register pointer alive.

This block does not decide what survives. It only reports which kind of START it was.

4. The Trap: A Repeated START Begins by Releasing SDA

Look at how a master builds one (17.5 §2): mid-transfer SDA may be low from a data zero, and a line already low cannot fall. So the sequence lifts SDA first, while SCL is still low, then releases SCL, then drops SDA.

Test T7 asserts that no STOP is reported anywhere inside a repeated START sequence.

5. What Is Deliberately Not Here

A slave that silently absorbs a STOP arriving part-way through a byte cannot be debugged from outside the chip, which is the whole argument for reporting a condition this block does nothing about.

6. Framing, and Two Non-Events

One START, then two SDA transitions that are not framing

10 cycles
Ten clock cycles. The synchronised SCL level is high for five cycles then low for the rest. The synchronised SDA level starts high, falls in cycle three while SCL is still high, rises again in cycle seven while SCL is low, and falls again in cycle nine while SCL is low. A start pulse appears for exactly one cycle in cycle four, coinciding with the SDA fall that happened while SCL was high. No stop pulse appears at any point, including at the SDA rise in cycle seven. The bus-active level goes high in cycle four and stays high.idleidletransfer opentransfer openSDA falls, SCL high: a STARTSDA falls, SCL high: aSTARTSDA rises, SCL low: not a STOPSDA rises, SCL low: not aSTOPSDA falls, SCL low: not a STARTSDA falls, SCL low: not aSTARTscl_qsda_qstart_pulsestop_pulsebus_activet0t1t2t3t4t5t6t7t8t9
Figure 1 — from the running RTL. A START is detected in interval four; SDA then moves twice more, both times while SCL is LOW, and neither is framing. Simulation-derived: these are the values i2c_slave_framing actually produced behind the real front end.

Intervals 7 and 9 are the point of the figure. Both are SDA transitions; neither produces an event, because SCL is low. A detector that ignored the level would fire twice there — and the second one, being a fall, would restart the address phase in the middle of a byte.

A flowchart. An SDA edge arrives and enters a decision asking whether SCL is currently high. If no, the flow ends at a box labelled ordinary data preparation, not framing. If yes, a second decision asks the edge direction. A falling edge leads to a third decision asking whether a transfer is already open: if no, the result is a first START; if yes, the result is a repeated START. A rising edge leads instead to a STOP result. Both START results set the bus-active state, and the STOP result clears it.noyesfallingrisingnoyesAn SDA edgeIs SCL high?Data preparation— not framingWhichdirection?Transferalready open?STOP — clearbus_activeFirst STARTRepeated START
Figure 2 — the classification, drawn as the decision it is. Two AND conditions and one piece of state: nothing about the edge itself distinguishes a first START from a repeated one.

The two figures are separate deliberately: one is about when an event is an event, the other about which event it is. A composite would obscure both.

7. The Detector, in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_slave_framing.sv — START, repeated START and STOP from the slave's side
   // -----------------------------------------------------------------------------
   // i2c_slave_framing.sv
   // START, repeated START and STOP, from the slave's side of the wire.
   //
   // THE RULE, and it is two signals evaluated together. §3.1.1:
   //
   //   START: "A HIGH to LOW transition on the SDA line while SCL is HIGH"
   //   STOP:  "A LOW to HIGH transition on the SDA line while SCL is HIGH"
   //
   // So framing is an SDA EDGE qualified by an SCL LEVEL. Chapter 18.2 produces both, and
   // this block is almost entirely those two lines:
   //
   //   start_pulse = sda_fall && scl_q
   //   stop_pulse  = sda_rise && scl_q
   //
   // WHY A SLAVE'S FRAMING DETECTOR IS NOT THE MASTER'S FRAMER TURNED ROUND. Module 17.5
   // GENERATED these edges and therefore knew when they were coming; it could hold SDA for
   // tHD;STA because it chose the instant. A slave has no such knowledge: the edge has
   // already happened by the time it is observed, and the slave's only job is to classify
   // it correctly and reset the right state.
   //
   // AND THE HARD PART IS WHAT IT RESETS, not what it detects. Detection is two AND gates.
   // The design decision is that a START -- of either kind -- must return the slave's
   // PROTOCOL state to "expecting an address byte", because §3.1.10's note 4 requires it:
   //
   //   note 4, verbatim: "A START condition immediately followed by a STOP condition
   //   (void message) is an illegal format. Many devices however are designed to operate
   //   properly under this condition." And the operative requirement for a target is that
   //   a repeated START restarts the FRAME.
   //
   // So this block produces the one signal every later chapter is gated on -- `bus_active`
   // -- and the distinction between a first START and a repeated one, which is not on the
   // wire and is state:
   //
   //   not active + START = a first START
   //   active     + START = a repeated START
   //
   // Chapter 18.10 is where the consequence lives: framing state restarts, and application
   // state (a register pointer) may deliberately survive. This block only reports which
   // kind of START it was; it does not decide what survives.
   //
   // WHAT IS DELIBERATELY NOT HERE. No bit counter, no address register, no acknowledge.
   // A framing detector that also counted bits would have to be reset by its own output,
   // and the two jobs have different reset conditions -- which is how a slave ends up
   // unable to recover from a repeated START arriving mid-byte.
   // -----------------------------------------------------------------------------

   module i2c_slave_framing #(
      parameter int CNT_W = 16
   ) (
      input  logic clk,
      input  logic rst_n,

      // From Chapter 18.2. This block reads NOTHING else: no pins, no timing.
      input  logic scl_q,
      input  logic sda_rise,
      input  logic sda_fall,

      // The three framing events, one cycle each.
      output logic start_pulse,     // a START of either kind
      output logic restart_pulse,   // ... and it arrived while a transfer was open
      output logic stop_pulse,

      // The level every later chapter is gated on: between a START and a STOP.
      output logic bus_active,

      // Reported, not acted on: a START or STOP that arrived part-way through a byte.
      // Chapter 18.11 decides the policy; this block only counts it, because a slave that
      // silently absorbs mid-byte framing cannot be debugged from the outside.
      input  logic mid_byte,        // from the bit counter: a byte is in progress
      output logic framing_midbyte,

      output logic [CNT_W-1:0] n_starts,
      output logic [CNT_W-1:0] n_restarts,
      output logic [CNT_W-1:0] n_stops
   );

      // The two AND gates that are the whole of §3.1.1, from the slave's side.
      //
      // Note what is NOT qualified: there is no check that SDA was previously high before
      // a fall, because `sda_fall` already means exactly that -- Chapter 18.2 produced it
      // from a level change. Re-deriving the precondition here would be a second source of
      // truth about the same wire.
      wire start_now = sda_fall & scl_q;
      wire stop_now  = sda_rise & scl_q;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            start_pulse     <= 1'b0;
            restart_pulse   <= 1'b0;
            stop_pulse      <= 1'b0;
            // A slave out of reset must assume NO transfer is in progress. Assuming one
            // was open would make it try to interpret the middle of somebody else's
            // transaction as an address byte.
            bus_active      <= 1'b0;
            framing_midbyte <= 1'b0;
            n_starts        <= {CNT_W{1'b0}};
            n_restarts      <= {CNT_W{1'b0}};
            n_stops         <= {CNT_W{1'b0}};
         end else begin
            start_pulse     <= 1'b0;
            restart_pulse   <= 1'b0;
            stop_pulse      <= 1'b0;
            framing_midbyte <= 1'b0;

            if (start_now) begin
               start_pulse <= 1'b1;
               // The ONLY thing that distinguishes a repeated START from a first one is
               // whether a transfer was already open. It is not on the wire.
               if (bus_active) begin
                  restart_pulse <= 1'b1;
                  n_restarts    <= n_restarts + 1'b1;
               end else begin
                  n_starts <= n_starts + 1'b1;
               end
               bus_active <= 1'b1;
               if (mid_byte) framing_midbyte <= 1'b1;
            end else if (stop_now) begin
               stop_pulse <= 1'b1;
               n_stops    <= n_stops + 1'b1;
               bus_active <= 1'b0;
               if (mid_byte) framing_midbyte <= 1'b1;
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_slave_framing.v — the same design in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_slave_framing.v
   // START, repeated START and STOP, from the slave's side of the wire.
   //
   // THE RULE, and it is two signals evaluated together. §3.1.1:
   //
   //   START: "A HIGH to LOW transition on the SDA line while SCL is HIGH"
   //   STOP:  "A LOW to HIGH transition on the SDA line while SCL is HIGH"
   //
   // So framing is an SDA EDGE qualified by an SCL LEVEL. Chapter 18.2 produces both, and
   // this block is almost entirely those two lines:
   //
   //   start_pulse = sda_fall && scl_q
   //   stop_pulse  = sda_rise && scl_q
   //
   // WHY A SLAVE'S FRAMING DETECTOR IS NOT THE MASTER'S FRAMER TURNED ROUND. Module 17.5
   // GENERATED these edges and therefore knew when they were coming; it could hold SDA for
   // tHD;STA because it chose the instant. A slave has no such knowledge: the edge has
   // already happened by the time it is observed, and the slave's only job is to classify
   // it correctly and reset the right state.
   //
   // AND THE HARD PART IS WHAT IT RESETS, not what it detects. Detection is two AND gates.
   // The design decision is that a START -- of either kind -- must return the slave's
   // PROTOCOL state to "expecting an address byte", because §3.1.10's note 4 requires it:
   //
   //   note 4, verbatim: "A START condition immediately followed by a STOP condition
   //   (void message) is an illegal format. Many devices however are designed to operate
   //   properly under this condition." And the operative requirement for a target is that
   //   a repeated START restarts the FRAME.
   //
   // So this block produces the one signal every later chapter is gated on -- `bus_active`
   // -- and the distinction between a first START and a repeated one, which is not on the
   // wire and is state:
   //
   //   not active + START = a first START
   //   active     + START = a repeated START
   //
   // Chapter 18.10 is where the consequence lives: framing state restarts, and application
   // state (a register pointer) may deliberately survive. This block only reports which
   // kind of START it was; it does not decide what survives.
   //
   // WHAT IS DELIBERATELY NOT HERE. No bit counter, no address register, no acknowledge.
   // A framing detector that also counted bits would have to be reset by its own output,
   // and the two jobs have different reset conditions -- which is how a slave ends up
   // unable to recover from a repeated START arriving mid-byte.
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   // -----------------------------------------------------------------------------

   module i2c_slave_framing #(
      parameter integer CNT_W = 16
   ) (
      input  wire  clk,
      input  wire  rst_n,

      // From Chapter 18.2. This block reads NOTHING else: no pins, no timing.
      input  wire  scl_q,
      input  wire  sda_rise,
      input  wire  sda_fall,

      // The three framing events, one cycle each.
      output reg   start_pulse,     // a START of either kind
      output reg   restart_pulse,   // ... and it arrived while a transfer was open
      output reg   stop_pulse,

      // The level every later chapter is gated on: between a START and a STOP.
      output reg   bus_active,

      // Reported, not acted on: a START or STOP that arrived part-way through a byte.
      // Chapter 18.11 decides the policy; this block only counts it, because a slave that
      // silently absorbs mid-byte framing cannot be debugged from the outside.
      input  wire  mid_byte,        // from the bit counter: a byte is in progress
      output reg   framing_midbyte,

      output reg   [CNT_W-1:0] n_starts,
      output reg   [CNT_W-1:0] n_restarts,
      output reg   [CNT_W-1:0] n_stops
   );

      // The two AND gates that are the whole of §3.1.1, from the slave's side.
      //
      // Note what is NOT qualified: there is no check that SDA was previously high before
      // a fall, because `sda_fall` already means exactly that -- Chapter 18.2 produced it
      // from a level change. Re-deriving the precondition here would be a second source of
      // truth about the same wire.
      wire start_now = sda_fall & scl_q;
      wire stop_now  = sda_rise & scl_q;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            start_pulse     <= 1'b0;
            restart_pulse   <= 1'b0;
            stop_pulse      <= 1'b0;
            // A slave out of reset must assume NO transfer is in progress. Assuming one
            // was open would make it try to interpret the middle of somebody else's
            // transaction as an address byte.
            bus_active      <= 1'b0;
            framing_midbyte <= 1'b0;
            n_starts        <= {CNT_W{1'b0}};
            n_restarts      <= {CNT_W{1'b0}};
            n_stops         <= {CNT_W{1'b0}};
         end else begin
            start_pulse     <= 1'b0;
            restart_pulse   <= 1'b0;
            stop_pulse      <= 1'b0;
            framing_midbyte <= 1'b0;

            if (start_now) begin
               start_pulse <= 1'b1;
               // The ONLY thing that distinguishes a repeated START from a first one is
               // whether a transfer was already open. It is not on the wire.
               if (bus_active) begin
                  restart_pulse <= 1'b1;
                  n_restarts    <= n_restarts + 1'b1;
               end else begin
                  n_starts <= n_starts + 1'b1;
               end
               bus_active <= 1'b1;
               if (mid_byte) framing_midbyte <= 1'b1;
            end else if (stop_now) begin
               stop_pulse <= 1'b1;
               n_stops    <= n_stops + 1'b1;
               bus_active <= 1'b0;
               if (mid_byte) framing_midbyte <= 1'b1;
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_slave_framing.vhd — the same design in VHDL
   -- -----------------------------------------------------------------------------
   -- i2c_slave_framing.vhd
   -- START, repeated START and STOP from the slave's side, in VHDL. Same ports, same
   -- generic, same reset values and same one-cycle pulse timing as the SystemVerilog and
   -- Verilog versions.
   -- -----------------------------------------------------------------------------
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_slave_framing is
      generic (
         CNT_W : positive := 16
      );
      port (
         clk   : in  std_logic;
         rst_n : in  std_logic;

         -- From Chapter 18.2. This block reads NOTHING else: no pins, no timing.
         scl_q    : in  std_logic;
         sda_rise : in  std_logic;
         sda_fall : in  std_logic;

         -- The three framing events, one cycle each.
         start_pulse   : out std_logic;
         restart_pulse : out std_logic;
         stop_pulse    : out std_logic;

         -- The level every later chapter is gated on.
         bus_active : out std_logic;

         -- Reported, not acted on.
         mid_byte        : in  std_logic;
         framing_midbyte : out std_logic;

         n_starts   : out unsigned(CNT_W-1 downto 0);
         n_restarts : out unsigned(CNT_W-1 downto 0);
         n_stops    : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_slave_framing;

   architecture rtl of i2c_slave_framing is
      -- The two AND gates that are the whole of §3.1.1, from the slave's side.
      signal start_now, stop_now : std_logic;

      signal r_start, r_restart, r_stop, r_active, r_mid : std_logic := '0';
      signal c_sta, c_rs, c_sto : unsigned(CNT_W-1 downto 0) := (others => '0');
   begin

      start_now <= sda_fall and scl_q;
      stop_now  <= sda_rise and scl_q;

      process (clk, rst_n)
      begin
         if rst_n = '0' then
            r_start   <= '0';
            r_restart <= '0';
            r_stop    <= '0';
            -- A slave out of reset must assume NO transfer is in progress.
            r_active  <= '0';
            r_mid     <= '0';
            c_sta     <= (others => '0');
            c_rs      <= (others => '0');
            c_sto     <= (others => '0');
         elsif rising_edge(clk) then
            r_start   <= '0';
            r_restart <= '0';
            r_stop    <= '0';
            r_mid     <= '0';

            if start_now = '1' then
               r_start <= '1';
               -- The ONLY thing that distinguishes a repeated START from a first one is
               -- whether a transfer was already open. It is not on the wire.
               if r_active = '1' then
                  r_restart <= '1';
                  c_rs      <= c_rs + 1;
               else
                  c_sta <= c_sta + 1;
               end if;
               r_active <= '1';
               if mid_byte = '1' then r_mid <= '1'; end if;
            elsif stop_now = '1' then
               r_stop   <= '1';
               c_sto    <= c_sto + 1;
               r_active <= '0';
               if mid_byte = '1' then r_mid <= '1'; end if;
            end if;
         end if;
      end process;

      start_pulse     <= r_start;
      restart_pulse   <= r_restart;
      stop_pulse      <= r_stop;
      bus_active      <= r_active;
      framing_midbyte <= r_mid;
      n_starts        <= c_sta;
      n_restarts      <= c_rs;
      n_stops         <= c_sto;

   end architecture rtl;

7a. The testbenches

Thirteen checks. The bench is a hand-written controller: SDA changes only while SCL is LOW for data and only while SCL is HIGH for framing. That distinction is the chapter's subject, so a bench that blurred it could not test the block.

#TestProperty
T1reset assumes no transferotherwise it reads somebody else's data as an address
T2a quiet bus produces nothingthe false-positive test
T3a START is one pulse, and opens the transfer
T4the central negative test — SDA moving while SCL is LOW is data
T5a STOP closes the transfer
T6a repeated START is the same edgeonly the state differs
T7and the SDA lift inside it is not a STOP§4's trap
T8after a STOP the next START is a first START againthe classification must clear
T9mid-byte framing on a STOP is reportednot absorbed
T9bmid-byte framing on a repeated START is reported toothe other half; see §8
T10and it is not reported between bytesthe false-positive half
T11three complete frames, nothing accumulates
T12reset mid-transfer returns to idle and does not resume
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_slave_framing_tb.sv — the self-checking testbench
   // -----------------------------------------------------------------------------
   // i2c_slave_framing_tb.sv
   // Independent oracle for i2c_slave_framing, driven through the real front end.
   //
   // The bench is a hand-written CONTROLLER: it drives the two pins the way a master
   // does, with SDA changed only while SCL is low for data and only while SCL is high for
   // framing. That matters -- a bench that drove framing and data the same way could not
   // distinguish a detector that checks the SCL level from one that does not.
   //
   // The DUT is instantiated behind i2c_slave_sync, so what is being tested is the pair as
   // the slave will actually use it. Every wait is bounded.
   // -----------------------------------------------------------------------------
   `timescale 1ns/1ps

   module i2c_slave_framing_tb;

      // A deliberately slow bus relative to clk: eight cycles per half period, so the
      // synchroniser's two-cycle latency is a small fraction of a phase, as on real
      // hardware. Chapter 18.5 is where the ratio starts to constrain the design.
      localparam integer HALF = 8;

      logic clk = 1'b0, rst_n = 1'b0;
      logic m_scl = 1'b1, m_sda = 1'b1;     // the controller's drive intent, active HIGH
                                            // in the bench for readability
      logic mid_byte = 1'b0;

      // Open-drain resolution, exactly as the bus does it: the line is low if the
      // controller pulls it low. Nothing else drives here.
      wire scl_pin = m_scl;
      wire sda_pin = m_sda;

      logic scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall;
      logic start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte;
      logic [15:0] n_sta, n_rs, n_sto;

      integer errors = 0;
      integer n, k;

      // ---- observers -------------------------------------------------------------
      integer n_start_obs = 0, n_restart_obs = 0, n_stop_obs = 0, n_mid_obs = 0;
      integer wide = 0;
      logic sp_d = 1'b0, rp_d = 1'b0, tp_d = 1'b0;
      always @(posedge clk) begin
         if (rst_n) begin
            if (start_pulse)     n_start_obs   <= n_start_obs + 1;
            if (restart_pulse)   n_restart_obs <= n_restart_obs + 1;
            if (stop_pulse)      n_stop_obs    <= n_stop_obs + 1;
            if (framing_midbyte) n_mid_obs     <= n_mid_obs + 1;
            if ((start_pulse & sp_d) | (restart_pulse & rp_d) | (stop_pulse & tp_d))
               wide <= wide + 1;
         end
         sp_d <= start_pulse; rp_d <= restart_pulse; tp_d <= stop_pulse;
      end

      i2c_slave_sync #(.SYNC_DEPTH(2)) u_sync (
         .clk(clk), .rst_n(rst_n), .scl_pin(scl_pin), .sda_pin(sda_pin),
         .scl_q(scl_q), .sda_q(sda_q),
         .scl_rise(scl_rise), .scl_fall(scl_fall),
         .sda_rise(sda_rise), .sda_fall(sda_fall));

      i2c_slave_framing #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .scl_q(scl_q), .sda_rise(sda_rise), .sda_fall(sda_fall),
         .start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
         .bus_active(bus_active), .mid_byte(mid_byte), .framing_midbyte(framing_midbyte),
         .n_starts(n_sta), .n_restarts(n_rs), .n_stops(n_sto));

      always #5 clk = ~clk;

      task step; begin @(posedge clk); @(negedge clk); end endtask
      task phase; begin for (n = 0; n < HALF; n = n + 1) step; end endtask

      task do_reset;
         begin
            @(negedge clk); rst_n = 1'b0; m_scl = 1'b1; m_sda = 1'b1; mid_byte = 1'b0;
            n_start_obs = 0; n_restart_obs = 0; n_stop_obs = 0; n_mid_obs = 0; wide = 0;
            step; step;
            @(negedge clk); rst_n = 1'b1;
            phase;
         end
      endtask

      // ---- controller-side stimulus ---------------------------------------------
      //
      // A START: SDA falls while SCL is HIGH. Then SCL is pulled low to begin the transfer.
      task gen_start;
         begin
            @(negedge clk); m_sda = 1'b1; m_scl = 1'b1; phase;
            @(negedge clk); m_sda = 1'b0;               // the START edge
            phase;
            @(negedge clk); m_scl = 1'b0;
            phase;
         end
      endtask

      // A repeated START: release SDA while SCL is LOW (ordinary preparation), release
      // SCL, then drop SDA while SCL is HIGH. The first move must NOT be seen as framing.
      task gen_restart;
         begin
            @(negedge clk); m_scl = 1'b0; m_sda = 1'b1; phase;   // SDA up, SCL still low
            @(negedge clk); m_scl = 1'b1; phase;                  // SCL up
            @(negedge clk); m_sda = 1'b0; phase;                  // the Sr edge
            @(negedge clk); m_scl = 1'b0; phase;
         end
      endtask

      // A STOP: SDA low, SCL released high, then SDA released while SCL is HIGH.
      task gen_stop;
         begin
            @(negedge clk); m_scl = 1'b0; m_sda = 1'b0; phase;
            @(negedge clk); m_scl = 1'b1; phase;
            @(negedge clk); m_sda = 1'b1;               // the STOP edge
            phase;
         end
      endtask

      // One ordinary data bit: SDA is changed only while SCL is LOW, then clocked.
      task gen_bit (input b);
         begin
            @(negedge clk); m_scl = 1'b0; phase;
            @(negedge clk); m_sda = b;    phase;        // changed in the LOW phase
            @(negedge clk); m_scl = 1'b1; phase;
            @(negedge clk); m_scl = 1'b0; phase;
         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

      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

      initial begin
         $display("=== i2c_slave_framing: an SDA edge qualified by an SCL level ===");

         // ----------------------------------------------------------------
         // T1. RESET ASSUMES NO TRANSFER. A slave that came up believing one was open
         //     would try to read the middle of somebody else's transaction as an address.
         // ----------------------------------------------------------------
         do_reset;
         $display("T1  a slave out of reset assumes the bus is idle");
         ck_bit("T1 no transfer open", bus_active, 1'b0);
         ck_int("T1 no framing seen", n_start_obs + n_stop_obs, 0);

         // ----------------------------------------------------------------
         // T2. A QUIET BUS PRODUCES NOTHING. The false-positive test.
         // ----------------------------------------------------------------
         for (k = 0; k < 6; k = k + 1) phase;
         $display("T2  a quiet bus produces no framing");
         ck_int("T2 still nothing", n_start_obs + n_stop_obs, 0);
         ck_bit("T2 still idle", bus_active, 1'b0);

         // ----------------------------------------------------------------
         // T3. A START. One pulse, one cycle wide, and the bus becomes active.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         $display("T3  a START is one pulse, and it opens the transfer");
         ck_int("T3 exactly one START", n_start_obs, 1);
         ck_int("T3 and it was not a repeated one", n_restart_obs, 0);
         ck_bit("T3 the bus is now active", bus_active, 1'b1);
         ck_int("T3 the pulse was one cycle", wide, 0);
         ck_int("T3 the counter agrees", n_sta, 1);

         // ----------------------------------------------------------------
         // T4. THE CENTRAL NEGATIVE TEST. An SDA transition while SCL is LOW is ordinary
         //     data preparation and is NOT framing. A detector that ignored the SCL level
         //     would report a START on every data bit that happens to be a zero -- which
         //     is most of them -- and the slave would restart its address phase mid-byte,
         //     forever.
         // ----------------------------------------------------------------
         n_start_obs = 0; n_stop_obs = 0; n_restart_obs = 0;
         gen_bit(1'b0);          // SDA falls while SCL is low
         gen_bit(1'b1);          // and rises while SCL is low
         gen_bit(1'b0);
         $display("T4  SDA moving while SCL is LOW is data, not framing");
         ck_int("T4 no START was reported", n_start_obs, 0);
         ck_int("T4 no STOP was reported", n_stop_obs, 0);
         ck_bit("T4 and the transfer is still the same one", bus_active, 1'b1);

         // ----------------------------------------------------------------
         // T5. A STOP closes the transfer.
         // ----------------------------------------------------------------
         gen_stop;
         $display("T5  a STOP closes the transfer");
         ck_int("T5 exactly one STOP", n_stop_obs, 1);
         ck_bit("T5 the bus is idle again", bus_active, 1'b0);
         ck_int("T5 the counter agrees", n_sto, 1);

         // ----------------------------------------------------------------
         // T6. A REPEATED START, and the distinction that is NOT on the wire. The edge is
         //     identical to a first START; only `bus_active` separates them.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         gen_bit(1'b1); gen_bit(1'b0);
         n_start_obs = 0; n_restart_obs = 0;
         gen_restart;
         $display("T6  a repeated START is the same edge; only the state differs");
         ck_int("T6 a START was reported", n_start_obs, 1);
         ck_int("T6 and it was flagged as a REPEATED start", n_restart_obs, 1);
         ck_bit("T6 the transfer is still open", bus_active, 1'b1);
         ck_int("T6 the restart counter moved", n_rs, 1);

         // ----------------------------------------------------------------
         // T7. AND THE SDA RELEASE THAT PRECEDES A REPEATED START IS NOT A STOP. This is
         //     the trap: a repeated START begins by lifting SDA, and if that lift were
         //     classified as framing the slave would see STOP-then-START and end the
         //     transaction -- losing exactly the state a repeated START exists to keep.
         // ----------------------------------------------------------------
         ck_int("T7 no STOP was reported during the repeated START", n_stop_obs, 0);

         // ----------------------------------------------------------------
         // T8. A FIRST START AFTER A STOP counts as a first START again, not a repeated
         //     one. The classification is stateful, so it must clear.
         // ----------------------------------------------------------------
         gen_stop;
         n_start_obs = 0; n_restart_obs = 0;
         gen_start;
         $display("T8  after a STOP the next START is a first START again");
         ck_int("T8 one START", n_start_obs, 1);
         ck_int("T8 and NOT a repeated one", n_restart_obs, 0);

         // ----------------------------------------------------------------
         // T9. MID-BYTE FRAMING IS REPORTED, not absorbed. A slave that silently swallows
         //     a STOP arriving part-way through a byte cannot be debugged from outside,
         //     and Chapter 18.11 needs the report to decide a policy.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         @(negedge clk); mid_byte = 1'b1;     // the bit counter says a byte is in progress
         n_mid_obs = 0;
         gen_stop;
         $display("T9  framing arriving mid-byte is reported rather than absorbed");
         ck_int("T9 the mid-byte report fired", n_mid_obs, 1);
         @(negedge clk); mid_byte = 1'b0;

         // ----------------------------------------------------------------
         // T9b. AND THE OTHER HALF: mid-byte framing on a repeated START. T9 tests the STOP
         //      path only, so the START path's report was untested -- and the two are
         //      separate branches. A repeated START arriving part-way through a byte is the
         //      more likely of the two in practice, because a master that aborts a transfer
         //      usually retries with a restart rather than a STOP.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         @(negedge clk); mid_byte = 1'b1;
         n_mid_obs = 0;
         gen_restart;
         $display("T9b a repeated START mid-byte is reported too, not only a STOP");
         ck_int("T9b the mid-byte report fired on the restart", n_mid_obs, 1);
         @(negedge clk); mid_byte = 1'b0;

         // ----------------------------------------------------------------
         // T10. AND IT IS NOT REPORTED WHEN NO BYTE IS IN PROGRESS. The false-positive
         //      half: an ordinary STOP between bytes is completely normal.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         n_mid_obs = 0;
         gen_stop;
         $display("T10 an ordinary STOP between bytes is not a mid-byte report");
         ck_int("T10 no mid-byte report", n_mid_obs, 0);

         // ----------------------------------------------------------------
         // T11. MANY TRANSFERS, and nothing accumulates. Three complete frames.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 3; k = k + 1) begin
            gen_start;
            gen_bit(1'b1); gen_bit(1'b0); gen_bit(1'b1);
            gen_stop;
         end
         $display("T11 three complete frames, counted exactly");
         ck_int("T11 three STARTs", n_start_obs, 3);
         ck_int("T11 three STOPs", n_stop_obs, 3);
         ck_int("T11 no repeated STARTs", n_restart_obs, 0);
         ck_bit("T11 and the bus is left idle", bus_active, 1'b0);
         ck_int("T11 no pulse was ever wide", wide, 0);

         // ----------------------------------------------------------------
         // T12. RESET MID-TRANSFER RETURNS TO IDLE. Whatever the bus is doing, a reset
         //      slave has no transfer of its own -- and must not resume one it cannot
         //      have seen the beginning of.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         ck_bit("T12 a transfer is open before reset", bus_active, 1'b1);
         @(negedge clk); rst_n = 1'b0; step; step;
         @(negedge clk); rst_n = 1'b1; phase;
         $display("T12 reset mid-transfer returns to idle and does not resume");
         ck_bit("T12 no transfer open after reset", bus_active, 1'b0);
         ck_int("T12 counters cleared", n_sta + n_rs + n_sto, 0);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_slave_framing_tb.v — the same tests in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_slave_framing_tb.v
   // Independent oracle for i2c_slave_framing, driven through the real front end.
   //
   // The bench is a hand-written CONTROLLER: it drives the two pins the way a master
   // does, with SDA changed only while SCL is low for data and only while SCL is high for
   // framing. That matters -- a bench that drove framing and data the same way could not
   // distinguish a detector that checks the SCL level from one that does not.
   //
   // The DUT is instantiated behind i2c_slave_sync, so what is being tested is the pair as
   // the slave will actually use it. Every wait is bounded.
   // -----------------------------------------------------------------------------
   `timescale 1ns/1ps

   module i2c_slave_framing_tb;

      // A deliberately slow bus relative to clk: eight cycles per half period, so the
      // synchroniser's two-cycle latency is a small fraction of a phase, as on real
      // hardware. Chapter 18.5 is where the ratio starts to constrain the design.
      localparam integer HALF = 8;

      reg clk = 1'b0, rst_n = 1'b0;
      reg m_scl = 1'b1, m_sda = 1'b1;     // the controller's drive intent, active HIGH
                                            // in the bench for readability
      reg mid_byte = 1'b0;

      // Open-drain resolution, exactly as the bus does it: the line is low if the
      // controller pulls it low. Nothing else drives here.
      wire scl_pin = m_scl;
      wire sda_pin = m_sda;

      wire scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall;
      wire start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte;
      wire [15:0] n_sta, n_rs, n_sto;

      integer errors = 0;
      integer n, k;

      // ---- observers -------------------------------------------------------------
      integer n_start_obs = 0, n_restart_obs = 0, n_stop_obs = 0, n_mid_obs = 0;
      integer wide = 0;
      reg sp_d = 1'b0, rp_d = 1'b0, tp_d = 1'b0;
      always @(posedge clk) begin
         if (rst_n) begin
            if (start_pulse)     n_start_obs   <= n_start_obs + 1;
            if (restart_pulse)   n_restart_obs <= n_restart_obs + 1;
            if (stop_pulse)      n_stop_obs    <= n_stop_obs + 1;
            if (framing_midbyte) n_mid_obs     <= n_mid_obs + 1;
            if ((start_pulse & sp_d) | (restart_pulse & rp_d) | (stop_pulse & tp_d))
               wide <= wide + 1;
         end
         sp_d <= start_pulse; rp_d <= restart_pulse; tp_d <= stop_pulse;
      end

      i2c_slave_sync #(.SYNC_DEPTH(2)) u_sync (
         .clk(clk), .rst_n(rst_n), .scl_pin(scl_pin), .sda_pin(sda_pin),
         .scl_q(scl_q), .sda_q(sda_q),
         .scl_rise(scl_rise), .scl_fall(scl_fall),
         .sda_rise(sda_rise), .sda_fall(sda_fall));

      i2c_slave_framing #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .scl_q(scl_q), .sda_rise(sda_rise), .sda_fall(sda_fall),
         .start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
         .bus_active(bus_active), .mid_byte(mid_byte), .framing_midbyte(framing_midbyte),
         .n_starts(n_sta), .n_restarts(n_rs), .n_stops(n_sto));

      always #5 clk = ~clk;

      task step; begin @(posedge clk); @(negedge clk); end endtask
      task phase; begin for (n = 0; n < HALF; n = n + 1) step; end endtask

      task do_reset;
         begin
            @(negedge clk); rst_n = 1'b0; m_scl = 1'b1; m_sda = 1'b1; mid_byte = 1'b0;
            n_start_obs = 0; n_restart_obs = 0; n_stop_obs = 0; n_mid_obs = 0; wide = 0;
            step; step;
            @(negedge clk); rst_n = 1'b1;
            phase;
         end
      endtask

      // ---- controller-side stimulus ---------------------------------------------
      //
      // A START: SDA falls while SCL is HIGH. Then SCL is pulled low to begin the transfer.
      task gen_start;
         begin
            @(negedge clk); m_sda = 1'b1; m_scl = 1'b1; phase;
            @(negedge clk); m_sda = 1'b0;               // the START edge
            phase;
            @(negedge clk); m_scl = 1'b0;
            phase;
         end
      endtask

      // A repeated START: release SDA while SCL is LOW (ordinary preparation), release
      // SCL, then drop SDA while SCL is HIGH. The first move must NOT be seen as framing.
      task gen_restart;
         begin
            @(negedge clk); m_scl = 1'b0; m_sda = 1'b1; phase;   // SDA up, SCL still low
            @(negedge clk); m_scl = 1'b1; phase;                  // SCL up
            @(negedge clk); m_sda = 1'b0; phase;                  // the Sr edge
            @(negedge clk); m_scl = 1'b0; phase;
         end
      endtask

      // A STOP: SDA low, SCL released high, then SDA released while SCL is HIGH.
      task gen_stop;
         begin
            @(negedge clk); m_scl = 1'b0; m_sda = 1'b0; phase;
            @(negedge clk); m_scl = 1'b1; phase;
            @(negedge clk); m_sda = 1'b1;               // the STOP edge
            phase;
         end
      endtask

      // One ordinary data bit: SDA is changed only while SCL is LOW, then clocked.
      task gen_bit (input b);
         begin
            @(negedge clk); m_scl = 1'b0; phase;
            @(negedge clk); m_sda = b;    phase;        // changed in the LOW phase
            @(negedge clk); m_scl = 1'b1; phase;
            @(negedge clk); m_scl = 1'b0; phase;
         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

      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

      initial begin
         $display("=== i2c_slave_framing: an SDA edge qualified by an SCL level ===");

         // ----------------------------------------------------------------
         // T1. RESET ASSUMES NO TRANSFER. A slave that came up believing one was open
         //     would try to read the middle of somebody else's transaction as an address.
         // ----------------------------------------------------------------
         do_reset;
         $display("T1  a slave out of reset assumes the bus is idle");
         ck_bit("T1 no transfer open", bus_active, 1'b0);
         ck_int("T1 no framing seen", n_start_obs + n_stop_obs, 0);

         // ----------------------------------------------------------------
         // T2. A QUIET BUS PRODUCES NOTHING. The false-positive test.
         // ----------------------------------------------------------------
         for (k = 0; k < 6; k = k + 1) phase;
         $display("T2  a quiet bus produces no framing");
         ck_int("T2 still nothing", n_start_obs + n_stop_obs, 0);
         ck_bit("T2 still idle", bus_active, 1'b0);

         // ----------------------------------------------------------------
         // T3. A START. One pulse, one cycle wide, and the bus becomes active.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         $display("T3  a START is one pulse, and it opens the transfer");
         ck_int("T3 exactly one START", n_start_obs, 1);
         ck_int("T3 and it was not a repeated one", n_restart_obs, 0);
         ck_bit("T3 the bus is now active", bus_active, 1'b1);
         ck_int("T3 the pulse was one cycle", wide, 0);
         ck_int("T3 the counter agrees", n_sta, 1);

         // ----------------------------------------------------------------
         // T4. THE CENTRAL NEGATIVE TEST. An SDA transition while SCL is LOW is ordinary
         //     data preparation and is NOT framing. A detector that ignored the SCL level
         //     would report a START on every data bit that happens to be a zero -- which
         //     is most of them -- and the slave would restart its address phase mid-byte,
         //     forever.
         // ----------------------------------------------------------------
         n_start_obs = 0; n_stop_obs = 0; n_restart_obs = 0;
         gen_bit(1'b0);          // SDA falls while SCL is low
         gen_bit(1'b1);          // and rises while SCL is low
         gen_bit(1'b0);
         $display("T4  SDA moving while SCL is LOW is data, not framing");
         ck_int("T4 no START was reported", n_start_obs, 0);
         ck_int("T4 no STOP was reported", n_stop_obs, 0);
         ck_bit("T4 and the transfer is still the same one", bus_active, 1'b1);

         // ----------------------------------------------------------------
         // T5. A STOP closes the transfer.
         // ----------------------------------------------------------------
         gen_stop;
         $display("T5  a STOP closes the transfer");
         ck_int("T5 exactly one STOP", n_stop_obs, 1);
         ck_bit("T5 the bus is idle again", bus_active, 1'b0);
         ck_int("T5 the counter agrees", n_sto, 1);

         // ----------------------------------------------------------------
         // T6. A REPEATED START, and the distinction that is NOT on the wire. The edge is
         //     identical to a first START; only `bus_active` separates them.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         gen_bit(1'b1); gen_bit(1'b0);
         n_start_obs = 0; n_restart_obs = 0;
         gen_restart;
         $display("T6  a repeated START is the same edge; only the state differs");
         ck_int("T6 a START was reported", n_start_obs, 1);
         ck_int("T6 and it was flagged as a REPEATED start", n_restart_obs, 1);
         ck_bit("T6 the transfer is still open", bus_active, 1'b1);
         ck_int("T6 the restart counter moved", n_rs, 1);

         // ----------------------------------------------------------------
         // T7. AND THE SDA RELEASE THAT PRECEDES A REPEATED START IS NOT A STOP. This is
         //     the trap: a repeated START begins by lifting SDA, and if that lift were
         //     classified as framing the slave would see STOP-then-START and end the
         //     transaction -- losing exactly the state a repeated START exists to keep.
         // ----------------------------------------------------------------
         ck_int("T7 no STOP was reported during the repeated START", n_stop_obs, 0);

         // ----------------------------------------------------------------
         // T8. A FIRST START AFTER A STOP counts as a first START again, not a repeated
         //     one. The classification is stateful, so it must clear.
         // ----------------------------------------------------------------
         gen_stop;
         n_start_obs = 0; n_restart_obs = 0;
         gen_start;
         $display("T8  after a STOP the next START is a first START again");
         ck_int("T8 one START", n_start_obs, 1);
         ck_int("T8 and NOT a repeated one", n_restart_obs, 0);

         // ----------------------------------------------------------------
         // T9. MID-BYTE FRAMING IS REPORTED, not absorbed. A slave that silently swallows
         //     a STOP arriving part-way through a byte cannot be debugged from outside,
         //     and Chapter 18.11 needs the report to decide a policy.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         @(negedge clk); mid_byte = 1'b1;     // the bit counter says a byte is in progress
         n_mid_obs = 0;
         gen_stop;
         $display("T9  framing arriving mid-byte is reported rather than absorbed");
         ck_int("T9 the mid-byte report fired", n_mid_obs, 1);
         @(negedge clk); mid_byte = 1'b0;

         // ----------------------------------------------------------------
         // T9b. AND THE OTHER HALF: mid-byte framing on a repeated START. T9 tests the STOP
         //      path only, so the START path's report was untested -- and the two are
         //      separate branches. A repeated START arriving part-way through a byte is the
         //      more likely of the two in practice, because a master that aborts a transfer
         //      usually retries with a restart rather than a STOP.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         @(negedge clk); mid_byte = 1'b1;
         n_mid_obs = 0;
         gen_restart;
         $display("T9b a repeated START mid-byte is reported too, not only a STOP");
         ck_int("T9b the mid-byte report fired on the restart", n_mid_obs, 1);
         @(negedge clk); mid_byte = 1'b0;

         // ----------------------------------------------------------------
         // T10. AND IT IS NOT REPORTED WHEN NO BYTE IS IN PROGRESS. The false-positive
         //      half: an ordinary STOP between bytes is completely normal.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         n_mid_obs = 0;
         gen_stop;
         $display("T10 an ordinary STOP between bytes is not a mid-byte report");
         ck_int("T10 no mid-byte report", n_mid_obs, 0);

         // ----------------------------------------------------------------
         // T11. MANY TRANSFERS, and nothing accumulates. Three complete frames.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 3; k = k + 1) begin
            gen_start;
            gen_bit(1'b1); gen_bit(1'b0); gen_bit(1'b1);
            gen_stop;
         end
         $display("T11 three complete frames, counted exactly");
         ck_int("T11 three STARTs", n_start_obs, 3);
         ck_int("T11 three STOPs", n_stop_obs, 3);
         ck_int("T11 no repeated STARTs", n_restart_obs, 0);
         ck_bit("T11 and the bus is left idle", bus_active, 1'b0);
         ck_int("T11 no pulse was ever wide", wide, 0);

         // ----------------------------------------------------------------
         // T12. RESET MID-TRANSFER RETURNS TO IDLE. Whatever the bus is doing, a reset
         //      slave has no transfer of its own -- and must not resume one it cannot
         //      have seen the beginning of.
         // ----------------------------------------------------------------
         do_reset;
         gen_start;
         ck_bit("T12 a transfer is open before reset", bus_active, 1'b1);
         @(negedge clk); rst_n = 1'b0; step; step;
         @(negedge clk); rst_n = 1'b1; phase;
         $display("T12 reset mid-transfer returns to idle and does not resume");
         ck_bit("T12 no transfer open after reset", bus_active, 1'b0);
         ck_int("T12 counters cleared", n_sta + n_rs + n_sto, 0);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_slave_framing_tb.vhd — the same tests in VHDL
   -- -----------------------------------------------------------------------------
   -- i2c_slave_framing_tb.vhd
   -- Independent oracle for i2c_slave_framing, behind the real front end. Behavioural twin
   -- of the SystemVerilog and Verilog benches: twelve tests, the same observers, the same
   -- finish time.
   --
   -- The bench is a hand-written CONTROLLER: SDA changes only while SCL is LOW for data and
   -- only while SCL is HIGH for framing. A bench that drove both the same way could not
   -- distinguish a detector that checks the SCL level from one that does not.
   -- -----------------------------------------------------------------------------
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_slave_framing_tb is
   end entity i2c_slave_framing_tb;

   architecture sim of i2c_slave_framing_tb is

      constant HALF : positive := 8;

      signal clk   : std_logic := '0';
      signal rst_n : std_logic := '0';
      signal m_scl : std_logic := '1';
      signal m_sda : std_logic := '1';
      signal mid_byte : std_logic := '0';

      signal scl_q, sda_q : std_logic;
      signal scl_rise, scl_fall, sda_rise, sda_fall : std_logic;
      signal start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte : std_logic;
      signal n_sta, n_rs, n_sto : unsigned(15 downto 0);

      signal halt : boolean := false;

      -- Observers, as SIGNALS so they update on the next edge exactly as the SystemVerilog
      -- registers do. Variables would advance immediately and lead the other two languages.
      signal n_start_obs, n_restart_obs, n_stop_obs, n_mid_obs : integer := 0;
      signal wide : integer := 0;
      signal clr  : boolean := false;

   begin

      u_sync : entity work.i2c_slave_sync
         generic map (SYNC_DEPTH => 2)
         port map (clk => clk, rst_n => rst_n, scl_pin => m_scl, sda_pin => m_sda,
            scl_q => scl_q, sda_q => sda_q,
            scl_rise => scl_rise, scl_fall => scl_fall,
            sda_rise => sda_rise, sda_fall => sda_fall);

      dut : entity work.i2c_slave_framing
         generic map (CNT_W => 16)
         port map (clk => clk, rst_n => rst_n, scl_q => scl_q,
            sda_rise => sda_rise, sda_fall => sda_fall,
            start_pulse => start_pulse, restart_pulse => restart_pulse,
            stop_pulse => stop_pulse, bus_active => bus_active,
            mid_byte => mid_byte, framing_midbyte => framing_midbyte,
            n_starts => n_sta, n_restarts => n_rs, n_stops => n_sto);

      clkgen : process
      begin
         while not halt loop
            clk <= '0'; wait for 5 ns;
            clk <= '1'; wait for 5 ns;
         end loop;
         wait;
      end process;

      obs : process (clk, clr)
         variable sp_d, rp_d, tp_d : std_logic := '0';
      begin
         if clr then
            n_start_obs <= 0; n_restart_obs <= 0; n_stop_obs <= 0; n_mid_obs <= 0; wide <= 0;
         elsif rising_edge(clk) then
            if rst_n = '1' then
               if start_pulse = '1'     then n_start_obs   <= n_start_obs + 1;   end if;
               if restart_pulse = '1'   then n_restart_obs <= n_restart_obs + 1; end if;
               if stop_pulse = '1'      then n_stop_obs    <= n_stop_obs + 1;    end if;
               if framing_midbyte = '1' then n_mid_obs     <= n_mid_obs + 1;     end if;
               if (start_pulse = '1' and sp_d = '1') or (restart_pulse = '1' and rp_d = '1')
                  or (stop_pulse = '1' and tp_d = '1') then
                  wide <= wide + 1;
               end if;
            end if;
            sp_d := start_pulse; rp_d := restart_pulse; tp_d := stop_pulse;
         end if;
      end process;

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

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

         procedure phase is
         begin
            for i in 1 to HALF loop step; end loop;
         end procedure;

         procedure do_reset is
         begin
            wait until falling_edge(clk);
            rst_n <= '0'; m_scl <= '1'; m_sda <= '1'; mid_byte <= '0';
            clr <= true; wait for 1 ns; clr <= false;
            step; step;
            wait until falling_edge(clk); rst_n <= '1';
            phase;
         end procedure;

         -- A START: SDA falls while SCL is HIGH, then SCL is pulled low.
         procedure gen_start is
         begin
            wait until falling_edge(clk); m_sda <= '1'; m_scl <= '1'; phase;
            wait until falling_edge(clk); m_sda <= '0'; phase;
            wait until falling_edge(clk); m_scl <= '0'; phase;
         end procedure;

         -- A repeated START: lift SDA while SCL is LOW (not framing), release SCL, then
         -- drop SDA while SCL is HIGH.
         procedure gen_restart is
         begin
            wait until falling_edge(clk); m_scl <= '0'; m_sda <= '1'; phase;
            wait until falling_edge(clk); m_scl <= '1'; phase;
            wait until falling_edge(clk); m_sda <= '0'; phase;
            wait until falling_edge(clk); m_scl <= '0'; phase;
         end procedure;

         -- A STOP: SDA low, SCL released, then SDA released while SCL is HIGH.
         procedure gen_stop is
         begin
            wait until falling_edge(clk); m_scl <= '0'; m_sda <= '0'; phase;
            wait until falling_edge(clk); m_scl <= '1'; phase;
            wait until falling_edge(clk); m_sda <= '1'; phase;
         end procedure;

         -- One data bit: SDA changed only while SCL is LOW, then clocked.
         procedure gen_bit (b : std_logic) is
         begin
            wait until falling_edge(clk); m_scl <= '0'; phase;
            wait until falling_edge(clk); m_sda <= b;   phase;
            wait until falling_edge(clk); m_scl <= '1'; phase;
            wait until falling_edge(clk); m_scl <= '0'; phase;
         end procedure;

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

         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;

      begin
         report "=== i2c_slave_framing: an SDA edge qualified by an SCL level ===" severity note;

         -- T1. Reset assumes no transfer.
         do_reset;
         report "T1  a slave out of reset assumes the bus is idle" severity note;
         ck_bit("T1 no transfer open", bus_active, '0');
         ck_int("T1 no framing seen", n_start_obs + n_stop_obs, 0);

         -- T2. A quiet bus produces nothing.
         for k in 0 to 5 loop phase; end loop;
         report "T2  a quiet bus produces no framing" severity note;
         ck_int("T2 still nothing", n_start_obs + n_stop_obs, 0);
         ck_bit("T2 still idle", bus_active, '0');

         -- T3. A START.
         do_reset;
         gen_start;
         report "T3  a START is one pulse, and it opens the transfer" severity note;
         ck_int("T3 exactly one START", n_start_obs, 1);
         ck_int("T3 and it was not a repeated one", n_restart_obs, 0);
         ck_bit("T3 the bus is now active", bus_active, '1');
         ck_int("T3 the pulse was one cycle", wide, 0);
         ck_int("T3 the counter agrees", to_integer(n_sta), 1);

         -- T4. THE CENTRAL NEGATIVE TEST: SDA moving while SCL is LOW is data.
         clr <= true; wait for 1 ns; clr <= false;
         gen_bit('0');
         gen_bit('1');
         gen_bit('0');
         report "T4  SDA moving while SCL is LOW is data, not framing" severity note;
         ck_int("T4 no START was reported", n_start_obs, 0);
         ck_int("T4 no STOP was reported", n_stop_obs, 0);
         ck_bit("T4 and the transfer is still the same one", bus_active, '1');

         -- T5. A STOP closes the transfer.
         gen_stop;
         report "T5  a STOP closes the transfer" severity note;
         ck_int("T5 exactly one STOP", n_stop_obs, 1);
         ck_bit("T5 the bus is idle again", bus_active, '0');
         ck_int("T5 the counter agrees", to_integer(n_sto), 1);

         -- T6. A repeated START: the same edge, distinguished only by state.
         do_reset;
         gen_start;
         gen_bit('1'); gen_bit('0');
         clr <= true; wait for 1 ns; clr <= false;
         gen_restart;
         report "T6  a repeated START is the same edge; only the state differs" severity note;
         ck_int("T6 a START was reported", n_start_obs, 1);
         ck_int("T6 and it was flagged as a REPEATED start", n_restart_obs, 1);
         ck_bit("T6 the transfer is still open", bus_active, '1');
         ck_int("T6 the restart counter moved", to_integer(n_rs), 1);

         -- T7. And the SDA lift that precedes a repeated START is NOT a STOP.
         ck_int("T7 no STOP was reported during the repeated START", n_stop_obs, 0);

         -- T8. After a STOP the next START is a first START again.
         gen_stop;
         clr <= true; wait for 1 ns; clr <= false;
         gen_start;
         report "T8  after a STOP the next START is a first START again" severity note;
         ck_int("T8 one START", n_start_obs, 1);
         ck_int("T8 and NOT a repeated one", n_restart_obs, 0);

         -- T9. Mid-byte framing is reported, not absorbed.
         do_reset;
         gen_start;
         wait until falling_edge(clk); mid_byte <= '1';
         clr <= true; wait for 1 ns; clr <= false;
         gen_stop;
         report "T9  framing arriving mid-byte is reported rather than absorbed" severity note;
         ck_int("T9 the mid-byte report fired", n_mid_obs, 1);
         wait until falling_edge(clk); mid_byte <= '0';

         -- T9b. AND THE OTHER HALF: mid-byte framing on a repeated START. T9 tests the STOP
         --      path only, so the START path's report was untested -- and they are separate
         --      branches. A repeated START mid-byte is the more likely of the two, because a
         --      master that aborts usually retries with a restart rather than a STOP.
         do_reset;
         gen_start;
         wait until falling_edge(clk); mid_byte <= '1';
         clr <= true; wait for 1 ns; clr <= false;
         gen_restart;
         report "T9b a repeated START mid-byte is reported too, not only a STOP" severity note;
         ck_int("T9b the mid-byte report fired on the restart", n_mid_obs, 1);
         wait until falling_edge(clk); mid_byte <= '0';

         -- T10. And not reported when no byte is in progress.
         do_reset;
         gen_start;
         clr <= true; wait for 1 ns; clr <= false;
         gen_stop;
         report "T10 an ordinary STOP between bytes is not a mid-byte report" severity note;
         ck_int("T10 no mid-byte report", n_mid_obs, 0);

         -- T11. Three complete frames, nothing accumulates.
         do_reset;
         for k in 0 to 2 loop
            gen_start;
            gen_bit('1'); gen_bit('0'); gen_bit('1');
            gen_stop;
         end loop;
         report "T11 three complete frames, counted exactly" severity note;
         ck_int("T11 three STARTs", n_start_obs, 3);
         ck_int("T11 three STOPs", n_stop_obs, 3);
         ck_int("T11 no repeated STARTs", n_restart_obs, 0);
         ck_bit("T11 and the bus is left idle", bus_active, '0');
         ck_int("T11 no pulse was ever wide", wide, 0);

         -- T12. Reset mid-transfer returns to idle and does not resume.
         do_reset;
         gen_start;
         ck_bit("T12 a transfer is open before reset", bus_active, '1');
         wait until falling_edge(clk); rst_n <= '0'; step; step;
         wait until falling_edge(clk); rst_n <= '1'; phase;
         report "T12 reset mid-transfer returns to idle and does not resume" severity note;
         ck_bit("T12 no transfer open after reset", bus_active, '0');
         ck_int("T12 counters cleared", to_integer(n_sta) + to_integer(n_rs)
                + to_integer(n_sto), 0);

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

   end architecture sim;

7b. Execution

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_slave_framingPASS 13/13PASS 13/13PASS 13/1311950 ns, all three

8. Mutation Testing

Eleven defects, one per claim.

#Injected defectExpected detectionResult
M1remove the SCL qualification — any SDA fall is a STARTT4KILLED (7)
M2reverse the START edgeT3, T11KILLED (17)
M3swap START and STOPT3, T5KILLED (15)
M4remove the SCL qualification from STOPT4, T7KILLED (9)
M5a repeated START is never distinguishedT6KILLED (3)
M6every START is reported as repeatedT3, T8KILLED (6)
M7a STOP does not clear bus_activeT5, T8KILLED (5)
M8reset assumes a transfer is openT1KILLED (8)
M9the pulses become levels, never clearedT3, T11KILLED (12)
M10mid-byte framing on a START is absorbedT9b newKILLED (2)
M11mid-byte framing on a STOP is absorbedT9KILLED (2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
valid mutants: 11   killed: 11   survived: 0   equivalent: 0   invalid: 0
restored: PASS

M10 survived first, and it was the same lesson as 18.2

T9 tested mid-byte framing on a STOP. The START path's report is a separate branch, and nothing exercised it — so a mutation that silenced it passed.

T9b adds the missing half, and it is arguably the more important one: a master that aborts a transfer usually retries with a repeated START rather than a STOP, so mid-byte framing in practice arrives on the branch that was untested.

9. Verification Connection — What the Monitor and the Target Must Agree On

Azvya Education Pvt. Ltd.VLSI Mentor
slave_framing_sva.sv — the invariants, and the classification a monitor shares
   // 1. FRAMING REQUIRES THE LEVEL. A start pulse may only be produced when SCL was
   //    high -- this is mutation M1 as an assertion, and it is the property every other
   //    block in the target depends on.
   property p_start_needs_scl_high;
      @(posedge clk) disable iff (!rst_n) start_pulse |-> $past(scl_q);
   endproperty
   a_qual: assert property (p_start_needs_scl_high);

   // 2. THE TWO EVENTS ARE MUTUALLY EXCLUSIVE. One SDA edge cannot be both, because an
   //    edge has one direction.
   property p_not_both;
      @(posedge clk) disable iff (!rst_n) !(start_pulse && stop_pulse);
   endproperty
   a_excl: assert property (p_not_both);

   // 3. A RESTART IMPLIES A START. `restart_pulse` is a qualification of `start_pulse`,
   //    not an independent event -- so a consumer can gate on either without needing to
   //    know the other's timing.
   property p_restart_implies_start;
      @(posedge clk) disable iff (!rst_n) restart_pulse |-> start_pulse;
   endproperty
   a_impl: assert property (p_restart_implies_start);

   // 4. bus_active TRACKS THE FRAMING. It rises with a START and falls with a STOP, and
   //    nothing else moves it.
   property p_active_tracks;
      @(posedge clk) disable iff (!rst_n)
         $changed(bus_active) |-> (start_pulse || stop_pulse);
   endproperty
   a_track: assert property (p_active_tracks);

   // WHAT A PIN-LEVEL MONITOR SHARES WITH THIS BLOCK, and why that is a hazard.
   //
   // A protocol monitor classifies framing with the SAME two conditions. So if the
   // monitor and the DUT are both wrong in the same way -- both forgetting the SCL
   // qualifier, say -- they agree perfectly and the scoreboard is silent.
   //
   // Which means the monitor must NOT be written by copying the DUT's expression. The
   // benches in this module avoid the trap differently: the CONTROLLER drives framing and
   // data with deliberately different timing, so a DUT that conflates them disagrees with
   // the stimulus rather than with a monitor that might share its mistake.
   //
   // COVERAGE. Three bins, and the third is the one a clean regression never reaches:
   //
   //   cover: a first START             -- every transfer
   //   cover: a repeated START          -- combined transfers only
   //   cover: framing arriving MID-BYTE -- an aborted transfer only

10. FPGA and ASIC Implications

On an FPGA this block is pure logic with no pin contact, so it has no I/O considerations of its own — which is the payoff of 18.2 owning the boundary. What it does inherit is the two-cycle latency: a START is detected two cycles after the edge, and nothing in the protocol requires a target to respond to one faster than that. The master's next act is to clock, which takes a whole phase.

The one implementation note worth making is about bus_active. It is the gate for every later block, so it fans out widely — and because it changes only on framing events, it is a slow signal with a lot of load. That is a placement consideration rather than a timing risk, and Module 19.6 owns the constraint side.

On an ASIC, the relevant point is that framing detection must survive the pad's input filter. A filter that suppresses Table 10's tSP spike also slightly delays both lines — and because framing depends on the relative timing of an SDA edge and an SCL level, a filter with different delays on the two lines could in principle move an edge across a level change. In practice both pads are the same cell with the same filter, so the skew is negligible; the reason to state it is that it is the one place where a per-line difference would matter, and it is worth knowing before somebody filters one line and not the other.

Reset leaves bus_active low, which is the only safe value: a target that came up believing a transfer was open would try to read the middle of somebody else's transaction as an address byte.

11. Debugging — The Target That Would Not Stay Addressed

Symptom

A newly written target is addressed correctly and acknowledges its address. It then fails to receive the first data byte: the master sees the address acknowledged and every subsequent byte NACKed. A logic analyser shows a clean, conforming write transaction from the master -- correct START, correct address, correct acknowledge, correct data bytes -- and the target simply stops responding after the address.

Root Cause

The SCL qualifier was missing from the START condition, so every ordinary data-preparation fall was reported as a START. Section 3.1.1 defines a START as an SDA fall while SCL is HIGH precisely because an SDA fall while SCL is LOW is the normal way a zero bit is presented -- it happens on most bits of most bytes. The address phase therefore worked by luck and the first data byte containing a zero destroyed the transaction. Nothing was wrong with the receive path, the acknowledge generator or the address comparator; all three were driven by an event that had been manufactured below them.

Fix
Qualify the edge with the level: start_now = sda_fall AND scl_q, which is mutation M1 in reverse. Then note what made this survivable for so long -- the address byte happened not to contain a qualifying transition, so the block appeared to work for the one byte that mattered most. The regression that catches it is T4, and its shape is worth copying: drive ordinary data bits deliberately, with SDA changing only in the LOW phase, and assert that ZERO framing events are reported. A bench that only ever sends framing cannot fail a detector that thinks everything is framing.

Three generalisations.

The defect was upstream of three correct blocks. The address block, the receive path and the acknowledge generator all behaved exactly as specified on an event that never happened. A single-entry-point observation pipeline localises the risk and globalises the blast radius.

It worked for the address byte by luck. 0xA0's only SDA fall while SCL was low fell before acquisition was armed. The first byte containing a zero in a different position broke it — so the failure looked like a data-path bug because the address path appeared fine.

The test that catches it asserts a zero. "Drive ordinary data and assert no framing" is a negative test, and negative tests are the easiest to omit from a plan that enumerates behaviours rather than non-behaviours.

12. Common Misconceptions

"A START is an SDA falling edge." It is an SDA falling edge while SCL is high. Without the qualifier it fires on most data zeros. §1.

"The SCL check is a refinement." It is the entire distinction between framing and data — the two most different things on the bus share one edge shape. §1.

"A repeated START is a different waveform." It is electrically identical to a first START. Only the target's own bus_active separates them. §3.

"So the target can ignore the difference." Then it cannot handle a combined transfer: a repeated START must restart the frame while leaving application state alone, and a first START must not. §3 and 18.10.

"A repeated START is just a START, so nothing precedes it." It begins by lifting SDA while SCL is low. If that lift were read as framing the target would see STOP-then-START and discard the state the restart exists to keep. §4.

"A framing detector should also track bit position." Then it needs resetting by its own output, and the two jobs have different reset conditions. §5.

"Mid-byte framing should be silently absorbed." A target that absorbs it cannot be debugged from outside the chip. Report it; let 18.11 decide the policy. §5.

"If the address byte decodes, framing detection works." It may have worked by luck — whether the address contained a qualifying transition before acquisition armed. §11.

"Testing framing means sending framing." A detector that thinks everything is framing passes every such test. The decisive test drives ordinary data and asserts zero events. §11.

"Writing the monitor from the DUT's expression saves effort." Then both can be wrong the same way and the scoreboard stays silent. §9.

13. Reason It Through

SDA falls while SCL is low. Why must this not be classified as a START?

Because that is how a zero bit is presented — §3.1.2 permits SDA to change only while SCL is low, so it happens on most bits of most bytes. Classifying it as framing restarts the address phase several times per byte. §1.

What distinguishes a repeated START from a first START, and where does that information live?

Only whether a transfer was already open. It lives in the target's own bus_active state, not on the wire — the two edges are electrically identical. §3.

A repeated START sequence lifts SDA before dropping it. Why is that lift not a STOP?

Because it happens while SCL is LOW, and a STOP requires SDA to rise while SCL is HIGH. The same qualifier that protects against data zeros protects against this. §4.

Why does this block report mid-byte framing without acting on it?

Because the policy is a transaction-level decision (18.11), while the observation is a framing-level fact. Reporting a condition it does nothing about is what makes the behaviour debuggable from outside the chip. §5.

A target acknowledges its address and then NACKs every data byte. The analyser shows a clean transaction. Where do you look?

At whether the framing detector is emitting spurious STARTs during the data byte — which happens if the SCL qualifier is missing, because data zeros then look like framing and the address phase re-arms mid-byte. §11.

Why did that defect survive the address byte?

Because 0xA0's only SDA fall while SCL was low occurred before acquisition was armed. The luck was in the bit pattern, not the logic. §11.

Three of this module's mutation survivors so far share a shape. What is it, and what does it imply for a test plan?

Each was the untested half of a symmetric property — one direction of independence, the pulse but not the level, the STOP branch but not the START branch. It implies enumerating branches, not behaviours. §8.

14. Understanding Check

15. Summary

Detection is two AND gates: an SDA edge qualified by the SCL level. Both lines are needed, which is why 18.2 exports levels as well as edges.

Dropping the SCL qualifier makes data zeros look like STARTs, several times per byte — and the address phase then re-arms mid-byte, forever.

A slave's detector is not the master's framer reversed. The master chose the instants; the target classifies an edge that has already happened and resets the right state.

A first START and a repeated START are the identical electrical event. Only the target's own bus_active separates them, so a target that does not track it cannot handle a combined transfer.

A repeated START begins by lifting SDA while SCL is low, and the SCL qualifier is what stops that lift being read as a STOP — the same qualifier doing a second job.

No bit counter lives here, because it would need resetting by this block's own output, and the two jobs have different reset conditions.

Mid-byte framing is reported and not acted on, because the policy is a transaction-level decision and an absorbed condition cannot be debugged from outside.

Eleven mutants, eleven killed — after M10 survived because only the STOP branch of the mid-byte report was tested.

That is the third survivor in this module with one shape: the untested half of a symmetric property. Enumerate branches, not behaviours.

And the decisive test asserts a zero. Drive ordinary data, assert no framing — because a detector that thinks everything is framing passes every positive test.

16. What Comes Next

The target now knows when a transfer begins and ends. Chapter 18.4 makes it find out whether the transfer is its.

That chapter's argument is a separation: receiving eight bits, deciding whether they match, and deciding whether to answer are three jobs that fail separately, and a design with one state called "address" cannot tell you which of them broke. It is also where the direction bit is latched — the one piece of the address byte that governs everything afterwards, long after the byte itself has left the wire.

Continue learning