Skip to content
VLSI Mentor

I²C · Module 17

The Bit Engine — Driving and Sampling One Bit

One bit, and deliberately no more. Shows how making the drive point the only place SDA is written turns the data-valid rule from a property to be checked into one that cannot be violated, why transmit and receive are the same logic differing by one bit, and why an engine needs two different kinds of release because two normative rules are in tension.

Everything so far has been infrastructure. 17.3 produces legal phases and announces two instants; 17.4 owns the inversion; 17.5 produces the edges that open and close a transfer. None of them moves data.

This chapter moves exactly one bit. Not a byte — one bit — and the restraint is the point: everything that makes a byte interesting belongs to 17.7, and mixing the two is how a bit engine acquires a bit counter and stops being testable.

1. This Block Contains No Timing

That is what Chapter 17.3 was buying when it exposed the strobes as outputs instead of keeping them internal. Every timing number lives in one block's parameters, and every block downstream is timing-free.

2. The Data-Valid Rule Becomes an Invariant

Here is the structural move. This block updates its SDA output only at drive_point, which is inside the low phase. So SDA cannot change while SCL is high — not because the engine checks, but because there is no path by which it could.

Compare the alternative. A design that updated SDA on some other event would have to prove the rule holds. Proving it requires knowing which phase the bus is in, which means duplicating the generator — and now two blocks own the phase, which is precisely the tangle Chapter 17.1 §2 warned about.

An invariant you cannot violate costs less than one you have to check. That is the whole argument for a single write point.

3. Transmit and Receive Are the Same Block

To receive, a master releases SDA — which is transmitting a one — and reads the line at the sample point.

So tx_en low is not a separate mode. It is transmitting ones and believing what comes back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tx_en = 1   ->  sda_bit = tx_bit      drive the bit we intend
tx_en = 0   ->  sda_bit = 1           release, and read the line

This matters beyond economy of logic. It is the same fact Chapter 17.4 §3b needed for arbitration: a receiving master has released the line on purpose, so a low readback is data rather than a lost contest. driving is the signal that carries that distinction outward, and §3.1.8's detector is gated by it.

3a. Why the sample is taken even while transmitting

The engine samples at sample_point whether or not the master is driving.

While receiving, the sampled value is the received bit. While transmitting, the sampled value is exactly what arbitration compares against. One sample point, two consumers — and an engine that skipped the sample while transmitting would leave Chapter 17.10 with nothing to compare.

4. Two Different Releases, Because Two Rules Are in Tension

This is the subtlest thing in the block, and it is a genuine conflict between two normative requirements.

RuleSaysImplies
§3.1.2SDA may change only while SCL is LOWdefer every release to the low phase
§3.1.8a loser turns off its driver "the moment there is a difference"release immediately, during the HIGH phase

They are reconciled by asking what actually reaches the wire.

An arbitration loser is being out-driven. Another device is holding SDA low, so this master releasing produces no edge at all — the line was low and stays low. Releasing immediately is therefore safe, and §3.1.8's "the moment" can be honoured literally.

An ordinary end-of-byte release does make SDA rise. And a rise while SCL is high is a STOP. Worse, this is not a hypothetical: at the end of a read byte the master has just driven a zero as its acknowledge, so releasing in the high phase is exactly when a spurious STOP would be manufactured.

5. One Bit, Both Directions

A transmitted zero, then a received bit — same strobes, same logic

10 cycles
Ten intervals covering two bit slots. In the first slot the drive point occurs in interval one while SCL is low, and the engine pulls SDA low to transmit a zero; SCL then goes high for intervals three and four and the sample point occurs in interval three, reading the zero back. In the second slot the drive point occurs in interval six while SCL is low, and the engine releases SDA because it is receiving; SCL goes high for intervals eight and nine and the sample point in interval eight reads the level the target is driving, which is low.transmit a zerotransmit a zeroreceive a bitreceive a bitdrive point — SDA written here onlydrive point — SDA writtenhere onlysample point — inside SCL highsample point — inside SCLhighreceiving: the drive point releasesreceiving: the drive pointreleasessample: the target's bitsample: the target's bitscl_busdrive_pointsample_pointsda_bustx_ent0t1t2t3t4t5t6t7t8t9
Figure 1 — two bit slots: a transmitted zero, then a received bit. The drive point sits inside the low phase and is the only place SDA is written; the sample point sits inside the high phase. Note that the receive slot's drive point still acts — it releases the line, which is how a one is transmitted. Conceptual figure at phase resolution.

The two slots are the same logic. In the first the drive point writes a zero; in the second it writes a one, which is a release. Nothing else differs, which is §3 made visual.

6. The Bit Engine, in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_engine.sv — one bit: drive at one instant, sample at the other
   // -----------------------------------------------------------------------------
   // i2c_bit_engine.sv
   // One bit: place it before the rising edge, sample the line in the high phase, advance.
   //
   // THIS BLOCK CONTAINS NO TIMING. That is its design, not an omission. Chapter 17.3's
   // generator exposes two strobes -- `drive_point` inside the LOW phase and
   // `sample_point` inside the HIGH phase -- and this block does exactly one thing at
   // each. Every Table 10 number lives in the generator's parameters, so changing from
   // Fast-mode to Fast-mode Plus changes no line of this file.
   //
   // THE DATA-VALID RULE IS AN INVARIANT HERE, NOT A CHECK. §3.1.2: "The data on the SDA
   // line must be stable during the HIGH period of the clock." This block updates its SDA
   // output ONLY at `drive_point`, which is inside the low phase. So SDA cannot change
   // while SCL is high, by construction -- there is no path by which it could. A design
   // that updated SDA on any other event would have to prove the rule instead of
   // structurally guaranteeing it, and proving it requires knowing the phase, which means
   // duplicating the generator.
   //
   // TRANSMIT AND RECEIVE ARE THE SAME BLOCK, differing by one bit. To receive, a master
   // releases SDA -- which is transmitting a one -- and reads the line at the sample
   // point. So `tx_en` low is not a separate mode; it is transmitting ones and believing
   // what comes back. That is worth stating because it is also why a receiving master is
   // not arbitrating: it is releasing the line on purpose, so a low readback is data
   // rather than a lost contest, and §3.1.8's detector must be gated by this signal.
   //
   // WHAT `rx_bit` IS FOR WHILE TRANSMITTING. The sample is taken whether or not the
   // master is driving, because the sampled value while transmitting is exactly what
   // arbitration compares against. One sample point, two consumers.
   // -----------------------------------------------------------------------------

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

      // From the SCL generator. This block has no notion of how long a phase is.
      input  logic            drive_point,
      input  logic            sample_point,

      // What to do with this bit.
      input  logic            active,       // run; low parks the engine
      input  logic            tx_en,        // 1 = drive tx_bit; 0 = release and receive
      input  logic            tx_bit,

      // ABORT: release SDA THIS CYCLE, whatever the clock is doing.
      //
      // This is a separate control from `active` going low, and the difference is a
      // protocol requirement rather than a convenience. Two rules are in tension:
      //
      //   §3.1.2  SDA may only change while SCL is LOW.
      //   §3.1.8  an arbitration loser "turns off its SDA output driver" the MOMENT there
      //           is a difference -- which is during the HIGH phase, since that is when
      //           the comparison is made.
      //
      // They are reconciled by what actually happens on the wire. An arbitration loser is
      // being out-driven: the winner is holding SDA LOW, so releasing produces no edge at
      // all and §3.1.2 is not engaged. An ordinary end-of-byte release is different --
      // the master is the only device driving, so letting go DOES make SDA rise, and if
      // SCL is still high that rise is a STOP condition (§3.1.1).
      //
      // So: `abort` releases immediately, and `active` going low defers the release until
      // SCL is low. Conflating them emits a spurious STOP at the end of every read byte
      // the master acknowledged, because the acknowledge is a zero the master was driving.
      input  logic            abort,

      // The bus.
      input  logic            sda_in,
      input  logic            scl_high,     // SCL, read back. Only used to defer a release.

      // To the SDA owner arbiter.
      output logic            sda_req,
      output logic            sda_bit,

      output logic            rx_bit,
      output logic            rx_valid,     // one cycle, at the sample point
      output logic            bit_done,     // one cycle, at the sample point
      output logic            driving,       // this bit slot is ours to drive
      output logic [CNT_W-1:0] bits_driven,
      output logic [CNT_W-1:0] bits_sampled
   );

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            sda_req      <= 1'b0;
            sda_bit      <= 1'b1;
            rx_bit       <= 1'b0;
            rx_valid     <= 1'b0;
            bit_done     <= 1'b0;
            driving      <= 1'b0;
            bits_driven  <= {CNT_W{1'b0}};
            bits_sampled <= {CNT_W{1'b0}};
         end else begin
            rx_valid <= 1'b0;
            bit_done <= 1'b0;

            if (abort) begin
               // Immediate. Safe because the only reason to abort is that somebody else is
               // holding the line low, so no edge results.
               sda_req <= 1'b0;
               sda_bit <= 1'b1;
               driving <= 1'b0;
            end else if (!active) begin
               // Parked, but not until SCL is LOW. Releasing while SCL is high would make
               // SDA rise in the high phase, which is a STOP -- and at the end of a read
               // byte the master has just driven a zero as its acknowledge, so that is
               // exactly when it would happen. Holding until SCL falls costs nothing: the
               // bus is between bit slots and the value is already the one that was
               // sampled.
               if (!scl_high) begin
                  sda_req <= 1'b0;
                  sda_bit <= 1'b1;
                  driving <= 1'b0;
               end
            end else begin
               // THE ONLY PLACE SDA IS UPDATED. Inside the low phase, tSU;DAT before the
               // rising edge, which is what makes §3.1.2 structural.
               if (drive_point) begin
                  sda_req <= 1'b1;            // we hold the line either way: a receiving
                                              // master transmits ones, which is releasing
                  sda_bit <= tx_en ? tx_bit : 1'b1;
                  driving <= tx_en;
                  if (tx_en) bits_driven <= bits_driven + 1'b1;
               end

               // THE ONLY PLACE SDA IS SAMPLED. Inside the high phase, where §3.1.2
               // guarantees the transmitter is holding it stable.
               if (sample_point) begin
                  rx_bit       <= sda_in;
                  rx_valid     <= 1'b1;
                  bit_done     <= 1'b1;
                  bits_sampled <= bits_sampled + 1'b1;
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_engine.v — the same design in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_bit_engine.sv
   // One bit: place it before the rising edge, sample the line in the high phase, advance.
   //
   // THIS BLOCK CONTAINS NO TIMING. That is its design, not an omission. Chapter 17.3's
   // generator exposes two strobes -- `drive_point` inside the LOW phase and
   // `sample_point` inside the HIGH phase -- and this block does exactly one thing at
   // each. Every Table 10 number lives in the generator's parameters, so changing from
   // Fast-mode to Fast-mode Plus changes no line of this file.
   //
   // THE DATA-VALID RULE IS AN INVARIANT HERE, NOT A CHECK. §3.1.2: "The data on the SDA
   // line must be stable during the HIGH period of the clock." This block updates its SDA
   // output ONLY at `drive_point`, which is inside the low phase. So SDA cannot change
   // while SCL is high, by construction -- there is no path by which it could. A design
   // that updated SDA on any other event would have to prove the rule instead of
   // structurally guaranteeing it, and proving it requires knowing the phase, which means
   // duplicating the generator.
   //
   // TRANSMIT AND RECEIVE ARE THE SAME BLOCK, differing by one bit. To receive, a master
   // releases SDA -- which is transmitting a one -- and reads the line at the sample
   // point. So `tx_en` low is not a separate mode; it is transmitting ones and believing
   // what comes back. That is worth stating because it is also why a receiving master is
   // not arbitrating: it is releasing the line on purpose, so a low readback is data
   // rather than a lost contest, and §3.1.8's detector must be gated by this signal.
   //
   // WHAT `rx_bit` IS FOR WHILE TRANSMITTING. The sample is taken whether or not the
   // master is driving, because the sampled value while transmitting is exactly what
   // arbitration compares against. One sample point, two consumers.
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_bit_engine #(
      parameter CNT_W = 16
   ) (
      input  wire            clk,
      input  wire            rst_n,

      // From the SCL generator. This block has no notion of how long a phase is.
      input  wire            drive_point,
      input  wire            sample_point,

      // What to do with this bit.
      input  wire            active,       // run; low parks the engine
      input  wire            tx_en,        // 1 = drive tx_bit; 0 = release and receive
      input  wire            tx_bit,

      // ABORT: release SDA THIS CYCLE, whatever the clock is doing.
      //
      // This is a separate control from `active` going low, and the difference is a
      // protocol requirement rather than a convenience. Two rules are in tension:
      //
      //   §3.1.2  SDA may only change while SCL is LOW.
      //   §3.1.8  an arbitration loser "turns off its SDA output driver" the MOMENT there
      //           is a difference -- which is during the HIGH phase, since that is when
      //           the comparison is made.
      //
      // They are reconciled by what actually happens on the wire. An arbitration loser is
      // being out-driven: the winner is holding SDA LOW, so releasing produces no edge at
      // all and §3.1.2 is not engaged. An ordinary end-of-byte release is different --
      // the master is the only device driving, so letting go DOES make SDA rise, and if
      // SCL is still high that rise is a STOP condition (§3.1.1).
      //
      // So: `abort` releases immediately, and `active` going low defers the release until
      // SCL is low. Conflating them emits a spurious STOP at the end of every read byte
      // the master acknowledged, because the acknowledge is a zero the master was driving.
      input  wire            abort,

      // The bus.
      input  wire            sda_in,
      input  wire            scl_high,     // SCL, read back. Only used to defer a release.

      // To the SDA owner arbiter.
      output reg             sda_req,
      output reg             sda_bit,

      output reg             rx_bit,
      output reg             rx_valid,     // one cycle, at the sample point
      output reg             bit_done,     // one cycle, at the sample point
      output reg             driving,       // this bit slot is ours to drive
      output reg [CNT_W-1:0] bits_driven,
      output reg [CNT_W-1:0] bits_sampled
   );

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            sda_req      <= 1'b0;
            sda_bit      <= 1'b1;
            rx_bit       <= 1'b0;
            rx_valid     <= 1'b0;
            bit_done     <= 1'b0;
            driving      <= 1'b0;
            bits_driven  <= {CNT_W{1'b0}};
            bits_sampled <= {CNT_W{1'b0}};
         end else begin
            rx_valid <= 1'b0;
            bit_done <= 1'b0;

            if (abort) begin
               // Immediate. Safe because the only reason to abort is that somebody else is
               // holding the line low, so no edge results.
               sda_req <= 1'b0;
               sda_bit <= 1'b1;
               driving <= 1'b0;
            end else if (!active) begin
               // Parked, but not until SCL is LOW. Releasing while SCL is high would make
               // SDA rise in the high phase, which is a STOP -- and at the end of a read
               // byte the master has just driven a zero as its acknowledge, so that is
               // exactly when it would happen. Holding until SCL falls costs nothing: the
               // bus is between bit slots and the value is already the one that was
               // sampled.
               if (!scl_high) begin
                  sda_req <= 1'b0;
                  sda_bit <= 1'b1;
                  driving <= 1'b0;
               end
            end else begin
               // THE ONLY PLACE SDA IS UPDATED. Inside the low phase, tSU;DAT before the
               // rising edge, which is what makes §3.1.2 structural.
               if (drive_point) begin
                  sda_req <= 1'b1;            // we hold the line either way: a receiving
                                              // master transmits ones, which is releasing
                  sda_bit <= tx_en ? tx_bit : 1'b1;
                  driving <= tx_en;
                  if (tx_en) bits_driven <= bits_driven + 1'b1;
               end

               // THE ONLY PLACE SDA IS SAMPLED. Inside the high phase, where §3.1.2
               // guarantees the transmitter is holding it stable.
               if (sample_point) begin
                  rx_bit       <= sda_in;
                  rx_valid     <= 1'b1;
                  bit_done     <= 1'b1;
                  bits_sampled <= bits_sampled + 1'b1;
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_engine.vhd — the same design in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_bit_engine.vhd
   -- One bit: place it before the rising edge, sample the line in the high phase, advance.
   -- Behavioural twin of i2c_bit_engine.sv / .v.
   --
   -- THIS BLOCK CONTAINS NO TIMING. Chapter 17.3's generator exposes two strobes and this
   -- block does exactly one thing at each. Every Table 10 number lives in the generator's
   -- generics, so changing from Fast-mode to Fast-mode Plus changes no line of this file.
   --
   -- THE DATA-VALID RULE IS AN INVARIANT HERE, NOT A CHECK. §3.1.2: "The data on the SDA line
   -- must be stable during the HIGH period of the clock." This block updates SDA ONLY at
   -- `drive_point`, which is inside the low phase -- so SDA cannot change while SCL is high,
   -- by construction. There is no path by which it could.
   --
   -- TRANSMIT AND RECEIVE ARE THE SAME BLOCK, differing by one bit. To receive, a master
   -- releases SDA -- which IS transmitting a one -- and reads the line at the sample point. So
   -- `tx_en` low is not a separate mode; it is transmitting ones and believing what comes back.
   -- That is also why a receiving master is not arbitrating, and why `driving` is an output.
   --
   -- AND THERE ARE TWO DIFFERENT RELEASES, because two normative rules are in tension:
   --   §3.1.2  SDA may only change while SCL is LOW.
   --   §3.1.8  an arbitration loser "turns off its SDA output driver" the MOMENT it sees the
   --           difference -- which is during the HIGH phase, since that is when it is checked.
   -- They reconcile in what reaches the wire. A loser is being out-driven: the winner holds SDA
   -- LOW, so releasing makes no edge and §3.1.2 is not engaged. An ordinary end-of-byte release
   -- is different -- the master is the only device driving, so letting go DOES make SDA rise,
   -- and a rise while SCL is high is a STOP (§3.1.1). Conflating them emits a spurious STOP at
   -- the end of every read byte the master acknowledged.
   -- ---------------------------------------------------------------------------

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

   entity i2c_bit_engine is
      generic (
         CNT_W : integer := 16
      );
      port (
         clk   : in std_logic;
         rst_n : in std_logic;

         drive_point  : in std_logic;
         sample_point : in std_logic;

         active : in std_logic;
         tx_en  : in std_logic;
         tx_bit : in std_logic;
         abort  : in std_logic;    -- release SDA THIS cycle, whatever the clock is doing

         sda_in   : in std_logic;
         scl_high : in std_logic;  -- SCL read back. Only used to defer a release.

         sda_req : out std_logic;
         sda_bit : out std_logic;

         rx_bit       : out std_logic;
         rx_valid     : out std_logic;
         bit_done     : out std_logic;
         driving      : out std_logic;
         bits_driven  : out unsigned(CNT_W-1 downto 0);
         bits_sampled : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_bit_engine;

   architecture rtl of i2c_bit_engine is
      signal sreq, sbit, drv : std_logic := '0';
      signal n_drv, n_smp : unsigned(CNT_W-1 downto 0) := (others => '0');
   begin

      sda_req      <= sreq;
      sda_bit      <= sbit;
      driving      <= drv;
      bits_driven  <= n_drv;
      bits_sampled <= n_smp;

      process (clk, rst_n)
      begin
         if rst_n = '0' then
            sreq     <= '0';
            sbit     <= '1';
            rx_bit   <= '0';
            rx_valid <= '0';
            bit_done <= '0';
            drv      <= '0';
            n_drv    <= (others => '0');
            n_smp    <= (others => '0');
         elsif rising_edge(clk) then
            rx_valid <= '0';
            bit_done <= '0';

            if abort = '1' then
               -- Immediate. Safe because the only reason to abort is that somebody else is
               -- holding the line low, so no edge results.
               sreq <= '0';
               sbit <= '1';
               drv  <= '0';
            elsif active = '0' then
               -- Parked, but not until SCL is LOW. Releasing while SCL is high would make SDA
               -- rise in the high phase, which is a STOP -- and at the end of a read byte the
               -- master has just driven a zero as its acknowledge, so that is exactly when it
               -- would happen.
               if scl_high = '0' then
                  sreq <= '0';
                  sbit <= '1';
                  drv  <= '0';
               end if;
            else
               -- THE ONLY PLACE SDA IS UPDATED. Inside the low phase, tSU;DAT before the
               -- rising edge, which is what makes §3.1.2 structural.
               if drive_point = '1' then
                  sreq <= '1';   -- we hold the line either way: a receiving master transmits
                                 -- ones, which is releasing
                  if tx_en = '1' then sbit <= tx_bit; else sbit <= '1'; end if;
                  drv <= tx_en;
                  if tx_en = '1' then n_drv <= n_drv + 1; end if;
               end if;

               -- THE ONLY PLACE SDA IS SAMPLED. Inside the high phase, where §3.1.2 guarantees
               -- the transmitter is holding it stable. The sample is taken whether or not the
               -- master is driving, because the sampled value while transmitting is exactly
               -- what arbitration compares against: one sample point, two consumers.
               if sample_point = '1' then
                  rx_bit   <= sda_in;
                  rx_valid <= '1';
                  bit_done <= '1';
                  n_smp    <= n_smp + 1;
               end if;
            end if;
         end if;
      end process;

   end architecture rtl;

6a. The testbenches

Thirteen checks across twelve tests. Two design details of the bench are worth reading before the code.

next_bit waits on the sample counter, not on bit_done. This looks equivalent and is not: the pulse is still high when the previous call returned, so a second call with no intervening delay would see it and return immediately without a bit having happened. A tight loop of such calls then counts half the bits it asked for, and every test using it passes or fails on a number the bench invented.

So bit_done gets an independent observer instead. Waiting on a pulse and checking a pulse are different jobs. A separate process counts the pulses, catches the pulse being high two cycles running, and asserts one pulse per completed bit — which matters because bit_done is what Chapter 17.7's byte engine advances on. An engine whose bits are all correct and whose bit_done never fires would stall the layer above while passing every other test here. That check was added after mutation M8 survived; see §7.

#TestProperty
T1parked means releasednot holding the last bit
T2transmit a one — the engine releases SDAa receiver sampling reads high
T3transmit a zero — the engine pulls SDA down
T4the invariant — SDA never changed while SCL was highover every bit so far
T5an arbitrary pattern, driven and read back off the wire
T6receive is transmitting onestx_en low releases, and the line is read
T7a receiving master is not arbitratingreleased on purpose, so a low is data
T8the sample is taken while transmitting tooone sample point, two consumers
T9a stretch delays the bit and does not corrupt itthe engine has no timing to lose
T10exactly one bit per SCL periodno double-drive, no skipped slot
T11two different releasesdeferred for deactivate, immediate for abort
T12reset releases SDA even mid-bit with a zero in flight
T13bit_done — one pulse, one cycle, once per bitthe independent observer
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_engine_tb.sv — the self-checking testbench
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_bit_engine_tb.sv
   // Independent oracle for i2c_bit_engine.
   //
   // The bit engine is meaningless in isolation -- it has no timing of its own -- so the
   // bench assembles the three blocks that make one bit happen: the SCL generator from
   // Chapter 17.3, the SDA owner and inverter from Chapter 17.4, and the engine itself. The
   // whole thing sits on a wired-AND bus with the bench as the second device.
   //
   // The central property is checked the way a receiving device would check it, from the
   // LINE: SDA must never change while SCL is high. That is §3.1.2, and in this design it
   // is structural rather than asserted -- the engine updates SDA only at `drive_point`,
   // which is inside the low phase -- so the bench's job is to demonstrate that the
   // structure delivers the guarantee over a long run rather than to trust the argument.
   // -----------------------------------------------------------------------------
   module i2c_bit_engine_tb;

      localparam integer NL = 13, NH = 6, NSU = 3, NSMP = 2;
      localparam integer OWN_BIT = 1;

      logic clk = 1'b0, rst_n = 1'b0;
      logic enable = 1'b0, active = 1'b0, tx_en = 1'b0, tx_bit = 1'b1;
      logic abort = 1'b0;

      // the bench, as the second device on the bus
      logic oth_scl_low = 1'b0, oth_sda_low = 1'b0;

      logic g_scl_low, drive_point, sample_point, scl_rising, scl_falling, stretching;
      logic [15:0] stretch_cycles, bits_generated;
      logic [1:0]  gphase;

      logic b_sda_req, b_sda_bit, rx_bit, rx_valid, bit_done, driving;
      logic [15:0] bits_driven, bits_sampled;

      logic [3:0] req, bit_val;
      logic       m_sda_low, grant_owned, tx_bit_eff, arb_now, arb_lost, conflict;
      logic [3:0] grant;
      logic [15:0] conflicts, arb_losses;

      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, g_scl_low}),
         .sda_drive_low({oth_sda_low, m_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));

      i2c_scl_gen #(.N_LOW(NL), .N_HIGH(NH), .N_SU(NSU), .N_SAMP(NSMP), .CNT_W(16)) u_scl (
         .clk(clk), .rst_n(rst_n), .enable(enable), .idle_low(1'b0),
         .scl_in(scl_in[0]),
         .scl_drive_low(g_scl_low),
         .drive_point(drive_point), .sample_point(sample_point),
         .scl_rising(scl_rising), .scl_falling(scl_falling),
         .stretching(stretching), .stretch_cycles(stretch_cycles),
         .bits_generated(bits_generated), .phase(gphase));

      i2c_bit_engine #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .active(active), .tx_en(tx_en), .tx_bit(tx_bit), .abort(abort),
         .sda_in(sda_in[0]), .scl_high(scl_in[0]),
         .sda_req(b_sda_req), .sda_bit(b_sda_bit),
         .rx_bit(rx_bit), .rx_valid(rx_valid), .bit_done(bit_done), .driving(driving),
         .bits_driven(bits_driven), .bits_sampled(bits_sampled));

      // Only the bit engine asks for SDA in this bench, at owner index 1.
      assign req     = {2'b00, b_sda_req, 1'b0};
      assign bit_val = {2'b00, b_sda_bit, 1'b0};

      i2c_sda_ctrl #(.N_OWNER(4), .CNT_W(16)) u_sda (
         .clk(clk), .rst_n(rst_n), .req(req), .bit_val(bit_val),
         .sda_in(sda_in[0]), .scl_in(scl_in[0]), .tx_active(driving),
         .sda_drive_low(m_sda_low),
         .grant(grant), .owned(grant_owned), .tx_bit(tx_bit_eff),
         .owner_conflict(conflict), .conflicts(conflicts),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(arb_losses),
         .arb_clear(1'b0));

      always #5 clk = ~clk;

      integer errors = 0;
      integer n, k;

      // ---- the §3.1.2 watchdog, from the LINE -------------------------------
      // Any SDA change while SCL is high is a framing event, and this bench never frames.
      // So over an entire run the count must be zero, and any nonzero value is an engine
      // that updated SDA outside the low phase.
      integer sda_change_while_high;
      logic scl_l, sda_l;
      // Capture what the line held at each rising edge, which is what a receiver reads.
      logic [15:0] rx_shift;
      integer n_line_bits;

      always @(negedge clk) begin
         if (rst_n) begin
            if (scl && scl_l && (sda != sda_l))
               sda_change_while_high = sda_change_while_high + 1;
            if (scl && !scl_l) begin
               rx_shift = {rx_shift[14:0], sda};
               n_line_bits = n_line_bits + 1;
            end
            scl_l = scl; sda_l = sda;
         end
      end

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

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; enable = 1'b0; active = 1'b0; tx_en = 1'b0; tx_bit = 1'b1;
            abort = 1'b0;
            oth_scl_low = 1'b0; oth_sda_low = 1'b0;
            sda_change_while_high = 0; scl_l = 1'b1; sda_l = 1'b1;
            rx_shift = 16'h0000; n_line_bits = 0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      // Wait for the next completed bit slot, bounded.
      //
      // This waits for the sample COUNTER to advance rather than for the `bit_done`
      // pulse. Waiting on the pulse looks equivalent and is not: the pulse is still high
      // when the previous call returned, so a second call with no intervening delay sees
      // it and returns at once without a bit having happened. A tight loop of such calls
      // then counts half the bits it asked for, and the tests that use it pass or fail
      // on a number the bench invented.
      integer bit_target;
      task next_bit (input integer max_cycles);
         begin
            bit_target = bits_sampled + 1;
            n = 0;
            while (bits_sampled < bit_target && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL next_bit: no bit completed in %0d cycles", max_cycles);
               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

      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

      // ---- independent observer for `bit_done` -------------------------------
      //
      // `next_bit` deliberately waits on the sample COUNTER rather than on this pulse
      // (see the note above), which leaves the pulse itself unchecked -- and it is the
      // signal the byte engine of Chapter 17.7 advances on, so an engine whose bits are
      // all correct and whose `bit_done` never fires would stall the layer above while
      // passing every test here.
      //
      // Observing it separately keeps `next_bit` as it is and still pins the contract:
      // one pulse, exactly one cycle long, exactly once per completed bit.
      integer n_done = 0;
      integer done_stuck = 0;
      reg     done_l = 1'b0;
      always @(posedge clk) begin
         if (!rst_n) begin
            n_done <= 0; done_stuck <= 0; done_l <= 1'b0;
         end else begin
            if (bit_done) begin
               n_done <= n_done + 1;
               if (done_l) done_stuck <= done_stuck + 1;   // high two cycles running
            end
            done_l <= bit_done;
         end
      end

      initial begin
         $display("=== i2c_bit_engine: one bit, two instants, no timing of its own ===");

         // ----------------------------------------------------------------
         // T1. Parked. An inactive engine releases SDA rather than holding the last bit.
         //     An engine that kept driving a zero after being stopped would hold the bus
         //     down, and the nine-pulse remedy of §3.1.16 exists because that happens.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b0; tx_en = 1'b1; tx_bit = 1'b0;
         for (k = 0; k < 40; k = k + 1) step;
         $display("T1  a parked engine releases SDA even when told to send a zero");
         ck_bit("T1 not asking for SDA", b_sda_req, 1'b0);
         ck_bit("T1 nothing driven", m_sda_low, 1'b0);
         ck_bit("T1 the line is high", sda, 1'b1);
         ck_int("T1 no bits driven", bits_driven, 0);

         // ----------------------------------------------------------------
         // T2. Transmit a one. The engine releases SDA, and a receiver sampling at the
         //     rising edge reads a one.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b1;
         next_bit(400);
         $display("T2  transmitting a one releases the line and a receiver reads a one");
         ck_bit("T2 driving", driving, 1'b1);
         ck_bit("T2 nothing pulled down", m_sda_low, 1'b0);
         ck_bit("T2 the last bit on the wire was a one", rx_shift[0], 1'b1);

         // ----------------------------------------------------------------
         // T3. Transmit a zero. The engine pulls SDA down and a receiver reads a zero.
         // ----------------------------------------------------------------
         @(negedge clk); tx_bit = 1'b0;
         next_bit(400); next_bit(400);
         $display("T3  transmitting a zero pulls the line down and a receiver reads zero");
         ck_bit("T3 pulled down", m_sda_low, 1'b1);
         ck_bit("T3 the last bit on the wire was a zero", rx_shift[0], 1'b0);

         // ----------------------------------------------------------------
         // T4. THE INVARIANT. Over every bit so far, SDA never changed while SCL was
         //     high. This is §3.1.2, and it holds because the engine's only write to SDA
         //     is at `drive_point`, which is inside the low phase by construction.
         // ----------------------------------------------------------------
         $display("T4  SDA never changed while SCL was high, over every bit so far");
         ck_int("T4 no SDA changes in a high phase", sda_change_while_high, 0);
         if (n_line_bits < 2) begin
            $display("  FAIL T4 too few bits observed for the invariant to mean anything");
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T5. An arbitrary pattern, driven bit by bit and read back off the wire. This is
         //     the end-to-end check that the inversion of Chapter 17.4 and the timing of
         //     Chapter 17.3 compose into a bit the bus actually carries.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1;
         for (k = 0; k < 8; k = k + 1) begin
            @(negedge clk); tx_bit = (8'hA6 >> (7 - k)) & 1'b1;
            next_bit(400);
         end
         $display("T5  an eight-bit pattern, MSB first, read back from the wire");
         ck_int("T5 the wire carried 0xA6", rx_shift[7:0], 8'hA6);
         ck_int("T5 eight bits were driven", bits_driven, 8);
         ck_int("T5 and still no SDA change in a high phase", sda_change_while_high, 0);

         // ----------------------------------------------------------------
         // T6. RECEIVE IS TRANSMITTING ONES. With tx_en low the engine releases SDA, so a
         //     second device can pull it down and the engine reads what the LINE says
         //     rather than what it wanted.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1;       // the other device sends a zero
         next_bit(400);
         $display("T6  receiving is releasing the line and believing what comes back");
         ck_bit("T6 not driving", driving, 1'b0);
         ck_bit("T6 the engine is not pulling SDA down", m_sda_low, 1'b0);
         ck_bit("T6 the line is low because the other device holds it", sda, 1'b0);
         ck_bit("T6 and the engine received a zero", rx_bit, 1'b0);
         @(negedge clk); oth_sda_low = 1'b0;
         next_bit(400);
         ck_bit("T6 released by both, the engine receives a one", rx_bit, 1'b1);

         // ----------------------------------------------------------------
         // T7. A RECEIVING MASTER IS NOT ARBITRATING. It released SDA deliberately, so a
         //     low readback is data. Chapter 17.4's detector fires on the electrical fact
         //     and the controller must gate it by direction -- which is why `driving` is
         //     an output of this block.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1;
         next_bit(400);
         $display("T7  a receiving master releases SDA on purpose, so a low is data");
         ck_bit("T7 the engine reports it is not driving", driving, 1'b0);
         ck_bit("T7 which is how the controller knows not to call this a lost contest",
                driving, 1'b0);
         ck_bit("T7 and the received bit is a zero", rx_bit, 1'b0);

         // ----------------------------------------------------------------
         // T8. THE SAMPLE IS TAKEN WHILE TRANSMITTING TOO. One sample point, two
         //     consumers: the receive path and §3.1.8's comparison. An engine that only
         //     sampled while receiving could not detect arbitration loss at all.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b1;
         @(negedge clk); oth_sda_low = 1'b1;       // a competitor sends a zero
         next_bit(400);
         $display("T8  the sample is taken while transmitting, which is what arbitration needs");
         ck_bit("T8 we intended a one", b_sda_bit, 1'b1);
         ck_bit("T8 the line reads zero", sda, 1'b0);
         ck_bit("T8 and the engine sampled that zero", rx_bit, 1'b0);
         ck_int("T8 the sample count keeps up with the bits", bits_sampled, bits_driven);
         ck_bit("T8 so Chapter 17.4 can see the loss", arb_lost, 1'b1);

         // ----------------------------------------------------------------
         // T9. A STRETCH DELAYS THE BIT AND DOES NOT CORRUPT IT. The engine has no
         //     timeout and no count; it waits because the generator waits.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b0;
         n = 0;
         while (gphase != 2'd1 && n < 200) begin step; n = n + 1; end
         @(negedge clk); oth_scl_low = 1'b1;       // hold SCL across the release
         k = bits_sampled;
         for (n = 0; n < 50; n = n + 1) begin
            step;
            ck_int("T9 no bit completed while SCL was held", bits_sampled, k);
         end
         @(negedge clk); oth_scl_low = 1'b0;
         next_bit(400);
         $display("T9  a stretch delays the bit and leaves it intact");
         ck_int("T9 the bit completed after the stretch", bits_sampled, k + 1);
         ck_bit("T9 and it was the zero we asked for", rx_shift[0], 1'b0);
         ck_int("T9 the invariant still holds", sda_change_while_high, 0);

         // ----------------------------------------------------------------
         // T10. Exactly one bit per SCL period. The engine cannot double-drive or
         //      double-sample, because both happen at a single-cycle strobe.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b1;
         for (k = 0; k < 6; k = k + 1) next_bit(400);
         $display("T10 exactly one drive and one sample per SCL period");
         ck_int("T10 six bits driven", bits_driven, 6);
         ck_int("T10 six bits sampled", bits_sampled, 6);
         // The generator's own count lags by exactly one here, and that is correct rather
         // than a discrepancy: it counts a bit when the HIGH phase ends, and the sample
         // happens early in that phase. So at the instant the sixth sample is taken the
         // sixth high phase is still running. Asserting equality would be asserting that
         // two different instants in the bit are the same instant.
         ck_int("T10 and the generator has completed five of them", bits_generated, 5);
         // Let the observer's own register catch the final pulse: it counts one cycle
         // after the DUT increments `bits_sampled`, so comparing immediately is a race in
         // the CHECK, not a defect in the design.
         step; step;
         ck_int("T13 bit_done fired exactly once per completed bit", n_done, bits_sampled);
         ck_int("T13 and was never high two cycles running", done_stuck, 0);
         if (bits_generated + 1 != bits_sampled) begin
            $display("  FAIL T10 the generator's count should lag the sample by one");
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T11. TWO DIFFERENT RELEASES, because two normative rules are in tension.
         //
         //      §3.1.2 says SDA may only change while SCL is LOW. §3.1.8 says an
         //      arbitration loser turns off its driver the MOMENT it sees the difference,
         //      which is during the HIGH phase. They are reconciled by what reaches the
         //      wire: a loser is being out-driven, so releasing produces no edge, while an
         //      ordinary end-of-byte release DOES make SDA rise -- and a rise while SCL is
         //      high is a STOP.
         //
         //      So deactivating defers until SCL is low, and `abort` does not.
         // ----------------------------------------------------------------
         @(negedge clk); tx_bit = 1'b0;
         next_bit(400);
         ck_bit("T11 currently pulling the line down", m_sda_low, 1'b1);
         @(negedge clk); active = 1'b0;
         step;
         $display("T11 two different releases, because two normative rules are in tension");
         // Deferred: while SCL is high the engine must still be holding the zero.
         if (scl && !m_sda_low) begin
            $display("  FAIL T11 released SDA during the high phase, which is a STOP");
            errors = errors + 1;
         end
         n = 0;
         while (m_sda_low && n < 300) begin step; n = n + 1; end
         ck_bit("T11 released, once SCL was low", m_sda_low, 1'b0);
         ck_bit("T11 and no longer driving", driving, 1'b0);
         // Immediate: an arbitration loser lets go at once, and may, because the winner is
         // holding the line low so no edge results.
         @(negedge clk); active = 1'b1; tx_en = 1'b1; tx_bit = 1'b0;
         next_bit(400);
         ck_bit("T11 driving a zero again", m_sda_low, 1'b1);
         @(negedge clk); oth_sda_low = 1'b1;    // the winner also holds it low
         @(negedge clk); abort = 1'b1;
         step;
         ck_bit("T11 abort releases immediately, whatever the clock is doing",
                m_sda_low, 1'b0);
         ck_bit("T11 and the line stays low, held by the winner", sda, 1'b0);
         @(negedge clk); abort = 1'b0; oth_sda_low = 1'b0; active = 1'b0;

         // ----------------------------------------------------------------
         // T12. Reset releases SDA even mid-bit with a zero in flight.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b0;
         next_bit(400);
         ck_bit("T12 pulling the line down before reset", m_sda_low, 1'b1);
         @(negedge clk); rst_n = 1'b0; #1;
         $display("T12 reset releases SDA with a zero in flight");
         ck_bit("T12 released", m_sda_low, 1'b0);
         ck_bit("T12 the line is high", sda, 1'b1);
         ck_bit("T12 not asking for SDA", b_sda_req, 1'b0);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_engine_tb.v — the same tests in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_bit_engine_tb.sv
   // Independent oracle for i2c_bit_engine.
   //
   // The bit engine is meaningless in isolation -- it has no timing of its own -- so the
   // bench assembles the three blocks that make one bit happen: the SCL generator from
   // Chapter 17.3, the SDA owner and inverter from Chapter 17.4, and the engine itself. The
   // whole thing sits on a wired-AND bus with the bench as the second device.
   //
   // The central property is checked the way a receiving device would check it, from the
   // LINE: SDA must never change while SCL is high. That is §3.1.2, and in this design it
   // is structural rather than asserted -- the engine updates SDA only at `drive_point`,
   // which is inside the low phase -- so the bench's job is to demonstrate that the
   // structure delivers the guarantee over a long run rather than to trust the argument.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_bit_engine_tb;

      localparam integer NL = 13, NH = 6, NSU = 3, NSMP = 2;
      localparam integer OWN_BIT = 1;

      reg clk = 1'b0, rst_n = 1'b0;
      reg enable = 1'b0, active = 1'b0, tx_en = 1'b0, tx_bit = 1'b1;
      reg abort = 1'b0;

      // the bench, as the second device on the bus
      reg oth_scl_low = 1'b0, oth_sda_low = 1'b0;

      wire g_scl_low, drive_point, sample_point, scl_rising, scl_falling, stretching;
      wire [15:0] stretch_cycles, bits_generated;
      wire [1:0]  gphase;

      wire b_sda_req, b_sda_bit, rx_bit, rx_valid, bit_done, driving;
      wire [15:0] bits_driven, bits_sampled;

      wire [3:0] req, bit_val;
      wire       m_sda_low, grant_owned, tx_bit_eff, arb_now, arb_lost, conflict;
      wire [3:0] grant;
      wire [15:0] conflicts, arb_losses;

      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, g_scl_low}),
         .sda_drive_low({oth_sda_low, m_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));

      i2c_scl_gen #(.N_LOW(NL), .N_HIGH(NH), .N_SU(NSU), .N_SAMP(NSMP), .CNT_W(16)) u_scl (
         .clk(clk), .rst_n(rst_n), .enable(enable), .idle_low(1'b0),
         .scl_in(scl_in[0]),
         .scl_drive_low(g_scl_low),
         .drive_point(drive_point), .sample_point(sample_point),
         .scl_rising(scl_rising), .scl_falling(scl_falling),
         .stretching(stretching), .stretch_cycles(stretch_cycles),
         .bits_generated(bits_generated), .phase(gphase));

      i2c_bit_engine #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .active(active), .tx_en(tx_en), .tx_bit(tx_bit), .abort(abort),
         .sda_in(sda_in[0]), .scl_high(scl_in[0]),
         .sda_req(b_sda_req), .sda_bit(b_sda_bit),
         .rx_bit(rx_bit), .rx_valid(rx_valid), .bit_done(bit_done), .driving(driving),
         .bits_driven(bits_driven), .bits_sampled(bits_sampled));

      // Only the bit engine asks for SDA in this bench, at owner index 1.
      assign req     = {2'b00, b_sda_req, 1'b0};
      assign bit_val = {2'b00, b_sda_bit, 1'b0};

      i2c_sda_ctrl #(.N_OWNER(4), .CNT_W(16)) u_sda (
         .clk(clk), .rst_n(rst_n), .req(req), .bit_val(bit_val),
         .sda_in(sda_in[0]), .scl_in(scl_in[0]), .tx_active(driving),
         .sda_drive_low(m_sda_low),
         .grant(grant), .owned(grant_owned), .tx_bit(tx_bit_eff),
         .owner_conflict(conflict), .conflicts(conflicts),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(arb_losses),
         .arb_clear(1'b0));

      always #5 clk = ~clk;

      integer errors = 0;
      integer n, k;

      // ---- the §3.1.2 watchdog, from the LINE -------------------------------
      // Any SDA change while SCL is high is a framing event, and this bench never frames.
      // So over an entire run the count must be zero, and any nonzero value is an engine
      // that updated SDA outside the low phase.
      integer sda_change_while_high;
      reg scl_l, sda_l;
      // Capture what the line held at each rising edge, which is what a receiver reads.
      reg [15:0] rx_shift;
      integer n_line_bits;

      always @(negedge clk) begin
         if (rst_n) begin
            if (scl && scl_l && (sda != sda_l))
               sda_change_while_high = sda_change_while_high + 1;
            if (scl && !scl_l) begin
               rx_shift = {rx_shift[14:0], sda};
               n_line_bits = n_line_bits + 1;
            end
            scl_l = scl; sda_l = sda;
         end
      end

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

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; enable = 1'b0; active = 1'b0; tx_en = 1'b0; tx_bit = 1'b1;
            abort = 1'b0;
            oth_scl_low = 1'b0; oth_sda_low = 1'b0;
            sda_change_while_high = 0; scl_l = 1'b1; sda_l = 1'b1;
            rx_shift = 16'h0000; n_line_bits = 0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      // Wait for the next completed bit slot, bounded.
      //
      // This waits for the sample COUNTER to advance rather than for the `bit_done`
      // pulse. Waiting on the pulse looks equivalent and is not: the pulse is still high
      // when the previous call returned, so a second call with no intervening delay sees
      // it and returns at once without a bit having happened. A tight loop of such calls
      // then counts half the bits it asked for, and the tests that use it pass or fail
      // on a number the bench invented.
      integer bit_target;
      task next_bit (input integer max_cycles);
         begin
            bit_target = bits_sampled + 1;
            n = 0;
            while (bits_sampled < bit_target && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL next_bit: no bit completed in %0d cycles", max_cycles);
               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

      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

      // ---- independent observer for `bit_done` -------------------------------
      //
      // `next_bit` deliberately waits on the sample COUNTER rather than on this pulse
      // (see the note above), which leaves the pulse itself unchecked -- and it is the
      // signal the byte engine of Chapter 17.7 advances on, so an engine whose bits are
      // all correct and whose `bit_done` never fires would stall the layer above while
      // passing every test here.
      //
      // Observing it separately keeps `next_bit` as it is and still pins the contract:
      // one pulse, exactly one cycle long, exactly once per completed bit.
      integer n_done;
      integer done_stuck;
      reg     done_l;
      always @(posedge clk) begin
         if (!rst_n) begin
            n_done <= 0; done_stuck <= 0; done_l <= 1'b0;
         end else begin
            if (bit_done) begin
               n_done <= n_done + 1;
               if (done_l) done_stuck <= done_stuck + 1;   // high two cycles running
            end
            done_l <= bit_done;
         end
      end

      initial begin
         $display("=== i2c_bit_engine: one bit, two instants, no timing of its own ===");

         // ----------------------------------------------------------------
         // T1. Parked. An inactive engine releases SDA rather than holding the last bit.
         //     An engine that kept driving a zero after being stopped would hold the bus
         //     down, and the nine-pulse remedy of §3.1.16 exists because that happens.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b0; tx_en = 1'b1; tx_bit = 1'b0;
         for (k = 0; k < 40; k = k + 1) step;
         $display("T1  a parked engine releases SDA even when told to send a zero");
         ck_bit("T1 not asking for SDA", b_sda_req, 1'b0);
         ck_bit("T1 nothing driven", m_sda_low, 1'b0);
         ck_bit("T1 the line is high", sda, 1'b1);
         ck_int("T1 no bits driven", bits_driven, 0);

         // ----------------------------------------------------------------
         // T2. Transmit a one. The engine releases SDA, and a receiver sampling at the
         //     rising edge reads a one.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b1;
         next_bit(400);
         $display("T2  transmitting a one releases the line and a receiver reads a one");
         ck_bit("T2 driving", driving, 1'b1);
         ck_bit("T2 nothing pulled down", m_sda_low, 1'b0);
         ck_bit("T2 the last bit on the wire was a one", rx_shift[0], 1'b1);

         // ----------------------------------------------------------------
         // T3. Transmit a zero. The engine pulls SDA down and a receiver reads a zero.
         // ----------------------------------------------------------------
         @(negedge clk); tx_bit = 1'b0;
         next_bit(400); next_bit(400);
         $display("T3  transmitting a zero pulls the line down and a receiver reads zero");
         ck_bit("T3 pulled down", m_sda_low, 1'b1);
         ck_bit("T3 the last bit on the wire was a zero", rx_shift[0], 1'b0);

         // ----------------------------------------------------------------
         // T4. THE INVARIANT. Over every bit so far, SDA never changed while SCL was
         //     high. This is §3.1.2, and it holds because the engine's only write to SDA
         //     is at `drive_point`, which is inside the low phase by construction.
         // ----------------------------------------------------------------
         $display("T4  SDA never changed while SCL was high, over every bit so far");
         ck_int("T4 no SDA changes in a high phase", sda_change_while_high, 0);
         if (n_line_bits < 2) begin
            $display("  FAIL T4 too few bits observed for the invariant to mean anything");
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T5. An arbitrary pattern, driven bit by bit and read back off the wire. This is
         //     the end-to-end check that the inversion of Chapter 17.4 and the timing of
         //     Chapter 17.3 compose into a bit the bus actually carries.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1;
         for (k = 0; k < 8; k = k + 1) begin
            @(negedge clk); tx_bit = (8'hA6 >> (7 - k)) & 1'b1;
            next_bit(400);
         end
         $display("T5  an eight-bit pattern, MSB first, read back from the wire");
         ck_int("T5 the wire carried 0xA6", rx_shift[7:0], 8'hA6);
         ck_int("T5 eight bits were driven", bits_driven, 8);
         ck_int("T5 and still no SDA change in a high phase", sda_change_while_high, 0);

         // ----------------------------------------------------------------
         // T6. RECEIVE IS TRANSMITTING ONES. With tx_en low the engine releases SDA, so a
         //     second device can pull it down and the engine reads what the LINE says
         //     rather than what it wanted.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1;       // the other device sends a zero
         next_bit(400);
         $display("T6  receiving is releasing the line and believing what comes back");
         ck_bit("T6 not driving", driving, 1'b0);
         ck_bit("T6 the engine is not pulling SDA down", m_sda_low, 1'b0);
         ck_bit("T6 the line is low because the other device holds it", sda, 1'b0);
         ck_bit("T6 and the engine received a zero", rx_bit, 1'b0);
         @(negedge clk); oth_sda_low = 1'b0;
         next_bit(400);
         ck_bit("T6 released by both, the engine receives a one", rx_bit, 1'b1);

         // ----------------------------------------------------------------
         // T7. A RECEIVING MASTER IS NOT ARBITRATING. It released SDA deliberately, so a
         //     low readback is data. Chapter 17.4's detector fires on the electrical fact
         //     and the controller must gate it by direction -- which is why `driving` is
         //     an output of this block.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b0;
         @(negedge clk); oth_sda_low = 1'b1;
         next_bit(400);
         $display("T7  a receiving master releases SDA on purpose, so a low is data");
         ck_bit("T7 the engine reports it is not driving", driving, 1'b0);
         ck_bit("T7 which is how the controller knows not to call this a lost contest",
                driving, 1'b0);
         ck_bit("T7 and the received bit is a zero", rx_bit, 1'b0);

         // ----------------------------------------------------------------
         // T8. THE SAMPLE IS TAKEN WHILE TRANSMITTING TOO. One sample point, two
         //     consumers: the receive path and §3.1.8's comparison. An engine that only
         //     sampled while receiving could not detect arbitration loss at all.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b1;
         @(negedge clk); oth_sda_low = 1'b1;       // a competitor sends a zero
         next_bit(400);
         $display("T8  the sample is taken while transmitting, which is what arbitration needs");
         ck_bit("T8 we intended a one", b_sda_bit, 1'b1);
         ck_bit("T8 the line reads zero", sda, 1'b0);
         ck_bit("T8 and the engine sampled that zero", rx_bit, 1'b0);
         ck_int("T8 the sample count keeps up with the bits", bits_sampled, bits_driven);
         ck_bit("T8 so Chapter 17.4 can see the loss", arb_lost, 1'b1);

         // ----------------------------------------------------------------
         // T9. A STRETCH DELAYS THE BIT AND DOES NOT CORRUPT IT. The engine has no
         //     timeout and no count; it waits because the generator waits.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b0;
         n = 0;
         while (gphase != 2'd1 && n < 200) begin step; n = n + 1; end
         @(negedge clk); oth_scl_low = 1'b1;       // hold SCL across the release
         k = bits_sampled;
         for (n = 0; n < 50; n = n + 1) begin
            step;
            ck_int("T9 no bit completed while SCL was held", bits_sampled, k);
         end
         @(negedge clk); oth_scl_low = 1'b0;
         next_bit(400);
         $display("T9  a stretch delays the bit and leaves it intact");
         ck_int("T9 the bit completed after the stretch", bits_sampled, k + 1);
         ck_bit("T9 and it was the zero we asked for", rx_shift[0], 1'b0);
         ck_int("T9 the invariant still holds", sda_change_while_high, 0);

         // ----------------------------------------------------------------
         // T10. Exactly one bit per SCL period. The engine cannot double-drive or
         //      double-sample, because both happen at a single-cycle strobe.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b1;
         for (k = 0; k < 6; k = k + 1) next_bit(400);
         $display("T10 exactly one drive and one sample per SCL period");
         ck_int("T10 six bits driven", bits_driven, 6);
         ck_int("T10 six bits sampled", bits_sampled, 6);
         // The generator's own count lags by exactly one here, and that is correct rather
         // than a discrepancy: it counts a bit when the HIGH phase ends, and the sample
         // happens early in that phase. So at the instant the sixth sample is taken the
         // sixth high phase is still running. Asserting equality would be asserting that
         // two different instants in the bit are the same instant.
         ck_int("T10 and the generator has completed five of them", bits_generated, 5);
         // Let the observer's own register catch the final pulse: it counts one cycle
         // after the DUT increments `bits_sampled`, so comparing immediately is a race in
         // the CHECK, not a defect in the design.
         step; step;
         ck_int("T13 bit_done fired exactly once per completed bit", n_done, bits_sampled);
         ck_int("T13 and was never high two cycles running", done_stuck, 0);
         if (bits_generated + 1 != bits_sampled) begin
            $display("  FAIL T10 the generator's count should lag the sample by one");
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T11. TWO DIFFERENT RELEASES, because two normative rules are in tension.
         //
         //      §3.1.2 says SDA may only change while SCL is LOW. §3.1.8 says an
         //      arbitration loser turns off its driver the MOMENT it sees the difference,
         //      which is during the HIGH phase. They are reconciled by what reaches the
         //      wire: a loser is being out-driven, so releasing produces no edge, while an
         //      ordinary end-of-byte release DOES make SDA rise -- and a rise while SCL is
         //      high is a STOP.
         //
         //      So deactivating defers until SCL is low, and `abort` does not.
         // ----------------------------------------------------------------
         @(negedge clk); tx_bit = 1'b0;
         next_bit(400);
         ck_bit("T11 currently pulling the line down", m_sda_low, 1'b1);
         @(negedge clk); active = 1'b0;
         step;
         $display("T11 two different releases, because two normative rules are in tension");
         // Deferred: while SCL is high the engine must still be holding the zero.
         if (scl && !m_sda_low) begin
            $display("  FAIL T11 released SDA during the high phase, which is a STOP");
            errors = errors + 1;
         end
         n = 0;
         while (m_sda_low && n < 300) begin step; n = n + 1; end
         ck_bit("T11 released, once SCL was low", m_sda_low, 1'b0);
         ck_bit("T11 and no longer driving", driving, 1'b0);
         // Immediate: an arbitration loser lets go at once, and may, because the winner is
         // holding the line low so no edge results.
         @(negedge clk); active = 1'b1; tx_en = 1'b1; tx_bit = 1'b0;
         next_bit(400);
         ck_bit("T11 driving a zero again", m_sda_low, 1'b1);
         @(negedge clk); oth_sda_low = 1'b1;    // the winner also holds it low
         @(negedge clk); abort = 1'b1;
         step;
         ck_bit("T11 abort releases immediately, whatever the clock is doing",
                m_sda_low, 1'b0);
         ck_bit("T11 and the line stays low, held by the winner", sda, 1'b0);
         @(negedge clk); abort = 1'b0; oth_sda_low = 1'b0; active = 1'b0;

         // ----------------------------------------------------------------
         // T12. Reset releases SDA even mid-bit with a zero in flight.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); enable = 1'b1; active = 1'b1; tx_en = 1'b1; tx_bit = 1'b0;
         next_bit(400);
         ck_bit("T12 pulling the line down before reset", m_sda_low, 1'b1);
         @(negedge clk); rst_n = 1'b0; #1;
         $display("T12 reset releases SDA with a zero in flight");
         ck_bit("T12 released", m_sda_low, 1'b0);
         ck_bit("T12 the line is high", sda, 1'b1);
         ck_bit("T12 not asking for SDA", b_sda_req, 1'b0);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_engine_tb.vhd — the same tests in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_bit_engine_tb.vhd
   -- Independent oracle for i2c_bit_engine. Behavioural twin of the SV and Verilog benches.
   --
   -- The bit engine is meaningless in isolation -- it has no timing of its own -- so the bench
   -- assembles the three blocks that make one bit happen: the SCL generator, the SDA owner and
   -- inverter, and the engine itself, on a wired-AND bus with the bench as the second device.
   --
   -- The central property is checked the way a receiving device would check it, from the LINE:
   -- SDA must never change while SCL is high. In this design that is structural rather than
   -- asserted, so the bench's job is to demonstrate that the structure delivers the guarantee
   -- over a long run rather than to trust the argument.
   -- ---------------------------------------------------------------------------

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

   entity i2c_bit_engine_tb is
   end entity i2c_bit_engine_tb;

   architecture sim of i2c_bit_engine_tb is

      constant TCLK : time := 10 ns;
      constant NL : integer := 13;
      constant NH : integer := 6;
      constant NSU : integer := 3;
      constant NSMP : integer := 2;
      constant OWN_BIT : integer := 1;

      signal clk, rst_n : std_logic := '0';
      signal enable, active, tx_en, abort : std_logic := '0';
      signal tx_bit : std_logic := '1';
      signal oth_scl_low, oth_sda_low : std_logic := '0';

      signal g_scl_low, drive_point, sample_point, g_rise, g_fall, g_stretch : std_logic;
      signal g_scyc, g_bits : unsigned(15 downto 0);
      signal gphase : unsigned(1 downto 0);

      signal b_sda_req, b_sda_bit, rx_bit, rx_valid, bit_done, driving : std_logic;
      signal bits_driven, bits_sampled : unsigned(15 downto 0);

      signal req, bit_val, grant : std_logic_vector(3 downto 0);
      signal m_sda_low, sda_owned, tx_bit_eff, arb_now, arb_lost, conflict : std_logic;
      signal n_conf, n_arb : unsigned(15 downto 0);

      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);

      -- A constant rather than an inline type conversion, because VHDL cannot index the result
      -- of a conversion: `std_logic_vector(to_unsigned(...))(i)` is not a legal name.
      constant PATTERN : std_logic_vector(7 downto 0) := x"A6";

      signal bad_high : integer := 0;
      signal rx_shift : std_logic_vector(15 downto 0) := (others => '0');
      signal n_line_bits : integer := 0;
      signal halt : boolean := false;

      -- Independent observer for `bit_done`. The stimulus waits on the sample COUNTER
      -- rather than on this pulse, which leaves the pulse unchecked -- and it is what the
      -- byte engine of Chapter 17.7 advances on, so an engine whose bits are all correct
      -- and whose `bit_done` never fires would stall the layer above while passing every
      -- other test. One pulse, one cycle, once per completed bit.
      signal n_done     : integer := 0;
      signal done_stuck : integer := 0;

   begin

      scl_drv <= oth_scl_low & g_scl_low;
      sda_drv <= oth_sda_low & m_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);

      u_scl : entity work.i2c_scl_gen
         generic map (N_LOW => NL, N_HIGH => NH, N_SU => NSU, N_SAMP => NSMP, CNT_W => 16)
         port map (clk => clk, rst_n => rst_n, enable => enable, idle_low => '0',
            scl_in => scl_in(0), scl_drive_low => g_scl_low,
            drive_point => drive_point, sample_point => sample_point,
            scl_rising => g_rise, scl_falling => g_fall,
            stretching => g_stretch, stretch_cycles => g_scyc,
            bits_generated => g_bits, phase => gphase);

      dut : entity work.i2c_bit_engine
         generic map (CNT_W => 16)
         port map (clk => clk, rst_n => rst_n,
            drive_point => drive_point, sample_point => sample_point,
            active => active, tx_en => tx_en, tx_bit => tx_bit, abort => abort,
            sda_in => sda_in(0), scl_high => scl_in(0),
            sda_req => b_sda_req, sda_bit => b_sda_bit,
            rx_bit => rx_bit, rx_valid => rx_valid, bit_done => bit_done,
            driving => driving,
            bits_driven => bits_driven, bits_sampled => bits_sampled);

      -- Only the bit engine asks for SDA in this bench, at owner index 1.
      req     <= "00" & b_sda_req & '0';
      bit_val <= "00" & b_sda_bit & '0';

      u_sda : entity work.i2c_sda_ctrl
         generic map (N_OWNER => 4, CNT_W => 16)
         port map (clk => clk, rst_n => rst_n, req => req, bit_val => bit_val,
            sda_in => sda_in(0), scl_in => scl_in(0), tx_active => driving,
            sda_drive_low => m_sda_low,
            grant => grant, owned => sda_owned, tx_bit => tx_bit_eff,
            owner_conflict => conflict, conflicts => n_conf,
            arb_loss_now => arb_now, arb_lost => arb_lost, arb_losses => n_arb,
            arb_clear => '0');

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

      -- The §3.1.2 watchdog, from the LINE. This bench never frames, so over an entire run the
      -- count must be zero and any nonzero value is an engine that updated SDA outside the low
      -- phase. Running counters are variables so they behave like blocking assignments.
      meas : process (clk, rst_n)
         variable scl_l, sda_l : std_logic := '1';
      begin
         if rst_n = '0' then
            scl_l := '1'; sda_l := '1';
            bad_high <= 0; rx_shift <= (others => '0'); n_line_bits <= 0;
         elsif falling_edge(clk) then
            if scl = '1' and scl_l = '1' and sda /= sda_l then
               bad_high <= bad_high + 1;
            end if;
            if scl = '1' and scl_l = '0' then
               rx_shift    <= rx_shift(14 downto 0) & sda;
               n_line_bits <= n_line_bits + 1;
            end if;
            scl_l := scl; sda_l := sda;
         end if;
      end process;

      done_obs : process (clk, rst_n)
         variable done_l : std_logic := '0';
      begin
         if rst_n = '0' then
            n_done <= 0; done_stuck <= 0; done_l := '0';
         elsif rising_edge(clk) then
            if bit_done = '1' then
               n_done <= n_done + 1;
               if done_l = '1' then done_stuck <= done_stuck + 1; end if;
            end if;
            done_l := bit_done;
         end if;
      end process;

      stim : process
         variable err : integer := 0;
         variable n, bit_target : integer;
         -- A separate saved count, because `next_bit` uses `bit_target` for its own target and
         -- reusing it silently overwrote the value T9 had just saved.
         variable saved : 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'; enable <= '0'; active <= '0'; tx_en <= '0'; tx_bit <= '1';
            abort <= '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;

         -- Waits for the sample COUNTER to advance rather than for the `bit_done` pulse.
         -- Waiting on the pulse looks equivalent and is not: the pulse is still high when the
         -- previous call returned, so a second call with no intervening delay returns at once
         -- without a bit having happened, and a tight loop counts half the bits it asked for.
         procedure next_bit (max_cycles : integer) is
         begin
            bit_target := to_integer(bits_sampled) + 1;
            n := 0;
            while to_integer(bits_sampled) < bit_target and n < max_cycles loop
               step; n := n + 1;
            end loop;
            if n >= max_cycles then
               report "  FAIL next_bit: no bit completed" severity note;
               err := err + 1;
            end if;
         end procedure;

      begin
         report "=== i2c_bit_engine: one bit, two instants, no timing of its own ==="
                severity note;

         -- T1. Parked. An inactive engine releases SDA rather than holding the last bit.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '0'; tx_en <= '1'; tx_bit <= '0';
         for i in 0 to 39 loop step; end loop;
         report "T1  a parked engine releases SDA even when told to send a zero" severity note;
         ck_bit("T1 not asking for SDA", b_sda_req, '0');
         ck_bit("T1 nothing driven", m_sda_low, '0');
         ck_bit("T1 the line is high", sda, '1');
         ck_int("T1 no bits driven", to_integer(bits_driven), 0);

         -- T2. Transmit a one: the engine releases SDA and a receiver reads a one.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '1'; tx_bit <= '1';
         next_bit(400);
         report "T2  transmitting a one releases the line and a receiver reads a one"
                severity note;
         ck_bit("T2 driving", driving, '1');
         ck_bit("T2 nothing pulled down", m_sda_low, '0');
         ck_bit("T2 the last bit on the wire was a one", rx_shift(0), '1');

         -- T3. Transmit a zero.
         wait until falling_edge(clk); tx_bit <= '0';
         next_bit(400); next_bit(400);
         report "T3  transmitting a zero pulls the line down and a receiver reads zero"
                severity note;
         ck_bit("T3 pulled down", m_sda_low, '1');
         ck_bit("T3 the last bit on the wire was a zero", rx_shift(0), '0');

         -- T4. THE INVARIANT. Over every bit so far, SDA never changed while SCL was high.
         report "T4  SDA never changed while SCL was high, over every bit so far"
                severity note;
         ck_int("T4 no SDA changes in a high phase", bad_high, 0);
         if n_line_bits < 2 then
            report "  FAIL T4 too few bits observed for the invariant to mean anything"
                   severity note;
            err := err + 1;
         end if;

         -- T5. An eight-bit pattern, driven bit by bit and read back off the wire.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '1';
         for k in 0 to 7 loop
            wait until falling_edge(clk);
            tx_bit <= PATTERN(7 - k);
            next_bit(400);
         end loop;
         report "T5  an eight-bit pattern, MSB first, read back from the wire" severity note;
         ck_int("T5 the wire carried 0xA6",
                to_integer(unsigned(rx_shift(7 downto 0))), 16#A6#);
         ck_int("T5 eight bits were driven", to_integer(bits_driven), 8);
         ck_int("T5 and still no SDA change in a high phase", bad_high, 0);

         -- T6. RECEIVE IS TRANSMITTING ONES. With tx_en low the engine releases SDA, so a
         --     second device can pull it down and the engine reads the LINE.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '0';
         wait until falling_edge(clk); oth_sda_low <= '1';
         next_bit(400);
         report "T6  receiving is releasing the line and believing what comes back"
                severity note;
         ck_bit("T6 not driving", driving, '0');
         ck_bit("T6 the engine is not pulling SDA down", m_sda_low, '0');
         ck_bit("T6 the line is low because the other device holds it", sda, '0');
         ck_bit("T6 and the engine received a zero", rx_bit, '0');
         wait until falling_edge(clk); oth_sda_low <= '0';
         next_bit(400);
         ck_bit("T6 released by both, the engine receives a one", rx_bit, '1');

         -- T7. A RECEIVING MASTER IS NOT ARBITRATING.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '0';
         wait until falling_edge(clk); oth_sda_low <= '1';
         next_bit(400);
         report "T7  a receiving master releases SDA on purpose, so a low is data"
                severity note;
         ck_bit("T7 the engine reports it is not driving", driving, '0');
         ck_bit("T7 which is how the controller knows not to call this a lost contest",
                driving, '0');
         ck_bit("T7 and the received bit is a zero", rx_bit, '0');

         -- T8. THE SAMPLE IS TAKEN WHILE TRANSMITTING TOO: one sample point, two consumers.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '1'; tx_bit <= '1';
         wait until falling_edge(clk); oth_sda_low <= '1';
         next_bit(400);
         report "T8  the sample is taken while transmitting, which is what arbitration needs"
                severity note;
         ck_bit("T8 we intended a one", b_sda_bit, '1');
         ck_bit("T8 the line reads zero", sda, '0');
         ck_bit("T8 and the engine sampled that zero", rx_bit, '0');
         ck_int("T8 the sample count keeps up with the bits",
                to_integer(bits_sampled), to_integer(bits_driven));
         ck_bit("T8 so Chapter 17.4 can see the loss", arb_lost, '1');

         -- T9. A STRETCH DELAYS THE BIT AND DOES NOT CORRUPT IT.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '1'; tx_bit <= '0';
         n := 0;
         while to_integer(gphase) /= 1 and n < 200 loop step; n := n + 1; end loop;
         wait until falling_edge(clk); oth_scl_low <= '1';
         saved := to_integer(bits_sampled);
         for i in 0 to 49 loop
            step;
            ck_int("T9 no bit completed while SCL was held",
                   to_integer(bits_sampled), saved);
         end loop;
         wait until falling_edge(clk); oth_scl_low <= '0';
         next_bit(400);
         report "T9  a stretch delays the bit and leaves it intact" severity note;
         ck_int("T9 the bit completed after the stretch",
                to_integer(bits_sampled), saved + 1);
         ck_bit("T9 and it was the zero we asked for", rx_shift(0), '0');
         ck_int("T9 the invariant still holds", bad_high, 0);

         -- T10. Exactly one bit per SCL period.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '1'; tx_bit <= '1';
         for k in 0 to 5 loop next_bit(400); end loop;
         report "T10 exactly one drive and one sample per SCL period" severity note;
         ck_int("T10 six bits driven", to_integer(bits_driven), 6);
         ck_int("T10 six bits sampled", to_integer(bits_sampled), 6);
         -- The generator's own count lags by exactly one here, and that is correct rather than
         -- a discrepancy: it counts a bit when the HIGH phase ends, and the sample happens
         -- early in that phase.
         ck_int("T10 and the generator has completed five of them", to_integer(g_bits), 5);
         -- Let the observer's own register catch the final pulse: it counts one cycle
         -- after the DUT increments bits_sampled, so comparing immediately is a race in
         -- the CHECK, not a defect in the design.
         step; step;
         ck_int("T13 bit_done fired exactly once per completed bit",
                n_done, to_integer(bits_sampled));
         ck_int("T13 and was never high two cycles running", done_stuck, 0);
         if to_integer(g_bits) + 1 /= to_integer(bits_sampled) then
            report "  FAIL T10 the generator's count should lag the sample by one"
                   severity note;
            err := err + 1;
         end if;

         -- T11. TWO DIFFERENT RELEASES, because two normative rules are in tension.
         wait until falling_edge(clk); tx_bit <= '0';
         next_bit(400);
         ck_bit("T11 currently pulling the line down", m_sda_low, '1');
         wait until falling_edge(clk); active <= '0';
         step;
         report "T11 two different releases, because two normative rules are in tension"
                severity note;
         if scl = '1' and m_sda_low = '0' then
            report "  FAIL T11 released SDA during the high phase, which is a STOP"
                   severity note;
            err := err + 1;
         end if;
         n := 0;
         while m_sda_low = '1' and n < 300 loop step; n := n + 1; end loop;
         ck_bit("T11 released, once SCL was low", m_sda_low, '0');
         ck_bit("T11 and no longer driving", driving, '0');
         wait until falling_edge(clk);
         active <= '1'; tx_en <= '1'; tx_bit <= '0';
         next_bit(400);
         ck_bit("T11 driving a zero again", m_sda_low, '1');
         wait until falling_edge(clk); oth_sda_low <= '1';
         wait until falling_edge(clk); abort <= '1';
         step;
         ck_bit("T11 abort releases immediately, whatever the clock is doing", m_sda_low, '0');
         ck_bit("T11 and the line stays low, held by the winner", sda, '0');
         wait until falling_edge(clk);
         abort <= '0'; oth_sda_low <= '0'; active <= '0';

         -- T12. Reset releases SDA even mid-bit with a zero in flight.
         do_reset;
         wait until falling_edge(clk);
         enable <= '1'; active <= '1'; tx_en <= '1'; tx_bit <= '0';
         next_bit(400);
         ck_bit("T12 pulling the line down before reset", m_sda_low, '1');
         wait until falling_edge(clk); rst_n <= '0'; wait for 1 ns;
         report "T12 reset releases SDA with a zero in flight" severity note;
         ck_bit("T12 released", m_sda_low, '0');
         ck_bit("T12 the line is high", sda, '1');
         ck_bit("T12 not asking for SDA", b_sda_req, '0');

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

   end architecture sim;

6b. Execution

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_bit_enginePASS 13/13PASS 13/13PASS 13/135891 ns, all three

7. Mutation Testing

Ten defects, each attacking one claim above.

#Injected defectExpected detectionResult
M1update SDA at the sample point — SDA changes while SCL is highT4KILLED (7)
M2sample at the drive point, inside the low phaseT5, T6KILLED (47)
M3the sign error — transmitting a one pulls the line lowT2, T5KILLED (14)
M4a receiving master drives a zero instead of releasingT6KILLED (3)
M5a receiving master claims to be drivingT7KILLED (4)
M6release immediately on deactivate, even while SCL is highT11KILLED (2)
M7no sample while transmittingT8KILLED (31)
M8bit_done never firesT13 after strengtheningKILLED (2)
M9abort defers like a normal releaseT11KILLED (2)
M10reset no longer releases SDAT12KILLED (2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
killed: 10   survived: 0   score: 10/10
restored: PASS

M8 survived, and it exposed an untested output

The mutation prevents bit_done from ever asserting. Every bit still drives and samples correctly, so the whole suite passed — because next_bit waits on the sample counter and nothing anywhere checked the pulse.

The bench's choice to avoid waiting on the pulse was correct and documented. The gap was the unexamined consequence: avoiding a signal as a synchronisation source quietly removed it from verification altogether.

The fix is not to change next_bit — that would reintroduce the double-count race — but to observe the pulse separately: count it, catch it being high two cycles running, and require one per completed bit. Ported to all three languages, with a two-cycle settle before the final comparison, since the observer registers the pulse one cycle after the DUT increments its sample counter.

On the spread: 47 and 31 versus 2

M2 and M7 fail dozens of checks because they break the sample path, and almost every test reads a sampled value. M6, M9 and M10 fail two each — each is guarded by exactly one test. As in Chapter 17.3 §7, the low counts mark the properties that a single test is protecting, and M6 and M9 are the two halves of §4's tension. Those are the checks not to delete.

8. Verification Connection — What a Bit-Level Monitor Cannot Tell You

Azvya Education Pvt. Ltd.VLSI Mentor
bit_engine_sva.sv — the invariant, and the one that cannot be written here
   // 1. THE DATA-VALID RULE, as an assertion. Note this is a CHECK of something the
   //    design makes structurally impossible -- which is exactly why it is worth
   //    having: it fails the day someone adds a second write path to sda_bit.
   property p_sda_stable_while_scl_high;
      @(posedge clk) disable iff (!rst_n)
         (scl_high && $past(scl_high)) |-> $stable(sda_bit);
   endproperty
   a_valid: assert property (p_sda_stable_while_scl_high);

   // 2. TRANSMITTING A ONE NEVER PULLS THE LINE DOWN. The sign error of Chapter 17.4,
   //    asserted at the engine's own boundary rather than at the pad.
   property p_one_never_drives_low;
      @(posedge clk) disable iff (!rst_n)
         (sda_req && sda_bit) |-> !sda_drive_low_o;
   endproperty
   a_sign: assert property (p_one_never_drives_low);

   // 3. bit_done IS A PULSE.
   property p_done_is_a_pulse;
      @(posedge clk) disable iff (!rst_n) bit_done |=> !bit_done;
   endproperty
   a_pulse: assert property (p_done_is_a_pulse);

   // WHAT CANNOT BE ASSERTED AT THIS LEVEL, and it is the important part.
   //
   // "the bit received was the bit the target sent" is NOT expressible here. The
   // engine samples the line; whether the line carried what some target intended is
   // a fact about another device. At bit level the only honest property is that the
   // engine sampled at the right INSTANT -- correctness of the VALUE needs a second
   // model of the sender, which is the target model, and it lives in the bench.
   //
   // Likewise "this bit was bit 3 of the address byte" has no meaning in this block.
   // It has no bit counter, by design (§1). A bit-level monitor can therefore report
   //
   //     a bit slot happened, its value, its direction
   //
   // and nothing about position or meaning. Position arrives in 17.7, and meaning in
   // 17.8 -- which is why a UVM environment for I²C has a LAYERED monitor rather
   // than one that tries to emit address-and-data transactions from pin wiggles.

9. FPGA and ASIC Implications

On an FPGA, the sampled value comes through the SDA readback synchroniser, so the bit engine reads the line two cycles after it settled. That interacts with sample_point in a way worth being explicit about: the strobe fires at a chosen offset into the high phase, and the value it latches is the line as it was two cycles earlier. Provided N_SAMP leaves more than the synchroniser depth of margin inside the high phase, the sample is still within the valid window — which is the real reason Chapter 17.3 requires N_SAMP >= 2 rather than merely preferring it.

The engine itself is trivial to synthesise — four flops — and that is the payoff of §1. The block with no timing has no timing closure problem either.

On an ASIC, the pad's input filter adds to that latency and also removes the spikes Table 10's tSP describes, which is a genuine benefit here: without filtering, a glitch inside the high phase could be sampled as the received bit. The filter is why an I²C receiver does not need a digital majority vote per bit, and a design that adds one is duplicating the pad.

Reset behaviour is the other ASIC-relevant point. T12 exists because an engine reset mid-bit with a zero in flight must release — and the release must not wait for SCL to fall, because during reset there may be no clock activity at all to wait for. That is the one case where the deferred release of §4 must be bypassed, and reset is the signal that bypasses it.

10. Debugging — The Read That Ended in a STOP Nobody Asked For

Symptom

A master reads two bytes from a sensor. The first byte is correct. The transaction then ends early: the master reports success on one byte and the target, on the next transaction, behaves as though the previous transfer had been abandoned. A logic analyser decoding the bus shows START, address, ACK, data byte, ACK -- and then a STOP, followed by the master attempting to continue clocking into a bus it has already released.

Root Cause

The bit engine released SDA immediately on deactivation instead of deferring until SCL was low. Section 3.1.2 permits SDA to change only while SCL is LOW, and a rise while SCL is high is a STOP -- so an immediate release at the end of an acknowledge bit manufactures a STOP out of ordinary bookkeeping. The reason this only appeared on reads is structural: the master drives SDA in the acknowledge slot of a READ, so only there does its release produce a rising edge. On writes the master's SDA is already released and the release is a no-op. Nothing was wrong with the byte count, the framer or the target.

Fix
Defer the deactivation release until SCL reads low, which costs nothing -- the bus is between bit slots and the sampled value is already captured. Then note what must NOT be deferred: an arbitration abort, because section 3.1.8 requires the driver off 'the moment there is a difference', and that release is safe precisely because a loser is being out-driven so no edge results. The two releases are different and the block needs both. For the regression, test T11 drives a zero, deactivates while SCL is high, and asserts the line is still held; a test that only ever deactivates during the low phase cannot fail the original design.

Three generalisations.

A STOP was created by a block that cannot issue one. The framer's STOP counter read zero and was telling the truth. The bus does not care which block moved SDA — framing is defined by the transition, so any block with SDA ownership can manufacture a framing condition by accident. That is the cost of the shared ownership Chapter 17.4 made explicit, and the reason that chapter reports conflicts rather than resolving them silently.

Reads and writes are asymmetric in exactly one slot. The master drives SDA in the acknowledge slot of a read and not of a write. Any defect in release timing is therefore read-only, which sends the investigation toward the read path and the target rather than toward a shared block.

"Release" is not a single operation. It is two, with different timing rules and different justifications, and a block that implements one of them has a latent framing bug. §4.

11. Common Misconceptions

"The bit engine needs to know the phase timing." It needs two strobes. Every Table 10 number lives in the generator, which is why a mode change edits no line of this block. §1.

"The data-valid rule is something the engine checks." It is something the engine cannot violate: SDA is written at one place, inside the low phase. An invariant by construction costs less than one that is proved. §2.

"Receiving is a separate mode." It is transmitting ones and believing what comes back. tx_en low releases the line, which electrically is a one. §3.

"There is no point sampling while transmitting." The sample taken while transmitting is exactly what arbitration compares against. One sample point, two consumers. §3a.

"A released line reads high." Eventually, if nothing else holds it and the rise time has elapsed. The engine must not assume it — which is why 17.10 exists. §3.

"Release the line as soon as the byte is done." At the end of a read byte the master has just driven its acknowledge low, so releasing while SCL is high makes SDA rise in the high phase — a STOP. §4 and §10.

"Then always defer the release to the low phase." Not for an arbitration abort: §3.1.8 requires the driver off the moment a difference appears, and that release is safe because the winner is holding the line. §4.

"A STOP can only come from the framer." Framing is defined by the transition, not by which block produced it. Any block holding SDA can create one by accident. §10.

"If the bits are right, the engine is verified." Mutation M8 left every bit correct and stopped bit_done from ever firing — which would stall the byte engine while passing the whole suite. §7.

"A bit-level monitor can report the received byte." It can report a bit slot, its value and its direction. Position needs 17.7 and meaning needs 17.8. §8.

12. Reason It Through

Why does writing SDA at exactly one point make §3.1.2 structural rather than something to verify?

Because the one write point is inside the low phase, so no execution path exists on which SDA changes during SCL-high. Proving the rule otherwise requires knowing the phase, which means a second copy of the generator's state. §2.

A master is receiving. What is it transmitting, and why does that matter for arbitration?

Ones — releasing the line is electrically a transmitted one. It matters because "sent a one, read a zero" is then true of every zero the target returns, so the arbitration detector must be gated on whether the master is actually driving. §3.

Why does the engine sample the line during a bit it is transmitting?

Because that sample is what arbitration compares against. Skipping it leaves 17.10 with no evidence. §3a.

§3.1.2 says change SDA only while SCL is low; §3.1.8 says release immediately on losing arbitration. How is this not a contradiction?

Because a loser is being out-driven: the line is already low and stays low, so releasing produces no transition and §3.1.2 is not engaged. The rule constrains transitions, not drive enables. §4.

Why does an immediate release at the end of a byte create a STOP on reads but not on writes?

Because the master drives SDA in the acknowledge slot of a read, so its release makes the line rise. On a write the target drives that slot and the master's SDA is already released, so releasing changes nothing. §10.

Mutation M8 left every transmitted and received bit correct. Why was it still a serious defect?

It stopped bit_done from ever firing, which is the pulse the byte engine advances on. The bits were right and the layer above would never have progressed. §7.

Why is N_SAMP >= 2 a requirement rather than a preference, once an FPGA synchroniser is in the path?

Because the sampled value is the line as it was a synchroniser-depth earlier. The strobe must sit far enough into the high phase that the latched value still falls inside the valid window. §9.

13. Understanding Check

14. Summary

The bit engine contains no timing. It acts once at the drive point and once at the sample point, both supplied by 17.3, so a speed-mode change edits no line of it.

The data-valid rule is an invariant, not a check. SDA is written in exactly one place, inside the low phase, so there is no path on which it can change while SCL is high — and an invariant by construction is cheaper than one that must be proved.

Transmit and receive are the same logic differing by one bit. Receiving is transmitting ones and believing what comes back, which is also why a receiving master is not arbitrating.

The sample is taken even while transmitting, because that value is what arbitration compares against. One sample point, two consumers.

The block needs two different releases, because §3.1.2 and §3.1.8 are in tension: a deactivation release must wait for SCL to fall or it manufactures a STOP, and an abort release must be immediate and is safe because a loser is being out-driven.

That STOP is not hypothetical. At the end of a read byte the master has just driven its acknowledge low, so an immediate release there is exactly when a spurious STOP appears — and only on reads, because only there does the master drive the acknowledge slot.

Ten mutants, ten killed — but M8 survived first, leaving every bit correct while stopping bit_done from ever firing.

A signal a bench deliberately avoids depending on is a signal nobody is checking. The fix was an independent observer for the pulse, not a change to how the bench waits.

And a bit-level monitor can report a slot, a value and a direction — nothing more. Position belongs to the byte engine, meaning to the transaction controller, which is why an I²C verification environment layers its monitors instead of decoding transactions from pin wiggles.

15. What Comes Next

One bit works in both directions. Eight of them plus a ninth make a byte — and the ninth is not simply the ninth bit.

Chapter 17.7 builds the byte engine, and its subject is the ownership handoff. §3.1.4 requires the transmitter to release SDA during the acknowledge pulse so the receiver can pull it low, which means SDA changes hands exactly once per byte and back again at the start of the next. That is the first place 17.4's explicit ownership earns its keep, and the first place a master must generate an acknowledge of its own.

Continue learning

Related tutorials