Skip to content
VLSI Mentor

I²C · Module 17

The Byte Engine and the ACK Slot — Where SDA Changes Hands

A byte is nine bit slots and the ninth differs from the eight in exactly one respect: who owns SDA. Builds the ownership flip as a mirror image for reads and writes, keeps the inverted acknowledge polarity in one place, and shows why an engine that holds the line one slot too long is invisible to every per-byte check.

Chapter 17.6 built one bit and stopped there deliberately. This chapter builds eight of them — and then a ninth that is not simply the ninth bit.

1. Nine Slots, and Only One Thing Differs

So a byte is nine bit slots, and the ninth differs from the eight in exactly one respect: who owns SDA. The clock is the same, the sampling instant is the same, the data-valid rule is the same.

That is why this block wraps the bit engine rather than replacing it. Eight slots with the shift register driving, one slot with the direction reversed.

2. The Ownership Flip Is the Whole Block

DirectionSlots 0–7Slot 8
Writemaster drives from the shift registermaster releases, reads the target's answer
Readmaster releases, shifts in the target's bitsmaster drives its own ACK or NACK

The two cases are mirror images, and the code says so in one expression rather than as two paths that happen to agree:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
slot 8:   tx_en   <= ~dir_write            the direction reverses, whichever way it was
          cur_bit <= dir_write ? 1'b1      writing: release so the target can answer
                                : ~ack_to_send   reading: send our own answer

Writing it as one mirrored expression rather than two branches is worth more than it looks: two branches can drift apart under maintenance while continuing to pass tests that only exercise one of them.

3. MSB First, and Why the Shift Direction Has a Second Consequence

So the shift register shifts left and the bit transmitted is always bit 7.

There is a second consequence worth noting now because Chapter 17.10 depends on it: a partially transmitted byte is left-justified in the register. A byte abandoned mid-flight to arbitration loss therefore has its transmitted bits in the high positions and its untransmitted bits in the low ones, which is exactly what has to be reported when a master loses and must retry.

The engine also shifts in what was on the line — even while transmitting. While receiving that is the received data; while transmitting it is a record of what actually reached the bus, which is not necessarily what was intended. That is the same one-sample-two-consumers argument as 17.6 §3a, one layer up.

4. The ACK Polarity Is Inverted Relative to Intuition

The receiver pulls SDA LOW to acknowledge. So:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sampled ZERO  ->  ACK    (acknowledged)
sampled ONE   ->  NACK   (not acknowledged)

A sampled one is a NACK because nobody pulled the line down — a NACK is the absence of a response, not a signal in its own right. That is why a missing device and a refusing device are indistinguishable, which Module 7 established at protocol level.

This block reports ack as the protocol event — 1 means acknowledged — and keeps the inversion in one place, for exactly the reason Chapter 17.4 keeps the SDA inversion in one place. Two inversions in a design are one sign error waiting to happen; two inversions in different blocks are two.

5. A Write Byte and a Read Byte

Who drives SDA in each of the nine slots

9 cycles
Nine intervals, each one clock pulse of a byte. Two rows show ownership. On a write the master drives intervals zero through seven and releases interval eight, which the target drives to acknowledge. On a read the master releases intervals zero through seven, which the target drives, and the master drives interval eight to send its own acknowledge or not-acknowledge. A third row marks interval eight as the acknowledge slot in both cases.eight data slotseight data slotsthe ACK slotthe ACKslotMSB first — bit 7MSB first — bit 7the flip: SDA changes handsthe flip: SDA changes handswrite: drvMMMMMMMMTread: drvTTTTTTTTMslot01234567ACKt0t1t2t3t4t5t6t7t8
Figure 1 — the ownership of SDA across nine slots, both directions. The only difference between the two rows is where the boundary falls. Conceptual figure at slot resolution; each interval is one SCL period, not one system clock.

Read the two rows as one rule seen twice: the transmitter releases for slot 8. On a write the master is the transmitter; on a read the target is. Nothing else in the byte depends on direction.

6. The Byte Engine, in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_byte_engine.sv — eight bits, then the slot where SDA changes hands
   // -----------------------------------------------------------------------------
   // i2c_byte_engine.sv
   // Eight bits, then the ninth slot in which SDA changes hands.
   //
   // §3.1.4, verbatim: "The acknowledge takes place after every byte. ... The master
   // generates all clock pulses, including the acknowledge ninth clock pulse." And: "the
   // transmitter releases the SDA line during the acknowledge clock pulse so the receiver
   // can pull the SDA line LOW and it remains stable LOW during the HIGH period of this
   // clock pulse."
   //
   // So a byte is NINE bit slots and the ninth is different from the eight in exactly one
   // respect: who owns SDA. Everything else -- the clock, the sampling instant, the data
   // rule -- is identical. That is why this block wraps the bit engine rather than
   // replacing it: eight slots with the shift register driving, one slot with the
   // direction reversed.
   //
   // THE OWNERSHIP FLIP IS THE WHOLE BLOCK. Writing a byte:  the master drives bits 0-7
   // and RELEASES for bit 8, then reads the target's answer. Reading a byte: the master
   // releases for bits 0-7 and DRIVES bit 8, sending its own ACK or NACK. The two cases
   // are mirror images and the code says so, which is worth more than two separate paths
   // that happen to agree.
   //
   // MSB FIRST. §3.1.3: "Data is transferred with the Most Significant Bit (MSB) first."
   // So the shift register shifts LEFT and the bit transmitted is always bit 7, which also
   // means a partially transmitted byte is left-justified in the register -- relevant to
   // Chapter 17.10, where a byte abandoned to arbitration loss has to be reported.
   //
   // THE ACK POLARITY IS INVERTED RELATIVE TO INTUITION. The receiver pulls SDA LOW to
   // acknowledge, so a sampled ZERO is an ACK and a sampled ONE is a NACK. This block
   // reports `ack` as the protocol event (1 = acknowledged) and keeps the inversion in one
   // place, for the same reason Chapter 17.4 keeps the SDA inversion in one place.
   // -----------------------------------------------------------------------------

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

      // From the SCL generator, passed through to the bit engine.
      input  logic            drive_point,
      input  logic            sample_point,

      // Command. `go` starts one nine-slot byte.
      input  logic            go,
      input  logic            dir_write,    // 1 = master transmits the 8 bits and reads ACK
                                           // 0 = master receives the 8 bits and sends ACK
      input  logic [7:0]      tx_byte,
      input  logic            ack_to_send,  // when receiving: 1 = ACK (pull low), 0 = NACK

      input  logic            sda_in,
      input  logic            scl_high,     // SCL read back, for the bit engine's release rule
      input  logic            abort,        // arbitration lost: release SDA immediately

      output logic           sda_req,
      output logic           sda_bit,

      output logic [7:0]      rx_byte,
      output logic            ack,          // 1 = the byte WAS acknowledged
      output logic            ack_valid,    // one cycle, when the ninth slot completes
      output logic            byte_done,    // one cycle
      output logic            busy,
      output logic [3:0]      bit_index,    // 0..8
      output logic           driving,
      output logic [CNT_W-1:0] bytes_done,
      output logic [CNT_W-1:0] acks,
      output logic [CNT_W-1:0] nacks
   );

      logic [7:0] shreg;
      logic       active;
      logic       tx_en;
      logic       cur_bit;

      logic      rx_bit, rx_valid, bit_done;

      i2c_bit_engine #(.CNT_W(CNT_W)) u_bit (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .active(active), .tx_en(tx_en), .tx_bit(cur_bit), .abort(abort),
         .sda_in(sda_in), .scl_high(scl_high),
         .sda_req(sda_req), .sda_bit(sda_bit),
         .rx_bit(rx_bit), .rx_valid(rx_valid), .bit_done(bit_done),
         .driving(driving),
         .bits_driven(), .bits_sampled());

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            shreg      <= 8'h00;
            active     <= 1'b0;
            tx_en      <= 1'b0;
            cur_bit    <= 1'b1;
            rx_byte    <= 8'h00;
            ack        <= 1'b0;
            ack_valid  <= 1'b0;
            byte_done  <= 1'b0;
            busy       <= 1'b0;
            bit_index  <= 4'd0;
            bytes_done <= {CNT_W{1'b0}};
            acks       <= {CNT_W{1'b0}};
            nacks      <= {CNT_W{1'b0}};
         end else begin
            ack_valid <= 1'b0;
            byte_done <= 1'b0;

            if (abort) begin
               // §3.1.8 obligation 2, propagated up: the byte is over. The partially
               // transmitted value is left in the shift register, left-justified, because
               // §3.1.3's MSB-first order means the bits that DID reach the bus are the
               // high ones -- and Chapter 17.10 has to report how far it got.
               busy      <= 1'b0;
               active    <= 1'b0;
               tx_en     <= 1'b0;
               bit_index <= 4'd0;
            end else if (go && !busy) begin
               busy      <= 1'b1;
               active    <= 1'b1;
               bit_index <= 4'd0;
               shreg     <= tx_byte;
               // Slot 0: for a write the master drives the MSB; for a read it releases.
               tx_en     <= dir_write;
               cur_bit   <= dir_write ? tx_byte[7] : 1'b1;
            end else if (busy) begin
               // One slot completes at each sample point.
               if (bit_done) begin
                  if (bit_index < 4'd8) begin
                     // A data slot. Shift in what was on the line -- which, when
                     // transmitting, is what WE put there, and the shift register's low
                     // bits are therefore a record of what actually reached the bus.
                     shreg <= {shreg[6:0], rx_bit};
                     if (bit_index == 4'd7) begin
                        rx_byte   <= {shreg[6:0], rx_bit};
                        // THE FLIP. Slot 8 reverses the direction of SDA: a transmitting
                        // master releases so the target can answer, and a receiving
                        // master drives its own answer.
                        tx_en     <= ~dir_write;
                        cur_bit   <= dir_write ? 1'b1 : ~ack_to_send;
                        bit_index <= 4'd8;
                     end else begin
                        bit_index <= bit_index + 4'd1;
                        tx_en     <= dir_write;
                        cur_bit   <= dir_write ? shreg[6] : 1'b1;
                     end
                  end else begin
                     // Slot 8 has completed. When writing, the sampled value IS the
                     // target's answer, and a LOW means acknowledged. When reading, the
                     // master sent the answer, so the event is what it chose to send.
                     ack        <= dir_write ? ~rx_bit : ack_to_send;
                     ack_valid  <= 1'b1;
                     if (dir_write ? ~rx_bit : ack_to_send) acks  <= acks  + 1'b1;
                     else                                   nacks <= nacks + 1'b1;
                     byte_done  <= 1'b1;
                     bytes_done <= bytes_done + 1'b1;
                     busy       <= 1'b0;
                     active     <= 1'b0;
                     tx_en      <= 1'b0;
                     bit_index  <= 4'd0;
                  end
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_byte_engine.v — the same design in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_byte_engine.sv
   // Eight bits, then the ninth slot in which SDA changes hands.
   //
   // §3.1.4, verbatim: "The acknowledge takes place after every byte. ... The master
   // generates all clock pulses, including the acknowledge ninth clock pulse." And: "the
   // transmitter releases the SDA line during the acknowledge clock pulse so the receiver
   // can pull the SDA line LOW and it remains stable LOW during the HIGH period of this
   // clock pulse."
   //
   // So a byte is NINE bit slots and the ninth is different from the eight in exactly one
   // respect: who owns SDA. Everything else -- the clock, the sampling instant, the data
   // rule -- is identical. That is why this block wraps the bit engine rather than
   // replacing it: eight slots with the shift register driving, one slot with the
   // direction reversed.
   //
   // THE OWNERSHIP FLIP IS THE WHOLE BLOCK. Writing a byte:  the master drives bits 0-7
   // and RELEASES for bit 8, then reads the target's answer. Reading a byte: the master
   // releases for bits 0-7 and DRIVES bit 8, sending its own ACK or NACK. The two cases
   // are mirror images and the code says so, which is worth more than two separate paths
   // that happen to agree.
   //
   // MSB FIRST. §3.1.3: "Data is transferred with the Most Significant Bit (MSB) first."
   // So the shift register shifts LEFT and the bit transmitted is always bit 7, which also
   // means a partially transmitted byte is left-justified in the register -- relevant to
   // Chapter 17.10, where a byte abandoned to arbitration loss has to be reported.
   //
   // THE ACK POLARITY IS INVERTED RELATIVE TO INTUITION. The receiver pulls SDA LOW to
   // acknowledge, so a sampled ZERO is an ACK and a sampled ONE is a NACK. This block
   // reports `ack` as the protocol event (1 = acknowledged) and keeps the inversion in one
   // place, for the same reason Chapter 17.4 keeps the SDA inversion in one place.
   // -----------------------------------------------------------------------------

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

      // From the SCL generator, passed through to the bit engine.
      input  wire            drive_point,
      input  wire            sample_point,

      // Command. `go` starts one nine-slot byte.
      input  wire            go,
      input  wire            dir_write,    // 1 = master transmits the 8 bits and reads ACK
                                           // 0 = master receives the 8 bits and sends ACK
      input  wire [7:0]      tx_byte,
      input  wire            ack_to_send,  // when receiving: 1 = ACK (pull low), 0 = NACK

      input  wire            sda_in,
      input  wire            scl_high,     // SCL read back, for the bit engine's release rule
      input  wire            abort,        // arbitration lost: release SDA immediately

      output wire            sda_req,
      output wire            sda_bit,

      output reg  [7:0]      rx_byte,
      output reg             ack,          // 1 = the byte WAS acknowledged
      output reg             ack_valid,    // one cycle, when the ninth slot completes
      output reg             byte_done,    // one cycle
      output reg             busy,
      output reg  [3:0]      bit_index,    // 0..8
      output wire            driving,
      output reg  [CNT_W-1:0] bytes_done,
      output reg  [CNT_W-1:0] acks,
      output reg  [CNT_W-1:0] nacks
   );

      reg [7:0] shreg;
      reg       active;
      reg       tx_en;
      reg       cur_bit;

      wire      rx_bit, rx_valid, bit_done;

      i2c_bit_engine #(.CNT_W(CNT_W)) u_bit (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .active(active), .tx_en(tx_en), .tx_bit(cur_bit), .abort(abort),
         .sda_in(sda_in), .scl_high(scl_high),
         .sda_req(sda_req), .sda_bit(sda_bit),
         .rx_bit(rx_bit), .rx_valid(rx_valid), .bit_done(bit_done),
         .driving(driving),
         .bits_driven(), .bits_sampled());

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            shreg      <= 8'h00;
            active     <= 1'b0;
            tx_en      <= 1'b0;
            cur_bit    <= 1'b1;
            rx_byte    <= 8'h00;
            ack        <= 1'b0;
            ack_valid  <= 1'b0;
            byte_done  <= 1'b0;
            busy       <= 1'b0;
            bit_index  <= 4'd0;
            bytes_done <= {CNT_W{1'b0}};
            acks       <= {CNT_W{1'b0}};
            nacks      <= {CNT_W{1'b0}};
         end else begin
            ack_valid <= 1'b0;
            byte_done <= 1'b0;

            if (abort) begin
               // §3.1.8 obligation 2, propagated up: the byte is over. The partially
               // transmitted value is left in the shift register, left-justified, because
               // §3.1.3's MSB-first order means the bits that DID reach the bus are the
               // high ones -- and Chapter 17.10 has to report how far it got.
               busy      <= 1'b0;
               active    <= 1'b0;
               tx_en     <= 1'b0;
               bit_index <= 4'd0;
            end else if (go && !busy) begin
               busy      <= 1'b1;
               active    <= 1'b1;
               bit_index <= 4'd0;
               shreg     <= tx_byte;
               // Slot 0: for a write the master drives the MSB; for a read it releases.
               tx_en     <= dir_write;
               cur_bit   <= dir_write ? tx_byte[7] : 1'b1;
            end else if (busy) begin
               // One slot completes at each sample point.
               if (bit_done) begin
                  if (bit_index < 4'd8) begin
                     // A data slot. Shift in what was on the line -- which, when
                     // transmitting, is what WE put there, and the shift register's low
                     // bits are therefore a record of what actually reached the bus.
                     shreg <= {shreg[6:0], rx_bit};
                     if (bit_index == 4'd7) begin
                        rx_byte   <= {shreg[6:0], rx_bit};
                        // THE FLIP. Slot 8 reverses the direction of SDA: a transmitting
                        // master releases so the target can answer, and a receiving
                        // master drives its own answer.
                        tx_en     <= ~dir_write;
                        cur_bit   <= dir_write ? 1'b1 : ~ack_to_send;
                        bit_index <= 4'd8;
                     end else begin
                        bit_index <= bit_index + 4'd1;
                        tx_en     <= dir_write;
                        cur_bit   <= dir_write ? shreg[6] : 1'b1;
                     end
                  end else begin
                     // Slot 8 has completed. When writing, the sampled value IS the
                     // target's answer, and a LOW means acknowledged. When reading, the
                     // master sent the answer, so the event is what it chose to send.
                     ack        <= dir_write ? ~rx_bit : ack_to_send;
                     ack_valid  <= 1'b1;
                     if (dir_write ? ~rx_bit : ack_to_send) acks  <= acks  + 1'b1;
                     else                                   nacks <= nacks + 1'b1;
                     byte_done  <= 1'b1;
                     bytes_done <= bytes_done + 1'b1;
                     busy       <= 1'b0;
                     active     <= 1'b0;
                     tx_en      <= 1'b0;
                     bit_index  <= 4'd0;
                  end
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_byte_engine.vhd — the same design in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_byte_engine.vhd
   -- Eight bits, then the ninth slot in which SDA changes hands.
   -- Behavioural twin of i2c_byte_engine.sv / .v.
   --
   -- §3.1.4, verbatim: "The acknowledge takes place after every byte. ... The master generates
   -- all clock pulses, including the acknowledge ninth clock pulse." And: "the transmitter
   -- releases the SDA line during the acknowledge clock pulse so the receiver can pull the SDA
   -- line LOW and it remains stable LOW during the HIGH period of this clock pulse."
   --
   -- So a byte is NINE bit slots and the ninth differs from the eight in exactly one respect:
   -- who owns SDA. Everything else -- the clock, the sampling instant, the data rule -- is
   -- identical, which is why this block WRAPS the bit engine rather than replacing it.
   --
   -- THE OWNERSHIP FLIP IS THE WHOLE BLOCK. Writing a byte: the master drives bits 0-7 and
   -- RELEASES for bit 8, then reads the target's answer. Reading: the master releases for bits
   -- 0-7 and DRIVES bit 8, sending its own ACK or NACK. Mirror images, and the code says so.
   --
   -- MSB FIRST. §3.1.3: "Data is transferred with the Most Significant Bit (MSB) first."
   --
   -- AND THE ACK POLARITY IS INVERTED RELATIVE TO INTUITION: the receiver pulls SDA LOW to
   -- acknowledge, so a sampled ZERO is an ACK. This block reports `ack` as the protocol event
   -- (1 = acknowledged) and keeps the inversion in one place.
   -- ---------------------------------------------------------------------------

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

   entity i2c_byte_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;

         go          : in std_logic;
         dir_write   : in std_logic;   -- '1' master transmits the 8 bits and reads the ACK
         tx_byte     : in std_logic_vector(7 downto 0);
         ack_to_send : in std_logic;   -- when receiving: '1' = ACK (pull low), '0' = NACK

         sda_in   : in std_logic;
         scl_high : in std_logic;
         abort    : in std_logic;

         sda_req : out std_logic;
         sda_bit : out std_logic;

         rx_byte    : out std_logic_vector(7 downto 0);
         ack        : out std_logic;   -- '1' = the byte WAS acknowledged
         ack_valid  : out std_logic;
         byte_done  : out std_logic;
         busy       : out std_logic;
         bit_index  : out unsigned(3 downto 0);
         driving    : out std_logic;
         bytes_done : out unsigned(CNT_W-1 downto 0);
         acks       : out unsigned(CNT_W-1 downto 0);
         nacks      : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_byte_engine;

   architecture rtl of i2c_byte_engine is

      signal shreg   : std_logic_vector(7 downto 0) := (others => '0');
      signal act     : std_logic := '0';
      signal txen    : std_logic := '0';
      signal cur_bit : std_logic := '1';
      signal bsy     : std_logic := '0';
      signal bidx    : unsigned(3 downto 0) := (others => '0');
      signal n_byt, n_ack, n_nak : unsigned(CNT_W-1 downto 0) := (others => '0');

      signal rx_bit, rx_valid, bit_dn : std_logic;

   begin

      busy       <= bsy;
      bit_index  <= bidx;
      bytes_done <= n_byt;
      acks       <= n_ack;
      nacks      <= n_nak;

      u_bit : entity work.i2c_bit_engine
         generic map (CNT_W => CNT_W)
         port map (clk => clk, rst_n => rst_n,
            drive_point => drive_point, sample_point => sample_point,
            active => act, tx_en => txen, tx_bit => cur_bit, abort => abort,
            sda_in => sda_in, scl_high => scl_high,
            sda_req => sda_req, sda_bit => sda_bit,
            rx_bit => rx_bit, rx_valid => rx_valid, bit_done => bit_dn,
            driving => driving,
            bits_driven => open, bits_sampled => open);

      process (clk, rst_n)
         variable acked : std_logic;
      begin
         if rst_n = '0' then
            shreg     <= (others => '0');
            act       <= '0';
            txen      <= '0';
            cur_bit   <= '1';
            rx_byte   <= (others => '0');
            ack       <= '0';
            ack_valid <= '0';
            byte_done <= '0';
            bsy       <= '0';
            bidx      <= (others => '0');
            n_byt     <= (others => '0');
            n_ack     <= (others => '0');
            n_nak     <= (others => '0');
         elsif rising_edge(clk) then
            ack_valid <= '0';
            byte_done <= '0';

            if abort = '1' then
               -- §3.1.8 obligation 2, propagated up: the byte is over. The partially
               -- transmitted value is left in the shift register, left-justified, because
               -- §3.1.3's MSB-first order means the bits that DID reach the bus are the high
               -- ones -- and Chapter 17.10 has to report how far it got.
               bsy  <= '0';
               act  <= '0';
               txen <= '0';
               bidx <= (others => '0');
            elsif go = '1' and bsy = '0' then
               bsy   <= '1';
               act   <= '1';
               bidx  <= (others => '0');
               shreg <= tx_byte;
               -- Slot 0: for a write the master drives the MSB; for a read it releases.
               txen <= dir_write;
               if dir_write = '1' then cur_bit <= tx_byte(7); else cur_bit <= '1'; end if;
            elsif bsy = '1' then
               -- One slot completes at each sample point.
               if bit_dn = '1' then
                  if bidx < 8 then
                     -- A data slot. Shift in what was on the line -- which, when transmitting,
                     -- is what WE put there, so the shift register's low bits are a record of
                     -- what actually reached the bus.
                     shreg <= shreg(6 downto 0) & rx_bit;
                     if bidx = 7 then
                        rx_byte <= shreg(6 downto 0) & rx_bit;
                        -- THE FLIP. Slot 8 reverses the direction of SDA.
                        txen <= not dir_write;
                        if dir_write = '1' then cur_bit <= '1';
                        else                    cur_bit <= not ack_to_send;
                        end if;
                        bidx <= to_unsigned(8, 4);
                     else
                        bidx <= bidx + 1;
                        txen <= dir_write;
                        if dir_write = '1' then cur_bit <= shreg(6); else cur_bit <= '1'; end if;
                     end if;
                  else
                     -- Slot 8 has completed. When writing, the sampled value IS the target's
                     -- answer and a LOW means acknowledged. When reading, the master sent the
                     -- answer, so the event is what it chose to send.
                     if dir_write = '1' then acked := not rx_bit; else acked := ack_to_send; end if;
                     ack       <= acked;
                     ack_valid <= '1';
                     if acked = '1' then n_ack <= n_ack + 1; else n_nak <= n_nak + 1; end if;
                     byte_done <= '1';
                     n_byt     <= n_byt + 1;
                     bsy       <= '0';
                     act       <= '0';
                     txen      <= '0';
                     bidx      <= (others => '0');
                  end if;
               end if;
            end if;
         end if;
      end process;

   end architecture rtl;

6a. The testbenches

Thirteen checks. The bench instantiates a real target model that finds its own edges from the wires, rather than a responder told when to reply. That distinction matters: a target that is scripted to acknowledge at slot 8 will acknowledge even if the master's ninth pulse never happens, and mutation M1 below removes exactly that pulse.

#TestProperty
T1an address byte, acknowledged by a real targetwhich found its own edges
T2nine slots, not eight§3.1.4's ninth clock pulse
T3a data byte written and read back out of the target's register
T4the ownership flip, writingmaster drives 0–7, releases slot 8
T5a readmaster releases 0–7, drives slot 8
T6the mirror image, checked in the ninth slot
T7ACK polarity is inverteda sampled zero is an acknowledge
T8a NACK from the master ends the read, and the target lets go
T9a NACK from the target on a wrong address
T10MSB firstan asymmetric byte proves order; 0x80 proves the top bit
T11the data-valid rule held throughoutover every byte so far
T12a complete transaction, framedSTART, address, data, STOP
T13the engine let go of SDA between bytesadded after M10; see §7
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_byte_engine_tb.sv — the self-checking testbench
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_byte_engine_tb.sv
   // Independent oracle for i2c_byte_engine, integrated against a pin-level target.
   //
   // This bench assembles everything built so far -- the SCL generator, the SDA owner and
   // inverter, the framer, the bit engine and the byte engine -- and puts a
   // i2c_target_model on the other end of a wired-AND bus. The target is driven by nothing
   // but the two wires: it finds its own edges, samples where §3.1.2 permits sampling and
   // drives where §3.1.2 permits driving.
   //
   // That is the point. A byte-level model handed "a byte arrived" cannot be wrong about
   // WHEN, and so cannot catch a master that is right about every byte and wrong about
   // every instant. This one can, and it does not know what the master's states are called.
   //
   // It is also where the SCL HANDOVER is exercised for the first time. Three blocks want
   // to drive SCL at different moments -- the framer during START and STOP, the generator
   // during bytes -- and the bench's `own_scl` task performs the overlapping handoff that
   // Chapter 17.12 builds into the master.
   // -----------------------------------------------------------------------------
   module i2c_byte_engine_tb;

      localparam integer NL = 9, NH = 5, NSU = 3, NSMP = 2;
      localparam integer NHD = 4, NSUA = 4, NSUO = 4, NBUF = 4;
      localparam [6:0]   TADDR = 7'h50;

      logic clk = 1'b0, rst_n = 1'b0;

      // ---- the master's pieces ------------------------------------------------
      logic  gen_idle_low = 1'b0, scl_yield = 1'b0;
      logic  do_start = 1'b0, do_restart = 1'b0, do_stop = 1'b0;
      logic  go = 1'b0, dir_write = 1'b1, ack_to_send = 1'b1;
      logic  [7:0] tx_byte = 8'h00;

      logic g_scl_low, drive_point, sample_point, g_rise, g_fall, g_stretch;
      logic [15:0] g_stretch_cyc, g_bits;
      logic [1:0]  g_phase;

      logic f_sda_req, f_sda_bit, f_scl_low, f_busy, f_done, f_bus_free, f_started, f_sw;
      logic [15:0] n_sta, n_rs, n_sto;
      logic [3:0]  f_state;

      logic b_sda_req, b_sda_bit, b_driving;
      logic [7:0]  rx_byte;
      logic        ack, ack_valid, byte_done, b_busy;
      logic [3:0]  bit_index;
      logic [15:0] bytes_done, n_acks, n_nacks;

      wire [3:0] req     = {2'b00, b_sda_req, f_sda_req};
      wire [3:0] bit_val = {2'b00, b_sda_bit, f_sda_bit};

      logic m_sda_low, sda_owned, sda_tx, arb_now, arb_lost, sda_conflict;
      logic [3:0] grant;
      logic [15:0] n_conflicts, n_arb;

      // THE CLOCK RUNS ONLY WHILE A BYTE IS IN FLIGHT. A generator left enabled between
      // bytes keeps pulsing SCL, and every pulse is a bit slot the target counts and the
      // master does not -- so the two fall out of step and the next byte read comes back
      // as all ones. So the enable is derived from the byte engine's own activity, which
      // is what the controller of Chapter 17.12 does.
      wire gen_enable = scl_yield && (b_busy || go);

      // The master's SCL contribution: the framer and the generator, wired-AND locally.
      wire m_scl_low = f_scl_low | g_scl_low;

      // ---- the bus and the target --------------------------------------------
      logic t_scl_low, t_sda_low;
      logic scl, sda;
      logic [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
      logic [7:0] scl_h, sda_h;

      logic  load_en = 1'b0;
      logic  [7:0] load_addr = 8'h00, load_data = 8'h00;

      i2c_line_model #(.N_DEV(2)) bus (
         .scl_drive_low({t_scl_low, m_scl_low}),
         .sda_drive_low({t_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(gen_enable), .idle_low(gen_idle_low),
         .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_stretch_cyc),
         .bits_generated(g_bits), .phase(g_phase));

      i2c_framer #(.N_HD_STA(NHD), .N_SU_STA(NSUA), .N_SU_STO(NSUO),
                   .N_BUF(NBUF), .N_SU_DAT(NSU), .CNT_W(16)) u_fr (
         .clk(clk), .rst_n(rst_n),
         .do_start(do_start), .do_restart(do_restart), .do_stop(do_stop),
         .scl_in(scl_in[0]), .sda_in(sda_in[0]), .scl_yield(scl_yield),
         .sda_req(f_sda_req), .sda_bit(f_sda_bit), .scl_drive_low(f_scl_low),
         .busy(f_busy), .done(f_done), .bus_free(f_bus_free), .started(f_started),
         .stretch_wait(f_sw), .starts(n_sta), .restarts(n_rs), .stops(n_sto),
         .state(f_state));

      i2c_byte_engine #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .go(go), .dir_write(dir_write), .tx_byte(tx_byte), .ack_to_send(ack_to_send),
         .sda_in(sda_in[0]), .scl_high(scl_in[0]), .abort(1'b0),
         .sda_req(b_sda_req), .sda_bit(b_sda_bit),
         .rx_byte(rx_byte), .ack(ack), .ack_valid(ack_valid), .byte_done(byte_done),
         .busy(b_busy), .bit_index(bit_index), .driving(b_driving),
         .bytes_done(bytes_done), .acks(n_acks), .nacks(n_nacks));

      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(b_driving),
         .sda_drive_low(m_sda_low),
         .grant(grant), .owned(sda_owned), .tx_bit(sda_tx),
         .owner_conflict(sda_conflict), .conflicts(n_conflicts),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(n_arb),
         .arb_clear(1'b0));

      logic t_sel, t_dirrd, t_wvalid;
      logic [7:0] t_lastwr;
      logic [15:0] t_rx, t_tx, t_nsta, t_nsto;
      logic [2:0]  t_state;

      i2c_target_model #(.MY_ADDR(TADDR), .ACK_ADDR(1'b1), .STRETCH_AFTER(0),
                         .NACK_AT(0), .N_MEM(16), .CNT_W(16)) u_tgt (
         .clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
         .scl_drive_low(t_scl_low), .sda_drive_low(t_sda_low),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .selected(t_sel), .dir_read(t_dirrd), .last_written(t_lastwr),
         .write_valid(t_wvalid), .bytes_rx(t_rx), .bytes_tx(t_tx),
         .n_starts(t_nsta), .n_stops(t_nsto), .state(t_state));

      // ---- the oracle ---------------------------------------------------------
      logic m_start, m_stop, m_bitv, m_byte, m_ackv, m_intr, m_mid, m_bit, m_ack;
      logic [7:0] m_byteval;
      logic [3:0] m_bidx;
      logic [15:0] m_nsta, m_nsto, m_nbyte, m_nmid;

      i2c_proto_mon #(.CNT_W(16)) mon (
         .clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
         .start_seen(m_start), .stop_seen(m_stop),
         .bit_seen(m_bit), .bit_val(m_bitv),
         .byte_seen(m_byte), .byte_val(m_byteval),
         .ack_seen(m_ack), .ack_val(m_ackv),
         .in_transfer(m_intr), .framing_midbyte(m_mid), .bit_index(m_bidx),
         .n_starts(m_nsta), .n_stops(m_nsto), .n_bytes(m_nbyte), .n_midbyte(m_nmid));

      always #5 clk = ~clk;

      integer errors = 0;
      integer n, k;
      logic [7:0] got;

      // §3.1.2 watchdog: SDA may only change while SCL is low, EXCEPT at framing.
      integer bad_sda_change;
      logic scl_l, sda_l;
      always @(negedge clk) begin
         if (rst_n) begin
            if (scl && scl_l && (sda != sda_l) && !m_start && !m_stop) begin
               // A change while SCL is high that the monitor is not about to call framing
               // is impossible -- so this counts the ones that are not at a byte boundary
               // either, which is what a corrupted bit would look like.
               if (m_bidx != 4'd0) bad_sda_change = bad_sda_change + 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;
            gen_idle_low = 1'b0; scl_yield = 1'b0;
            do_start = 1'b0; do_restart = 1'b0; do_stop = 1'b0;
            go = 1'b0; dir_write = 1'b1; ack_to_send = 1'b1; tx_byte = 8'h00;
            load_en = 1'b0;
            bad_sda_change = 0; scl_l = 1'b1; sda_l = 1'b1;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task preload (input [7:0] a, input [7:0] d);
         begin
            @(negedge clk); load_en = 1'b1; load_addr = a; load_data = d;
            @(posedge clk); @(negedge clk); load_en = 1'b0;
         end
      endtask

      task wait_frame (input integer max_cycles);
         begin
            n = 0;
            while (!f_done && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_frame: framer stuck in state %0d", f_state);
               errors = errors + 1;
            end
         end
      endtask

      // THE HANDOVER, in both directions. Asserting `scl_yield` and `gen_enable` in the
      // same cycle means the framer releases SCL and the generator drives it low at the
      // same clock edge, so the line never rises in between. Reversing it takes the clock
      // back while the generator is still parked low, so again no gap appears.
      task clock_to_generator;
         begin @(negedge clk); scl_yield = 1'b1; gen_idle_low = 1'b1; end
      endtask

      task clock_to_framer;
         begin
            // The generator is already parked low between bytes, so taking the clock back
            // is just a matter of the framer picking up the hold before the generator lets
            // go -- in that order, so the line never rises in between.
            n = 0;
            while (g_phase != 2'd0 && n < 200) begin step; n = n + 1; end
            @(negedge clk); scl_yield = 1'b0;   // the framer holds SCL low now
            step;
            @(negedge clk); gen_idle_low = 1'b0; // and the generator lets go
         end
      endtask

      // Advance to the ninth slot AND past its drive point, which is where ownership has
      // actually changed hands. Checking at the instant bit_index becomes 8 is too early:
      // the flip happens at the drive point inside slot 8's low phase, and until then
      // `driving` still reflects slot 7.
      task to_ack_slot;
         begin
            n = 0;
            while (bit_index != 4'd8 && n < 800) begin step; n = n + 1; end
            n = 0;
            while (!drive_point && n < 800) begin step; n = n + 1; end
            step;
         end
      endtask

      task send_byte (input [7:0] b);
         begin
            @(negedge clk); tx_byte = b; dir_write = 1'b1; go = 1'b1;
            @(posedge clk); @(negedge clk); go = 1'b0;
            n = 0;
            while (!byte_done && n < 800) begin step; n = n + 1; end
            if (n >= 800) begin
               $display("  FAIL send_byte(%02h): never completed (bit %0d)", b, bit_index);
               errors = errors + 1;
            end
         end
      endtask

      task recv_byte (input do_ack);
         begin
            @(negedge clk); dir_write = 1'b0; ack_to_send = do_ack; go = 1'b1;
            @(posedge clk); @(negedge clk); go = 1'b0;
            n = 0;
            while (!byte_done && n < 800) begin step; n = n + 1; end
            if (n >= 800) begin
               $display("  FAIL recv_byte: never completed (bit %0d)", bit_index);
               errors = errors + 1;
            end
            got = rx_byte;
         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 (0x%0h) expected %0d (0x%0h)", what, g, g, e, 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

      // ---- the engine must let go of SDA between bytes ------------------------
      //
      // A byte engine that finishes a byte and keeps requesting SDA holds the line into
      // the gap before the next byte. That is invisible in the per-byte checks: the next
      // byte's `go` overwrites the request, the gap sits inside the SCL low phase, and if
      // the held value is low it even helps a STOP that may follow. But the master is
      // driving a line it no longer owns, and the next block that wants SDA -- the framer,
      // for a repeated START -- collides with it.
      //
      // The window has to be stated carefully, because the bit engine's DEFERRED RELEASE
      // is correct and required: after deactivation it keeps holding SDA until SCL is low,
      // so that releasing cannot make the line rise during the high phase and manufacture
      // a STOP. So `sda_req` staying high immediately after `byte_done` is not a defect.
      //
      // The real invariant is: once the byte is done AND SCL has actually been observed
      // low, the request must be gone.
      integer held_after_byte = 0;
      reg     between_bytes = 1'b0;
      reg     seen_low = 1'b0;
      always @(posedge clk) begin
         if (!rst_n) begin
            held_after_byte <= 0; between_bytes <= 1'b0; seen_low <= 1'b0;
         end else begin
            if (byte_done)  begin between_bytes <= 1'b1; seen_low <= 1'b0; end
            else if (go)          between_bytes <= 1'b0;
            if (between_bytes && !scl_in[0]) seen_low <= 1'b1;
            if (between_bytes && seen_low && !go && b_sda_req)
               held_after_byte <= held_after_byte + 1;
         end
      end

      initial begin
         $display("=== i2c_byte_engine: eight bits, then the slot where SDA changes hands ===");

         // ----------------------------------------------------------------
         // T1. An address byte, acknowledged by a real target that found its own edges.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b0});
         $display("T1  an address byte, acknowledged by a pin-level target");
         ck_bit("T1 acknowledged", ack, 1'b1);
         ck_bit("T1 the target selected itself", t_sel, 1'b1);
         ck_int("T1 the monitor saw one byte", m_nbyte, 1);
         ck_int("T1 and it was the address with the write bit", m_byteval, {TADDR, 1'b0});
         // Eight, not nine: the generator counts a bit when its HIGH phase ends, and the
         // byte completes at the SAMPLE point inside the ninth high phase. So at the
         // instant the byte is done, the ninth pulse is still in progress.
         ck_int("T1 eight pulses completed, with the ninth still in flight", g_bits, 8);

         // ----------------------------------------------------------------
         // T2. NINE SLOTS, NOT EIGHT. §3.1.4: "The master generates all clock pulses,
         //     including the acknowledge ninth clock pulse." A byte costs nine.
         // ----------------------------------------------------------------
         $display("T2  a byte costs nine clock pulses, not eight");
         ck_int("T2 the bit index returned to zero", bit_index, 0);
         ck_int("T2 one byte completed", bytes_done, 1);

         // ----------------------------------------------------------------
         // T3. A data byte, written and read back out of the target's own register.
         // ----------------------------------------------------------------
         send_byte(8'h5A);
         $display("T3  a data byte reaches the target and is acknowledged");
         ck_bit("T3 acknowledged", ack, 1'b1);
         ck_int("T3 the target received it", t_lastwr, 8'h5A);
         ck_int("T3 and counted it", t_rx, 1);
         ck_int("T3 the monitor agrees", m_byteval, 8'h5A);

         // ----------------------------------------------------------------
         // T4. THE OWNERSHIP FLIP, when writing. The master drives slots 0-7 and RELEASES
         //     for slot 8 so the target can answer. §3.1.4, and the test looks at the
         //     master's own driving signal in the ninth slot.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         @(negedge clk); tx_byte = {TADDR, 1'b0}; dir_write = 1'b1; go = 1'b1;
         @(posedge clk); @(negedge clk); go = 1'b0;
         to_ack_slot;
         $display("T4  writing: the master releases SDA for the ninth slot");
         ck_int("T4 in the ninth slot", bit_index, 8);
         ck_bit("T4 the master is no longer driving", b_driving, 1'b0);
         n = 0;
         while (!byte_done && n < 800) begin step; n = n + 1; end
         ck_bit("T4 and the target's ACK was read", ack, 1'b1);

         // ----------------------------------------------------------------
         // T5. A READ. The master releases slots 0-7 and DRIVES slot 8 -- the mirror
         //     image. The byte comes from the target's memory.
         // ----------------------------------------------------------------
         do_reset;
         preload(8'h00, 8'hC3);
         preload(8'h01, 8'h7E);
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b1});                 // address with the read bit
         ck_bit("T5 the read address was acknowledged", ack, 1'b1);
         ck_bit("T5 the target knows it is a read", t_dirrd, 1'b1);
         recv_byte(1'b1);                          // ACK: send me another
         $display("T5  reading: the master releases the data slots and drives the ninth");
         ck_int("T5 the first byte came from the target", got, 8'hC3);
         ck_int("T5 the target counted a transmit", t_tx, 1);

         // ----------------------------------------------------------------
         // T6. THE MIRROR IMAGE, checked in the ninth slot. When reading, the master
         //     drives slot 8 with its own answer.
         // ----------------------------------------------------------------
         @(negedge clk); dir_write = 1'b0; ack_to_send = 1'b1; go = 1'b1;
         @(posedge clk); @(negedge clk); go = 1'b0;
         to_ack_slot;
         $display("T6  reading: the master drives the ninth slot with its own answer");
         ck_int("T6 in the ninth slot", bit_index, 8);
         ck_bit("T6 and now the master IS driving", b_driving, 1'b1);
         n = 0;
         while (!byte_done && n < 800) begin step; n = n + 1; end
         ck_int("T6 the second byte arrived", rx_byte, 8'h7E);

         // ----------------------------------------------------------------
         // T7. ACK POLARITY IS INVERTED. The receiver pulls SDA LOW to acknowledge, so
         //     the monitor -- which reads the line -- sees a ZERO for an ACK.
         // ----------------------------------------------------------------
         $display("T7  an acknowledge is a LOW on the wire, the inverse of intuition");
         ck_bit("T7 the wire carried a zero in the ack slot", m_ackv, 1'b0);
         ck_bit("T7 and the engine reports it as acknowledged", ack, 1'b1);

         // ----------------------------------------------------------------
         // T8. A NACK from the master ends the read, and the target lets go.
         // ----------------------------------------------------------------
         recv_byte(1'b0);                          // NACK: I am finished
         $display("T8  the master's NACK ends the read and the target releases SDA");
         ck_bit("T8 the engine reports a NACK", ack, 1'b0);
         ck_bit("T8 the wire carried a one in the ack slot", m_ackv, 1'b1);
         ck_bit("T8 the target has released SDA", t_sda_low, 1'b0);
         ck_int("T8 the NACK was counted", n_nacks, 1);

         // ----------------------------------------------------------------
         // T9. A NACK from the TARGET, on a wrong address. The address is not
         //     acknowledged, and the engine must report that rather than proceeding.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({7'h51, 1'b0});                 // not our target
         $display("T9  an unaddressed target does not answer, and the engine says so");
         ck_bit("T9 not acknowledged", ack, 1'b0);
         ck_bit("T9 the target did not select itself", t_sel, 1'b0);
         ck_int("T9 the monitor saw the byte all the same", m_nbyte, 1);
         ck_bit("T9 and the ack slot carried a one", m_ackv, 1'b1);

         // ----------------------------------------------------------------
         // T10. MSB FIRST. §3.1.3. An asymmetric byte proves the order, and 0x80 proves
         //      it in the strongest way: only the first bit is set.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b0});
         send_byte(8'h80);
         $display("T10 bits go out most significant first");
         ck_int("T10 the target received 0x80", t_lastwr, 8'h80);
         ck_int("T10 and the monitor read 0x80 off the wire", m_byteval, 8'h80);
         send_byte(8'h01);
         ck_int("T10 and 0x01 the other way round", t_lastwr, 8'h01);

         // ----------------------------------------------------------------
         // T11. THE DATA-VALID RULE HELD THROUGHOUT. Over every byte so far, SDA changed
         //      while SCL was high only at framing -- never inside a byte.
         // ----------------------------------------------------------------
         $display("T11 SDA never changed inside a byte while SCL was high");
         ck_int("T11 no mid-byte changes", bad_sda_change, 0);
         ck_int("T11 no owner conflicts", n_conflicts, 0);
         ck_int("T11 and no spurious arbitration losses", n_arb, 0);

         // ----------------------------------------------------------------
         // T12. A COMPLETE TRANSACTION, framed. START, address, data, STOP -- with the
         //      clock handed from the framer to the generator and back again.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b0});
         send_byte(8'h42);
         clock_to_framer;
         @(negedge clk); do_stop = 1'b1; @(posedge clk); @(negedge clk); do_stop = 1'b0;
         wait_frame(600);
         $display("T12 a whole transaction, with the clock handed over and handed back");
         ck_int("T12 one START", m_nsta, 1);
         ck_int("T12 one STOP", m_nsto, 1);
         ck_int("T12 two bytes", m_nbyte, 2);
         ck_int("T12 no mid-byte framing", m_nmid, 0);
         ck_bit("T12 the transfer is closed", m_intr, 1'b0);
         ck_int("T12 the target saw one START and one STOP", t_nsta + t_nsto, 2);
         ck_int("T12 and received the data byte", t_lastwr, 8'h42);
         ck_bit("T12 both lines released at the end", m_scl_low | m_sda_low, 1'b0);

         ck_int("T13 the engine let go of SDA between bytes", held_after_byte, 0);


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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_byte_engine_tb.v — the same tests in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_byte_engine_tb.sv
   // Independent oracle for i2c_byte_engine, integrated against a pin-level target.
   //
   // This bench assembles everything built so far -- the SCL generator, the SDA owner and
   // inverter, the framer, the bit engine and the byte engine -- and puts a
   // i2c_target_model on the other end of a wired-AND bus. The target is driven by nothing
   // but the two wires: it finds its own edges, samples where §3.1.2 permits sampling and
   // drives where §3.1.2 permits driving.
   //
   // That is the point. A byte-level model handed "a byte arrived" cannot be wrong about
   // WHEN, and so cannot catch a master that is right about every byte and wrong about
   // every instant. This one can, and it does not know what the master's states are called.
   //
   // It is also where the SCL HANDOVER is exercised for the first time. Three blocks want
   // to drive SCL at different moments -- the framer during START and STOP, the generator
   // during bytes -- and the bench's `own_scl` task performs the overlapping handoff that
   // Chapter 17.12 builds into the master.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_byte_engine_tb;

      localparam integer NL = 9, NH = 5, NSU = 3, NSMP = 2;
      localparam integer NHD = 4, NSUA = 4, NSUO = 4, NBUF = 4;
      localparam [6:0]   TADDR = 7'h50;

      reg clk = 1'b0, rst_n = 1'b0;

      // ---- the master's pieces ------------------------------------------------
      reg  gen_idle_low = 1'b0, scl_yield = 1'b0;
      reg  do_start = 1'b0, do_restart = 1'b0, do_stop = 1'b0;
      reg  go = 1'b0, dir_write = 1'b1, ack_to_send = 1'b1;
      reg  [7:0] tx_byte = 8'h00;

      wire g_scl_low, drive_point, sample_point, g_rise, g_fall, g_stretch;
      wire [15:0] g_stretch_cyc, g_bits;
      wire [1:0]  g_phase;

      wire f_sda_req, f_sda_bit, f_scl_low, f_busy, f_done, f_bus_free, f_started, f_sw;
      wire [15:0] n_sta, n_rs, n_sto;
      wire [3:0]  f_state;

      wire b_sda_req, b_sda_bit, b_driving;
      wire [7:0]  rx_byte;
      wire        ack, ack_valid, byte_done, b_busy;
      wire [3:0]  bit_index;
      wire [15:0] bytes_done, n_acks, n_nacks;

      wire [3:0] req     = {2'b00, b_sda_req, f_sda_req};
      wire [3:0] bit_val = {2'b00, b_sda_bit, f_sda_bit};

      wire m_sda_low, sda_owned, sda_tx, arb_now, arb_lost, sda_conflict;
      wire [3:0] grant;
      wire [15:0] n_conflicts, n_arb;

      // THE CLOCK RUNS ONLY WHILE A BYTE IS IN FLIGHT. A generator left enabled between
      // bytes keeps pulsing SCL, and every pulse is a bit slot the target counts and the
      // master does not -- so the two fall out of step and the next byte read comes back
      // as all ones. So the enable is derived from the byte engine's own activity, which
      // is what the controller of Chapter 17.12 does.
      wire gen_enable = scl_yield && (b_busy || go);

      // The master's SCL contribution: the framer and the generator, wired-AND locally.
      wire m_scl_low = f_scl_low | g_scl_low;

      // ---- the bus and the target --------------------------------------------
      wire t_scl_low, t_sda_low;
      wire scl, sda;
      wire [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
      wire [7:0] scl_h, sda_h;

      reg  load_en = 1'b0;
      reg  [7:0] load_addr = 8'h00, load_data = 8'h00;

      i2c_line_model #(.N_DEV(2)) bus (
         .scl_drive_low({t_scl_low, m_scl_low}),
         .sda_drive_low({t_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(gen_enable), .idle_low(gen_idle_low),
         .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_stretch_cyc),
         .bits_generated(g_bits), .phase(g_phase));

      i2c_framer #(.N_HD_STA(NHD), .N_SU_STA(NSUA), .N_SU_STO(NSUO),
                   .N_BUF(NBUF), .N_SU_DAT(NSU), .CNT_W(16)) u_fr (
         .clk(clk), .rst_n(rst_n),
         .do_start(do_start), .do_restart(do_restart), .do_stop(do_stop),
         .scl_in(scl_in[0]), .sda_in(sda_in[0]), .scl_yield(scl_yield),
         .sda_req(f_sda_req), .sda_bit(f_sda_bit), .scl_drive_low(f_scl_low),
         .busy(f_busy), .done(f_done), .bus_free(f_bus_free), .started(f_started),
         .stretch_wait(f_sw), .starts(n_sta), .restarts(n_rs), .stops(n_sto),
         .state(f_state));

      i2c_byte_engine #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .go(go), .dir_write(dir_write), .tx_byte(tx_byte), .ack_to_send(ack_to_send),
         .sda_in(sda_in[0]), .scl_high(scl_in[0]), .abort(1'b0),
         .sda_req(b_sda_req), .sda_bit(b_sda_bit),
         .rx_byte(rx_byte), .ack(ack), .ack_valid(ack_valid), .byte_done(byte_done),
         .busy(b_busy), .bit_index(bit_index), .driving(b_driving),
         .bytes_done(bytes_done), .acks(n_acks), .nacks(n_nacks));

      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(b_driving),
         .sda_drive_low(m_sda_low),
         .grant(grant), .owned(sda_owned), .tx_bit(sda_tx),
         .owner_conflict(sda_conflict), .conflicts(n_conflicts),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(n_arb),
         .arb_clear(1'b0));

      wire t_sel, t_dirrd, t_wvalid;
      wire [7:0] t_lastwr;
      wire [15:0] t_rx, t_tx, t_nsta, t_nsto;
      wire [2:0]  t_state;

      i2c_target_model #(.MY_ADDR(TADDR), .ACK_ADDR(1'b1), .STRETCH_AFTER(0),
                         .NACK_AT(0), .N_MEM(16), .CNT_W(16)) u_tgt (
         .clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
         .scl_drive_low(t_scl_low), .sda_drive_low(t_sda_low),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .selected(t_sel), .dir_read(t_dirrd), .last_written(t_lastwr),
         .write_valid(t_wvalid), .bytes_rx(t_rx), .bytes_tx(t_tx),
         .n_starts(t_nsta), .n_stops(t_nsto), .state(t_state));

      // ---- the oracle ---------------------------------------------------------
      wire m_start, m_stop, m_bitv, m_byte, m_ackv, m_intr, m_mid, m_bit, m_ack;
      wire [7:0] m_byteval;
      wire [3:0] m_bidx;
      wire [15:0] m_nsta, m_nsto, m_nbyte, m_nmid;

      i2c_proto_mon #(.CNT_W(16)) mon (
         .clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
         .start_seen(m_start), .stop_seen(m_stop),
         .bit_seen(m_bit), .bit_val(m_bitv),
         .byte_seen(m_byte), .byte_val(m_byteval),
         .ack_seen(m_ack), .ack_val(m_ackv),
         .in_transfer(m_intr), .framing_midbyte(m_mid), .bit_index(m_bidx),
         .n_starts(m_nsta), .n_stops(m_nsto), .n_bytes(m_nbyte), .n_midbyte(m_nmid));

      always #5 clk = ~clk;

      integer errors = 0;
      integer n, k;
      reg [7:0] got;

      // §3.1.2 watchdog: SDA may only change while SCL is low, EXCEPT at framing.
      integer bad_sda_change;
      reg scl_l, sda_l;
      always @(negedge clk) begin
         if (rst_n) begin
            if (scl && scl_l && (sda != sda_l) && !m_start && !m_stop) begin
               // A change while SCL is high that the monitor is not about to call framing
               // is impossible -- so this counts the ones that are not at a byte boundary
               // either, which is what a corrupted bit would look like.
               if (m_bidx != 4'd0) bad_sda_change = bad_sda_change + 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;
            gen_idle_low = 1'b0; scl_yield = 1'b0;
            do_start = 1'b0; do_restart = 1'b0; do_stop = 1'b0;
            go = 1'b0; dir_write = 1'b1; ack_to_send = 1'b1; tx_byte = 8'h00;
            load_en = 1'b0;
            bad_sda_change = 0; scl_l = 1'b1; sda_l = 1'b1;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task preload (input [7:0] a, input [7:0] d);
         begin
            @(negedge clk); load_en = 1'b1; load_addr = a; load_data = d;
            @(posedge clk); @(negedge clk); load_en = 1'b0;
         end
      endtask

      task wait_frame (input integer max_cycles);
         begin
            n = 0;
            while (!f_done && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_frame: framer stuck in state %0d", f_state);
               errors = errors + 1;
            end
         end
      endtask

      // THE HANDOVER, in both directions. Asserting `scl_yield` and `gen_enable` in the
      // same cycle means the framer releases SCL and the generator drives it low at the
      // same clock edge, so the line never rises in between. Reversing it takes the clock
      // back while the generator is still parked low, so again no gap appears.
      task clock_to_generator;
         begin @(negedge clk); scl_yield = 1'b1; gen_idle_low = 1'b1; end
      endtask

      task clock_to_framer;
         begin
            // The generator is already parked low between bytes, so taking the clock back
            // is just a matter of the framer picking up the hold before the generator lets
            // go -- in that order, so the line never rises in between.
            n = 0;
            while (g_phase != 2'd0 && n < 200) begin step; n = n + 1; end
            @(negedge clk); scl_yield = 1'b0;   // the framer holds SCL low now
            step;
            @(negedge clk); gen_idle_low = 1'b0; // and the generator lets go
         end
      endtask

      // Advance to the ninth slot AND past its drive point, which is where ownership has
      // actually changed hands. Checking at the instant bit_index becomes 8 is too early:
      // the flip happens at the drive point inside slot 8's low phase, and until then
      // `driving` still reflects slot 7.
      task to_ack_slot;
         begin
            n = 0;
            while (bit_index != 4'd8 && n < 800) begin step; n = n + 1; end
            n = 0;
            while (!drive_point && n < 800) begin step; n = n + 1; end
            step;
         end
      endtask

      task send_byte (input [7:0] b);
         begin
            @(negedge clk); tx_byte = b; dir_write = 1'b1; go = 1'b1;
            @(posedge clk); @(negedge clk); go = 1'b0;
            n = 0;
            while (!byte_done && n < 800) begin step; n = n + 1; end
            if (n >= 800) begin
               $display("  FAIL send_byte(%02h): never completed (bit %0d)", b, bit_index);
               errors = errors + 1;
            end
         end
      endtask

      task recv_byte (input do_ack);
         begin
            @(negedge clk); dir_write = 1'b0; ack_to_send = do_ack; go = 1'b1;
            @(posedge clk); @(negedge clk); go = 1'b0;
            n = 0;
            while (!byte_done && n < 800) begin step; n = n + 1; end
            if (n >= 800) begin
               $display("  FAIL recv_byte: never completed (bit %0d)", bit_index);
               errors = errors + 1;
            end
            got = rx_byte;
         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 (0x%0h) expected %0d (0x%0h)", what, g, g, e, 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

      // ---- the engine must let go of SDA between bytes ------------------------
      //
      // A byte engine that finishes a byte and keeps requesting SDA holds the line into
      // the gap before the next byte. That is invisible in the per-byte checks: the next
      // byte's `go` overwrites the request, the gap sits inside the SCL low phase, and if
      // the held value is low it even helps a STOP that may follow. But the master is
      // driving a line it no longer owns, and the next block that wants SDA -- the framer,
      // for a repeated START -- collides with it.
      //
      // The window has to be stated carefully, because the bit engine's DEFERRED RELEASE
      // is correct and required: after deactivation it keeps holding SDA until SCL is low,
      // so that releasing cannot make the line rise during the high phase and manufacture
      // a STOP. So `sda_req` staying high immediately after `byte_done` is not a defect.
      //
      // The real invariant is: once the byte is done AND SCL has actually been observed
      // low, the request must be gone.
      integer held_after_byte;
      reg     between_bytes;
      reg     seen_low;
      always @(posedge clk) begin
         if (!rst_n) begin
            held_after_byte <= 0; between_bytes <= 1'b0; seen_low <= 1'b0;
         end else begin
            if (byte_done)  begin between_bytes <= 1'b1; seen_low <= 1'b0; end
            else if (go)          between_bytes <= 1'b0;
            if (between_bytes && !scl_in[0]) seen_low <= 1'b1;
            if (between_bytes && seen_low && !go && b_sda_req)
               held_after_byte <= held_after_byte + 1;
         end
      end

      initial begin
         $display("=== i2c_byte_engine: eight bits, then the slot where SDA changes hands ===");

         // ----------------------------------------------------------------
         // T1. An address byte, acknowledged by a real target that found its own edges.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b0});
         $display("T1  an address byte, acknowledged by a pin-level target");
         ck_bit("T1 acknowledged", ack, 1'b1);
         ck_bit("T1 the target selected itself", t_sel, 1'b1);
         ck_int("T1 the monitor saw one byte", m_nbyte, 1);
         ck_int("T1 and it was the address with the write bit", m_byteval, {TADDR, 1'b0});
         // Eight, not nine: the generator counts a bit when its HIGH phase ends, and the
         // byte completes at the SAMPLE point inside the ninth high phase. So at the
         // instant the byte is done, the ninth pulse is still in progress.
         ck_int("T1 eight pulses completed, with the ninth still in flight", g_bits, 8);

         // ----------------------------------------------------------------
         // T2. NINE SLOTS, NOT EIGHT. §3.1.4: "The master generates all clock pulses,
         //     including the acknowledge ninth clock pulse." A byte costs nine.
         // ----------------------------------------------------------------
         $display("T2  a byte costs nine clock pulses, not eight");
         ck_int("T2 the bit index returned to zero", bit_index, 0);
         ck_int("T2 one byte completed", bytes_done, 1);

         // ----------------------------------------------------------------
         // T3. A data byte, written and read back out of the target's own register.
         // ----------------------------------------------------------------
         send_byte(8'h5A);
         $display("T3  a data byte reaches the target and is acknowledged");
         ck_bit("T3 acknowledged", ack, 1'b1);
         ck_int("T3 the target received it", t_lastwr, 8'h5A);
         ck_int("T3 and counted it", t_rx, 1);
         ck_int("T3 the monitor agrees", m_byteval, 8'h5A);

         // ----------------------------------------------------------------
         // T4. THE OWNERSHIP FLIP, when writing. The master drives slots 0-7 and RELEASES
         //     for slot 8 so the target can answer. §3.1.4, and the test looks at the
         //     master's own driving signal in the ninth slot.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         @(negedge clk); tx_byte = {TADDR, 1'b0}; dir_write = 1'b1; go = 1'b1;
         @(posedge clk); @(negedge clk); go = 1'b0;
         to_ack_slot;
         $display("T4  writing: the master releases SDA for the ninth slot");
         ck_int("T4 in the ninth slot", bit_index, 8);
         ck_bit("T4 the master is no longer driving", b_driving, 1'b0);
         n = 0;
         while (!byte_done && n < 800) begin step; n = n + 1; end
         ck_bit("T4 and the target's ACK was read", ack, 1'b1);

         // ----------------------------------------------------------------
         // T5. A READ. The master releases slots 0-7 and DRIVES slot 8 -- the mirror
         //     image. The byte comes from the target's memory.
         // ----------------------------------------------------------------
         do_reset;
         preload(8'h00, 8'hC3);
         preload(8'h01, 8'h7E);
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b1});                 // address with the read bit
         ck_bit("T5 the read address was acknowledged", ack, 1'b1);
         ck_bit("T5 the target knows it is a read", t_dirrd, 1'b1);
         recv_byte(1'b1);                          // ACK: send me another
         $display("T5  reading: the master releases the data slots and drives the ninth");
         ck_int("T5 the first byte came from the target", got, 8'hC3);
         ck_int("T5 the target counted a transmit", t_tx, 1);

         // ----------------------------------------------------------------
         // T6. THE MIRROR IMAGE, checked in the ninth slot. When reading, the master
         //     drives slot 8 with its own answer.
         // ----------------------------------------------------------------
         @(negedge clk); dir_write = 1'b0; ack_to_send = 1'b1; go = 1'b1;
         @(posedge clk); @(negedge clk); go = 1'b0;
         to_ack_slot;
         $display("T6  reading: the master drives the ninth slot with its own answer");
         ck_int("T6 in the ninth slot", bit_index, 8);
         ck_bit("T6 and now the master IS driving", b_driving, 1'b1);
         n = 0;
         while (!byte_done && n < 800) begin step; n = n + 1; end
         ck_int("T6 the second byte arrived", rx_byte, 8'h7E);

         // ----------------------------------------------------------------
         // T7. ACK POLARITY IS INVERTED. The receiver pulls SDA LOW to acknowledge, so
         //     the monitor -- which reads the line -- sees a ZERO for an ACK.
         // ----------------------------------------------------------------
         $display("T7  an acknowledge is a LOW on the wire, the inverse of intuition");
         ck_bit("T7 the wire carried a zero in the ack slot", m_ackv, 1'b0);
         ck_bit("T7 and the engine reports it as acknowledged", ack, 1'b1);

         // ----------------------------------------------------------------
         // T8. A NACK from the master ends the read, and the target lets go.
         // ----------------------------------------------------------------
         recv_byte(1'b0);                          // NACK: I am finished
         $display("T8  the master's NACK ends the read and the target releases SDA");
         ck_bit("T8 the engine reports a NACK", ack, 1'b0);
         ck_bit("T8 the wire carried a one in the ack slot", m_ackv, 1'b1);
         ck_bit("T8 the target has released SDA", t_sda_low, 1'b0);
         ck_int("T8 the NACK was counted", n_nacks, 1);

         // ----------------------------------------------------------------
         // T9. A NACK from the TARGET, on a wrong address. The address is not
         //     acknowledged, and the engine must report that rather than proceeding.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({7'h51, 1'b0});                 // not our target
         $display("T9  an unaddressed target does not answer, and the engine says so");
         ck_bit("T9 not acknowledged", ack, 1'b0);
         ck_bit("T9 the target did not select itself", t_sel, 1'b0);
         ck_int("T9 the monitor saw the byte all the same", m_nbyte, 1);
         ck_bit("T9 and the ack slot carried a one", m_ackv, 1'b1);

         // ----------------------------------------------------------------
         // T10. MSB FIRST. §3.1.3. An asymmetric byte proves the order, and 0x80 proves
         //      it in the strongest way: only the first bit is set.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b0});
         send_byte(8'h80);
         $display("T10 bits go out most significant first");
         ck_int("T10 the target received 0x80", t_lastwr, 8'h80);
         ck_int("T10 and the monitor read 0x80 off the wire", m_byteval, 8'h80);
         send_byte(8'h01);
         ck_int("T10 and 0x01 the other way round", t_lastwr, 8'h01);

         // ----------------------------------------------------------------
         // T11. THE DATA-VALID RULE HELD THROUGHOUT. Over every byte so far, SDA changed
         //      while SCL was high only at framing -- never inside a byte.
         // ----------------------------------------------------------------
         $display("T11 SDA never changed inside a byte while SCL was high");
         ck_int("T11 no mid-byte changes", bad_sda_change, 0);
         ck_int("T11 no owner conflicts", n_conflicts, 0);
         ck_int("T11 and no spurious arbitration losses", n_arb, 0);

         // ----------------------------------------------------------------
         // T12. A COMPLETE TRANSACTION, framed. START, address, data, STOP -- with the
         //      clock handed from the framer to the generator and back again.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0;
         wait_frame(400);
         clock_to_generator;
         send_byte({TADDR, 1'b0});
         send_byte(8'h42);
         clock_to_framer;
         @(negedge clk); do_stop = 1'b1; @(posedge clk); @(negedge clk); do_stop = 1'b0;
         wait_frame(600);
         $display("T12 a whole transaction, with the clock handed over and handed back");
         ck_int("T12 one START", m_nsta, 1);
         ck_int("T12 one STOP", m_nsto, 1);
         ck_int("T12 two bytes", m_nbyte, 2);
         ck_int("T12 no mid-byte framing", m_nmid, 0);
         ck_bit("T12 the transfer is closed", m_intr, 1'b0);
         ck_int("T12 the target saw one START and one STOP", t_nsta + t_nsto, 2);
         ck_int("T12 and received the data byte", t_lastwr, 8'h42);
         ck_bit("T12 both lines released at the end", m_scl_low | m_sda_low, 1'b0);

         ck_int("T13 the engine let go of SDA between bytes", held_after_byte, 0);


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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_byte_engine_tb.vhd — the same tests in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_byte_engine_tb.vhd
   -- Independent oracle for i2c_byte_engine, integrated against a pin-level target.
   -- Behavioural twin of the SV and Verilog benches.
   --
   -- This bench assembles everything built so far -- the SCL generator, the SDA owner and
   -- inverter, the framer, the bit engine and the byte engine -- and puts an i2c_target_model
   -- on the other end of a wired-AND bus. The target is driven by nothing but the two wires.
   --
   -- That is the point. A byte-level model handed "a byte arrived" cannot be wrong about WHEN,
   -- and so cannot catch a master that is right about every byte and wrong about every instant.
   -- This one can, and it does not know what the master's states are called.
   --
   -- It is also where the SCL HANDOVER is exercised for the first time.
   -- ---------------------------------------------------------------------------

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

   entity i2c_byte_engine_tb is
   end entity i2c_byte_engine_tb;

   architecture sim of i2c_byte_engine_tb is

      constant TCLK : time := 10 ns;
      constant NL : integer := 9;
      constant NH : integer := 5;
      constant NSU : integer := 3;
      constant NSMP : integer := 2;
      constant NHD : integer := 4;
      constant NSUA : integer := 4;
      constant NSUO : integer := 4;
      constant NBUF : integer := 4;
      constant TADDR : std_logic_vector(6 downto 0) := "1010000";   -- 0x50

      signal clk, rst_n : std_logic := '0';
      signal gen_idle_low, scl_yield : std_logic := '0';
      signal do_start, do_restart, do_stop : std_logic := '0';
      signal go, dir_write, ack_to_send : std_logic := '0';
      signal tx_byte : std_logic_vector(7 downto 0) := (others => '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 g_phase : unsigned(1 downto 0);

      signal f_sda_req, f_sda_bit, f_scl_low : std_logic;
      signal f_busy, f_done, f_bus_free, f_started, f_sw : std_logic;
      signal n_sta, n_rs, n_sto : unsigned(15 downto 0);
      signal f_state : unsigned(3 downto 0);

      signal b_sda_req, b_sda_bit, b_driving, b_busy, b_ack, b_ackv, b_done : std_logic;
      signal rx_byte : std_logic_vector(7 downto 0);
      signal bit_index : unsigned(3 downto 0);
      signal bytes_done, n_acks, n_nacks : unsigned(15 downto 0);

      signal req, bit_val, grant : std_logic_vector(3 downto 0);
      signal m_sda_low, sda_owned, sda_tx, arb_now, arb_lost, sda_conflict : std_logic;
      signal n_conflicts, n_arb : unsigned(15 downto 0);

      signal gen_enable, m_scl_low : std_logic;

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

      signal load_en : std_logic := '0';
      signal load_addr, load_data : std_logic_vector(7 downto 0) := (others => '0');
      signal t_sel, t_dirrd, t_wvalid : std_logic;
      signal t_lastwr : std_logic_vector(7 downto 0);
      signal t_rx, t_tx, t_nsta, t_nsto : unsigned(15 downto 0);
      signal t_state : unsigned(2 downto 0);

      signal mo_start, mo_stop, mo_bit, mo_bitv, mo_byte, mo_ack, mo_ackv : std_logic;
      signal mo_intr, mo_mid : std_logic;
      signal mo_byteval : std_logic_vector(7 downto 0);
      signal mo_bidx : unsigned(3 downto 0);
      signal mo_nsta, mo_nsto, mo_nbyte, mo_nmid : unsigned(15 downto 0);

      signal bad_sda_change : integer := 0;
      signal halt : boolean := false;


      -- The engine must let go of SDA between bytes. The window has to be stated
      -- carefully, because the bit engine's DEFERRED RELEASE is correct and required:
      -- after deactivation it holds SDA until SCL is low, so that releasing cannot make
      -- the line rise during the high phase and manufacture a STOP. So sda_req staying
      -- high immediately after byte_done is not a defect. The real invariant is: once the
      -- byte is done AND SCL has actually been observed low, the request must be gone.
      signal held_after_byte : integer := 0;
      -- SIGNALS, not variables: the SystemVerilog and Verilog versions hold these in
      -- registers, so they take effect on the NEXT edge. A VHDL variable updates
      -- immediately and would evaluate the guard one cycle earlier, catching the
      -- legitimate deferred-release cycle and reporting a defect that is not there.
      signal between_bytes, seen_low : boolean := false;

   begin

      -- THE CLOCK RUNS ONLY WHILE A BYTE IS IN FLIGHT. A generator left enabled between bytes
      -- keeps pulsing SCL, and every pulse is a bit slot the target counts and the master does
      -- not -- so the two fall out of step and the next read comes back as all ones.
      gen_enable <= scl_yield and (b_busy or go);

      -- The master's SCL contribution: the framer and the generator, wired-AND locally.
      m_scl_low <= f_scl_low or g_scl_low;

      scl_drv <= t_scl_low & m_scl_low;
      sda_drv <= t_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 => gen_enable, idle_low => gen_idle_low,
            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 => g_phase);

      u_fr : entity work.i2c_framer
         generic map (N_HD_STA => NHD, N_SU_STA => NSUA, N_SU_STO => NSUO,
                      N_BUF => NBUF, N_SU_DAT => NSU, CNT_W => 16)
         port map (clk => clk, rst_n => rst_n,
            do_start => do_start, do_restart => do_restart, do_stop => do_stop,
            scl_in => scl_in(0), sda_in => sda_in(0), scl_yield => scl_yield,
            sda_req => f_sda_req, sda_bit => f_sda_bit, scl_drive_low => f_scl_low,
            busy => f_busy, done => f_done, bus_free => f_bus_free, started => f_started,
            stretch_wait => f_sw, starts => n_sta, restarts => n_rs, stops => n_sto,
            state => f_state);

      dut : entity work.i2c_byte_engine
         generic map (CNT_W => 16)
         port map (clk => clk, rst_n => rst_n,
            drive_point => drive_point, sample_point => sample_point,
            go => go, dir_write => dir_write, tx_byte => tx_byte, ack_to_send => ack_to_send,
            sda_in => sda_in(0), scl_high => scl_in(0), abort => '0',
            sda_req => b_sda_req, sda_bit => b_sda_bit,
            rx_byte => rx_byte, ack => b_ack, ack_valid => b_ackv, byte_done => b_done,
            busy => b_busy, bit_index => bit_index, driving => b_driving,
            bytes_done => bytes_done, acks => n_acks, nacks => n_nacks);

      req     <= "00" & b_sda_req & f_sda_req;
      bit_val <= "00" & b_sda_bit & f_sda_bit;

      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 => b_driving,
            sda_drive_low => m_sda_low,
            grant => grant, owned => sda_owned, tx_bit => sda_tx,
            owner_conflict => sda_conflict, conflicts => n_conflicts,
            arb_loss_now => arb_now, arb_lost => arb_lost, arb_losses => n_arb,
            arb_clear => '0');

      u_tgt : entity work.i2c_target_model
         generic map (MY_ADDR => TADDR, ACK_ADDR => '1', STRETCH_AFTER => 0,
                      NACK_AT => 0, N_MEM => 16, CNT_W => 16)
         port map (clk => clk, rst_n => rst_n, scl => scl, sda => sda,
            scl_drive_low => t_scl_low, sda_drive_low => t_sda_low,
            load_en => load_en, load_addr => load_addr, load_data => load_data,
            selected => t_sel, dir_read => t_dirrd, last_written => t_lastwr,
            write_valid => t_wvalid, bytes_rx => t_rx, bytes_tx => t_tx,
            n_starts => t_nsta, n_stops => t_nsto, state => t_state);

      mon : entity work.i2c_proto_mon
         generic map (CNT_W => 16)
         port map (clk => clk, rst_n => rst_n, scl => scl, sda => sda,
            start_seen => mo_start, stop_seen => mo_stop, bit_seen => mo_bit,
            bit_val => mo_bitv, byte_seen => mo_byte, byte_val => mo_byteval,
            ack_seen => mo_ack, ack_val => mo_ackv, in_transfer => mo_intr,
            framing_midbyte => mo_mid, bit_index => mo_bidx,
            n_starts => mo_nsta, n_stops => mo_nsto, n_bytes => mo_nbyte,
            n_midbyte => mo_nmid);

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

      -- §3.1.2 watchdog: SDA may only change while SCL is low, EXCEPT at framing.
      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_sda_change <= 0;
         elsif falling_edge(clk) then
            if scl = '1' and scl_l = '1' and sda /= sda_l
               and mo_start = '0' and mo_stop = '0' and mo_bidx /= 0 then
               bad_sda_change <= bad_sda_change + 1;
            end if;
            scl_l := scl; sda_l := sda;
         end if;
      end process;

      letgo_obs : process (clk, rst_n)
      begin
         if rst_n = '0' then
            held_after_byte <= 0; between_bytes <= false; seen_low <= false;
         elsif rising_edge(clk) then
            if b_done = '1' then
               between_bytes <= true; seen_low <= false;
            elsif go = '1' then
               between_bytes <= false;
            end if;
            if between_bytes and scl_in(0) = '0' then seen_low <= true; end if;
            if between_bytes and seen_low and go = '0' and b_sda_req = '1' then
               held_after_byte <= held_after_byte + 1;
            end if;
         end if;
      end process;

      stim : process
         variable err : integer := 0;
         variable n : integer;
         variable got : std_logic_vector(7 downto 0);

         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';
            gen_idle_low <= '0'; scl_yield <= '0';
            do_start <= '0'; do_restart <= '0'; do_stop <= '0';
            go <= '0'; dir_write <= '1'; ack_to_send <= '1'; tx_byte <= (others => '0');
            load_en <= '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;

         procedure preload (a : integer; d : integer) is
         begin
            wait until falling_edge(clk);
            load_en <= '1';
            load_addr <= std_logic_vector(to_unsigned(a, 8));
            load_data <= std_logic_vector(to_unsigned(d, 8));
            wait until rising_edge(clk); wait until falling_edge(clk);
            load_en <= '0';
         end procedure;

         procedure pulse_start is
         begin
            wait until falling_edge(clk); do_start <= '1';
            wait until rising_edge(clk); wait until falling_edge(clk); do_start <= '0';
         end procedure;

         procedure pulse_stop is
         begin
            wait until falling_edge(clk); do_stop <= '1';
            wait until rising_edge(clk); wait until falling_edge(clk); do_stop <= '0';
         end procedure;

         procedure wait_frame (max_cycles : integer) is
         begin
            n := 0;
            while f_done = '0' and n < max_cycles loop step; n := n + 1; end loop;
            if n >= max_cycles then
               report "  FAIL wait_frame: framer stuck in state "
                      & integer'image(to_integer(f_state)) severity note;
               err := err + 1;
            end if;
         end procedure;

         -- THE HANDOVER, in both directions. Yielding and enabling in the same cycle means the
         -- framer releases SCL and the generator drives it low at the same clock edge.
         procedure clock_to_generator is
         begin
            wait until falling_edge(clk); scl_yield <= '1'; gen_idle_low <= '1';
         end procedure;

         procedure clock_to_framer is
         begin
            n := 0;
            while to_integer(g_phase) /= 0 and n < 200 loop step; n := n + 1; end loop;
            wait until falling_edge(clk); scl_yield <= '0';
            step;
            wait until falling_edge(clk); gen_idle_low <= '0';
         end procedure;

         procedure send_byte (b : integer) is
         begin
            wait until falling_edge(clk);
            tx_byte <= std_logic_vector(to_unsigned(b, 8)); dir_write <= '1'; go <= '1';
            wait until rising_edge(clk); wait until falling_edge(clk); go <= '0';
            n := 0;
            while b_done = '0' and n < 800 loop step; n := n + 1; end loop;
            if n >= 800 then
               report "  FAIL send_byte: never completed" severity note;
               err := err + 1;
            end if;
         end procedure;

         procedure recv_byte (do_ack : std_logic) is
         begin
            wait until falling_edge(clk);
            dir_write <= '0'; ack_to_send <= do_ack; go <= '1';
            wait until rising_edge(clk); wait until falling_edge(clk); go <= '0';
            n := 0;
            while b_done = '0' and n < 800 loop step; n := n + 1; end loop;
            if n >= 800 then
               report "  FAIL recv_byte: never completed" severity note;
               err := err + 1;
            end if;
            got := rx_byte;
         end procedure;

         -- Advance to the ninth slot AND past its drive point, which is where ownership has
         -- actually changed hands. Checking at the instant bit_index becomes 8 is too early.
         procedure to_ack_slot is
         begin
            n := 0;
            while bit_index /= 8 and n < 800 loop step; n := n + 1; end loop;
            n := 0;
            while drive_point = '0' and n < 800 loop step; n := n + 1; end loop;
            step;
         end procedure;

      begin
         report "=== i2c_byte_engine: eight bits, then the slot where SDA changes hands ==="
                severity note;

         -- T1. An address byte, acknowledged by a real target that found its own edges.
         do_reset;
         pulse_start;
         wait_frame(400);
         clock_to_generator;
         send_byte(16#50# * 2);
         report "T1  an address byte, acknowledged by a pin-level target" severity note;
         ck_bit("T1 acknowledged", b_ack, '1');
         ck_bit("T1 the target selected itself", t_sel, '1');
         ck_int("T1 the monitor saw one byte", to_integer(mo_nbyte), 1);
         ck_int("T1 and it was the address with the write bit",
                to_integer(unsigned(mo_byteval)), 16#A0#);
         -- Eight, not nine: the generator counts a bit when its HIGH phase ends, and the byte
         -- completes at the SAMPLE point inside the ninth high phase.
         ck_int("T1 eight pulses completed, with the ninth still in flight",
                to_integer(g_bits), 8);

         -- T2. NINE SLOTS, NOT EIGHT. §3.1.4: "including the acknowledge ninth clock pulse."
         report "T2  a byte costs nine clock pulses, not eight" severity note;
         ck_int("T2 the bit index returned to zero", to_integer(bit_index), 0);
         ck_int("T2 one byte completed", to_integer(bytes_done), 1);

         -- T3. A data byte, read back out of the target's own register.
         send_byte(16#5A#);
         report "T3  a data byte reaches the target and is acknowledged" severity note;
         ck_bit("T3 acknowledged", b_ack, '1');
         ck_int("T3 the target received it", to_integer(unsigned(t_lastwr)), 16#5A#);
         ck_int("T3 and counted it", to_integer(t_rx), 1);
         ck_int("T3 the monitor agrees", to_integer(unsigned(mo_byteval)), 16#5A#);

         -- T4. THE OWNERSHIP FLIP, when writing: the master RELEASES for slot 8.
         do_reset;
         pulse_start;
         wait_frame(400);
         clock_to_generator;
         wait until falling_edge(clk);
         tx_byte <= x"A0"; dir_write <= '1'; go <= '1';
         wait until rising_edge(clk); wait until falling_edge(clk); go <= '0';
         to_ack_slot;
         report "T4  writing: the master releases SDA for the ninth slot" severity note;
         ck_int("T4 in the ninth slot", to_integer(bit_index), 8);
         ck_bit("T4 the master is no longer driving", b_driving, '0');
         n := 0;
         while b_done = '0' and n < 800 loop step; n := n + 1; end loop;
         ck_bit("T4 and the target's ACK was read", b_ack, '1');

         -- T5. A READ: the master releases slots 0-7 and DRIVES slot 8 -- the mirror image.
         do_reset;
         preload(0, 16#C3#);
         preload(1, 16#7E#);
         pulse_start;
         wait_frame(400);
         clock_to_generator;
         send_byte(16#A1#);
         ck_bit("T5 the read address was acknowledged", b_ack, '1');
         ck_bit("T5 the target knows it is a read", t_dirrd, '1');
         recv_byte('1');
         report "T5  reading: the master releases the data slots and drives the ninth"
                severity note;
         ck_int("T5 the first byte came from the target",
                to_integer(unsigned(got)), 16#C3#);
         ck_int("T5 the target counted a transmit", to_integer(t_tx), 1);

         -- T6. THE MIRROR IMAGE, checked in the ninth slot.
         wait until falling_edge(clk);
         dir_write <= '0'; ack_to_send <= '1'; go <= '1';
         wait until rising_edge(clk); wait until falling_edge(clk); go <= '0';
         to_ack_slot;
         report "T6  reading: the master drives the ninth slot with its own answer"
                severity note;
         ck_int("T6 in the ninth slot", to_integer(bit_index), 8);
         ck_bit("T6 and now the master IS driving", b_driving, '1');
         n := 0;
         while b_done = '0' and n < 800 loop step; n := n + 1; end loop;
         ck_int("T6 the second byte arrived", to_integer(unsigned(rx_byte)), 16#7E#);

         -- T7. ACK POLARITY IS INVERTED: the monitor, reading the line, sees a ZERO for an ACK.
         report "T7  an acknowledge is a LOW on the wire, the inverse of intuition"
                severity note;
         ck_bit("T7 the wire carried a zero in the ack slot", mo_ackv, '0');
         ck_bit("T7 and the engine reports it as acknowledged", b_ack, '1');

         -- T8. A NACK from the master ends the read, and the target lets go.
         recv_byte('0');
         report "T8  the master's NACK ends the read and the target releases SDA"
                severity note;
         ck_bit("T8 the engine reports a NACK", b_ack, '0');
         ck_bit("T8 the wire carried a one in the ack slot", mo_ackv, '1');
         ck_bit("T8 the target has released SDA", t_sda_low, '0');
         ck_int("T8 the NACK was counted", to_integer(n_nacks), 1);

         -- T9. A NACK from the TARGET, on a wrong address.
         do_reset;
         pulse_start;
         wait_frame(400);
         clock_to_generator;
         send_byte(16#A2#);                 -- address 0x51, write
         report "T9  an unaddressed target does not answer, and the engine says so"
                severity note;
         ck_bit("T9 not acknowledged", b_ack, '0');
         ck_bit("T9 the target did not select itself", t_sel, '0');
         ck_int("T9 the monitor saw the byte all the same", to_integer(mo_nbyte), 1);
         ck_bit("T9 and the ack slot carried a one", mo_ackv, '1');

         -- T10. MSB FIRST. §3.1.3, proved with 0x80 and 0x01.
         do_reset;
         pulse_start;
         wait_frame(400);
         clock_to_generator;
         send_byte(16#A0#);
         send_byte(16#80#);
         report "T10 bits go out most significant first" severity note;
         ck_int("T10 the target received 0x80", to_integer(unsigned(t_lastwr)), 16#80#);
         ck_int("T10 and the monitor read 0x80 off the wire",
                to_integer(unsigned(mo_byteval)), 16#80#);
         send_byte(16#01#);
         ck_int("T10 and 0x01 the other way round",
                to_integer(unsigned(t_lastwr)), 16#01#);

         -- T11. THE DATA-VALID RULE HELD THROUGHOUT.
         report "T11 SDA never changed inside a byte while SCL was high" severity note;
         ck_int("T11 no mid-byte changes", bad_sda_change, 0);
         ck_int("T11 no owner conflicts", to_integer(n_conflicts), 0);
         ck_int("T11 and no spurious arbitration losses", to_integer(n_arb), 0);

         -- T12. A COMPLETE TRANSACTION, framed, with the clock handed over and handed back.
         do_reset;
         pulse_start;
         wait_frame(400);
         clock_to_generator;
         send_byte(16#A0#);
         send_byte(16#42#);
         clock_to_framer;
         pulse_stop;
         wait_frame(600);
         report "T12 a whole transaction, with the clock handed over and handed back"
                severity note;
         ck_int("T12 one START", to_integer(mo_nsta), 1);
         ck_int("T12 one STOP", to_integer(mo_nsto), 1);
         ck_int("T12 two bytes", to_integer(mo_nbyte), 2);
         ck_int("T12 no mid-byte framing", to_integer(mo_nmid), 0);
         ck_bit("T12 the transfer is closed", mo_intr, '0');
         ck_int("T12 the target saw one START and one STOP",
                to_integer(t_nsta) + to_integer(t_nsto), 2);
         ck_int("T12 and received the data byte",
                to_integer(unsigned(t_lastwr)), 16#42#);
         ck_bit("T12 both lines released at the end", m_scl_low or m_sda_low, '0');

         ck_int("T13 the engine let go of SDA between bytes", held_after_byte, 0);


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

   end architecture sim;

6b. Execution

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_byte_enginePASS 13/13PASS 13/13PASS 13/1317380 ns, all three

7. Mutation Testing

Ten defects, each aimed at one claim above.

#Injected defectExpected detectionResult
M1eight slots instead of nine — no acknowledge pulse at allT2KILLED (22)
M2no ownership flip — master drives through the target's ACK slotT4, T6KILLED (6)
M3LSB first — shift the wrong wayT10KILLED (19)
M4transmit bit 0 instead of the MSBT10KILLED (12)
M5ACK polarity inverted on a writeT7, T9KILLED (6)
M6the master's own ACK sent with wrong polarity on a readT8KILLED (4)
M7the received byte latched one slot earlyT3, T5KILLED (3)
M8data slots transmit the wrong shift-register bitT3, T10KILLED (19)
M9byte_done never firesT1, T12KILLED (14)
M10the engine holds SDA past the end of the byteT13 after strengtheningKILLED (2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
killed: 10   survived: 0   score: 10/10
restored: PASS

M10 took three attempts to state correctly

This one is instructive about mutation construction, not just about the design.

Attempt one set tx_en high at byte completion. It survived — and inspecting 17.6 shows why: tx_en is read only inside the bit engine's active branch, at the drive point. With active low it is dead. An equivalent mutant.

Attempt two kept the engine active instead. It also survived, for the mirror reason: staying active with tx_en low means the engine keeps releasing the line, which is what it was going to do anyway.

Attempt three set both. Now the master genuinely holds its acknowledge bit past the end of the byte — and it still survived, which made it a real testbench gap rather than an equivalent mutant.

Why nothing saw it: the next byte's go overwrites the request, the gap between bytes sits inside the SCL low phase, and if the held value happens to be low it even helps a STOP that may follow. No per-byte check can observe it. But the master is driving a line it no longer owns, and the next block that wants SDA — the framer, for a repeated START — collides with it.

Stating the invariant required care, because the obvious version is wrong:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
WRONG:  after byte_done, sda_req must be low

That fails on the correct design, because 17.6 §4's deferred release is required: after deactivation the bit engine deliberately holds SDA until SCL is low, so releasing cannot make the line rise in the high phase and manufacture a STOP.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
RIGHT:  once the byte is done AND SCL has been observed low, sda_req must be gone

8. Verification Connection — What a Byte Monitor Knows, and What Needs Context

Azvya Education Pvt. Ltd.VLSI Mentor
byte_monitor.sv — the layer between pin wiggles and transactions
   // Chapter 17.6 §8 said a bit-level monitor can report a slot, a value and a
   // direction, and nothing about position or meaning. A BYTE monitor is the next
   // layer, and it is worth being exact about what it adds and what it still cannot
   // know -- because this is where most I²C environments over-reach.
   //
   // WHAT A BYTE MONITOR CAN REPORT, from the wires plus its own bit counting:
   //
   //     eight bits, MSB first, as a byte value
   //     the level in the ninth slot        -> acknowledged or not
   //     which side was driving each part   -> inferred from the framing it tracked
   //
   // WHAT IT CANNOT REPORT, and each needs transaction context from 17.8:
   //
   //     whether this byte was an ADDRESS or DATA. Identical on the wire. The only
   //     difference is that it was the first byte after a START -- which is framing
   //     state, not byte state.
   //
   //     whether a NACK was an ERROR. A NACK on an address is a failure; a NACK from
   //     the MASTER on the last byte of a read is CORRECT and required. Same level in
   //     the same slot, opposite meanings.
   //
   //     WHO sent the acknowledge. The monitor sees SDA low in slot 8. Whether the
   //     target pulled it (a write) or the master did (a read) depends on the
   //     direction bit of an address byte it may not have seen.
   //
   // SO THE TRANSACTION ITEM IS ASSEMBLED ABOVE THIS LAYER. A byte monitor emits
   // byte-level items; a predictor turns a START plus an address plus N data bytes
   // plus a STOP into one transaction. Trying to emit transaction items directly from
   // a byte monitor forces it to re-implement the framing tracker it already depends
   // on, and the duplicate is what goes stale.
   //
   // COVERAGE this block makes reachable:
   //
   //   cover: a write byte NACKed by the target        (T9)
   //   cover: a read byte the MASTER nacked            (T8)  -- correct behaviour
   //   cover: a read byte the master ACKed             (T5)  -- more bytes to come
   //   cover: byte value 0x80 and 0x01                 (T10) -- the MSB/LSB extremes
   //
   // The 0x80 and 0x01 bins are not decoration: they are the only values whose
   // transmission distinguishes a correct shift direction from a reversed one when
   // the byte is otherwise symmetric.

9. FPGA and ASIC Implications

On an FPGA, the shift register and the four-bit slot index are trivial. The interesting consequence is at the block's input: tx_byte is captured when go asserts, so the host data path has one SCL period per byte — tens of microseconds — to present the next byte. That is an enormous margin, and it is why an I²C master needs no FIFO to keep up with the bus. Chapter 17.2 §9 noted that a large N_BUF pushes the buffers into block RAM and makes the read registered; that extra cycle is absorbed here without difficulty for the same reason.

On an ASIC, the ownership flip is where the pad's output enable toggles mid-byte, once per byte, every byte. That is the highest-frequency ownership change anywhere in the design, and it is worth knowing that it happens inside the SCL low phase — at the drive point, never during the high phase — because a pad enable toggling while the line is high would create exactly the framing edges 17.6 §4 works to avoid. The structural guarantee that SDA is written only at the drive point is what makes the pad's enable safe to toggle at all.

Reset leaves the engine inactive with SDA released, which is the same requirement as every other block: a master coming out of reset must not be holding the bus down.

10. Debugging — The Read That Was Off by One Bit, and Only Sometimes

Symptom

A master reads a 16-bit value from a sensor as two bytes. The value is usually correct. Roughly one reading in eight is exactly double the expected value, with the least significant bit of the low byte being zero. The high byte is always correct. Reading the same register twice in a row returns a correct value and then a doubled one, alternating irregularly.

Root Cause

The received byte was latched on the slot index rather than on the bit engine's completion pulse, so on any bit where the sample point was delayed the latch ran before the final bit had been sampled. The shift register then contributed its previous contents and the newest bit was lost, shifting the byte left by one. Nothing was wrong with the sensor, the bus, the framing or the acknowledge -- and nothing was wrong with the master's timing either, in the sense that every edge it produced was legal. The defect was a dependency on a counter reaching a value instead of on the event that says the value is ready, which is the same class as the wrong-anchor defects of Chapters 17.3 and 17.5.

Fix
Latch the byte on bit_done for the final slot, not on the slot index alone -- which is what the published engine does: every slot transition is inside if (bit_done). Then note the environment failure that let it survive: a target model that never stretches makes the two designs identical, because without a stretch the sample point and the slot index advance together. Test T3 reads a byte back out of a real target model, and the stretch tests of Chapter 17.3 T6 to T8 are what make the timing of that latch observable. A bench built from a scripted responder cannot fail the original design at all.

Three generalisations.

A counter reaching a value is not an event. bit_index == 7 says which slot we are in, not that the slot has finished. The engine advances on bit_done for exactly this reason, and every state change in the published block is inside that condition.

The high byte was always correct, which sent the investigation to the sensor. A fault that appears on one byte of two looks like a property of what that byte contains. It was a property of when the target chose to stretch, which correlates with position only accidentally.

A permissive target model made two different designs identical. Without a stretch, the sample point and the slot index advance in lockstep, so the correct and incorrect latch conditions cannot be distinguished. This is the third time in the module that an environment which never withholds anything has certified a defect — 17.1 §10, 17.5 §9, and here.

11. Common Misconceptions

"A byte is eight bit slots." It is nine. §3.1.4 requires the master to generate the acknowledge pulse as well. §1.

"The ACK is bit 9 of the byte." It is a different ownership phase that occupies a clock pulse. Treating it as data means driving it from the shift register, during the slot the transmitter must release. §1.

"Reads and writes need separate datapaths." They are mirror images differing in where the ownership boundary falls. One mirrored expression, not two branches that may drift apart. §2.

"A sampled one in the ACK slot means the target said no." It means nobody pulled the line down. A NACK is the absence of a response, which is why a missing device and a refusing one are indistinguishable. §4.

"ACK polarity can be handled wherever it is needed." Then the inversion exists in several places and one of them is wrong. Keep it in one block, as with the SDA inversion. §4.

"The shift register only matters while receiving." It also records what actually reached the bus while transmitting, which is what arbitration reporting needs. §3.

"MSB-first is just a convention to get right." It also determines that a partially transmitted byte is left-justified, which is what a master must report after losing arbitration mid-byte. §3.

"Latch the byte when the slot index reaches seven." The index says which slot, not that it has finished. Latch on the completion pulse, or a stretched final bit loses the newest bit. §10.

"If the bits on the bus are right, the master read them right." The bus carried correct data and the master assembled it shifted by one. Wire-level correctness and assembled-value correctness are different claims. §10.

"A byte monitor can emit transactions." It cannot tell an address from data, or an error NACK from a required one, without framing context it does not own. §8.

12. Reason It Through

Why does the byte engine wrap the bit engine rather than reimplementing it?

Because the ninth slot differs from the eight in exactly one respect — who owns SDA. Clock, sampling instant and data rule are identical, so eight slots plus one reversed slot is the whole difference. §1.

A read returns eight correct bits and the target then behaves as though the transfer was abandoned. Which ownership mistake do you check?

The ninth slot. On a read the master must drive its own acknowledge there; if it released instead, the line floats high and the target reads a NACK and lets go — bits correct, transfer ended. §2.

Why is a sampled one in the acknowledge slot not a signal from the target?

Because it is the pull-up, not a device. A NACK is the absence of anyone pulling the line down, which is why a device that is absent and one that refuses look identical. §4.

What does MSB-first imply about a byte abandoned to arbitration loss?

That it is left-justified: the bits already transmitted occupy the high positions. That is the form a master must report when it loses mid-byte and has to retry. §3.

Mutation attempts one and two for M10 both survived for opposite reasons. What were they?

Setting tx_en alone is dead because tx_en is read only while active. Keeping active alone means continuing to release, which was going to happen anyway. Only setting both produces a master that actually holds the line. §7.

Why is "after byte_done, sda_req must be low" the wrong invariant?

Because the bit engine's deferred release is required: it holds SDA until SCL is low so that releasing cannot make the line rise during the high phase. The correct invariant waits for SCL to have been observed low. §7.

Why did the VHDL version of that check report a defect the SystemVerilog version did not?

Because it used variables, which update immediately, where the SystemVerilog used registers, which update on the next edge. The VHDL guard therefore evaluated one cycle earlier and caught the legitimate deferred-release cycle. §7.

Why can a bench with a non-stretching target not distinguish latching on bit_index == 7 from latching on bit_done?

Because without a stretch the sample point and the slot index advance together, so both conditions are true in the same cycle. The distinction only appears when the sample is delayed. §10.

13. Understanding Check

14. Summary

A byte is nine bit slots, and the ninth differs from the eight in exactly one respect: who owns SDA. Clock, sampling instant and data rule are identical, which is why this block wraps the bit engine instead of replacing it.

The acknowledge is an ownership phase, not a ninth data bit. Treating it as data means driving it from the shift register during the slot the transmitter must release.

Writes and reads are mirror images. The transmitter releases for slot 8 — the master on a write, the target on a read — and one mirrored expression is safer than two branches that can drift apart.

MSB first has a second consequence: a partially transmitted byte is left-justified, which is the form a master must report after losing arbitration mid-byte.

The engine shifts in the line even while transmitting, so the register records what actually reached the bus rather than what was intended.

ACK polarity is inverted and belongs in one place. A sampled zero is an acknowledge; a sampled one is the absence of anyone pulling the line down, which is why a missing device and a refusing one are indistinguishable.

Ten mutants, ten killed — and M10 needed three attempts to state, with the first two surviving as equivalent mutants for opposite reasons.

The real gap M10 exposed was invisible to every per-byte check, because the next byte overwrites the request, the gap sits in the low phase, and a held low even helps a following STOP.

Stating that invariant required allowing the deferred release. "No request after byte_done" is wrong; "no request once SCL has been observed low" is right — and the VHDL port needed signals rather than variables to evaluate it at the same instant.

Latch on the event, not the counter. bit_index == 7 says which slot is current, not that it is complete, and a stretched final bit loses its newest bit — a defect a non-stretching target model cannot expose.

15. What Comes Next

Bytes now move in both directions, with the acknowledge handled correctly at each end. What no block yet decides is which bytes, in what order, and what to do when one is refused.

Chapter 17.8 builds the transaction controller: the address byte and its direction bit, the payload count, the master's acknowledge policy on a read, the final NACK that ends it, and the decision to issue a STOP. It is also the first block that has to act on a refusal — and the specification does not say what a master should do with a NACK on a data byte mid-burst, so that becomes a stated design decision rather than a derivation.

Repeated START stays out of it. That is 17.9, for the reason this module has used throughout: one architectural responsibility per chapter.

Continue learning

Related tutorials