Skip to content
VLSI Mentor

I²C · Module 17

Transaction Control — Address, Payload and the ACK Policy

The one decision only a master can make, and it has to be made before the byte is clocked. Derives why a read length must be known in advance, why a NACK means different things in the two phases, and how a testbench that was labelled for a data NACK tested an address NACK twice and hid three defects.

Bytes move correctly in both directions now. Nothing yet decides which bytes, in what order, or what to do when one is refused.

Most of this block is bookkeeping — a byte counter and a direction bit. One part of it is a genuine protocol obligation that is genuinely awkward, and it constrains the host interface Chapter 17.2 built.

1. The Master ACK Policy, and Why It Forces a Length

So on a read, the master acknowledges every byte it wants another after, and not-acknowledges the last one. The NACK is how a master says stop sending.

Now the awkward part:

That is a real constraint on the interface rather than an implementation detail. It is why Chapter 17.2's command register carries a length, and it is why a "read until the device says stop" transaction is not expressible on this bus at all. The bus gives the master the decision and gives it no information on which to base it.

In RTL the policy is one expression evaluated at two points — once when the first data byte is issued, once for each continuation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first byte:     byte_ack_send <= (n_left != 1)      one byte left  -> NACK now
continuation:   byte_ack_send <= (n_left != 2)      two left       -> NACK the next

The two constants differ by one because n_left has not yet been decremented at the second point. That is the kind of off-by-one that a test written for the sequence catches and a test written for a single byte does not — mutation M2 below.

2. A NACK Means Different Things in the Two Phases

The block reports them as different failures, because Chapter 17.11's taxonomy needs them separated:

NACK onMeansNote
the address bytenothing is at that address, or the device is busyChapter 16.4 showed these are indistinguishable, so they share a code
a data bytethe device is there and declined this bytea write-protect pin, or a read-only location — Chapter 16.1 question 4

And only the write path can produce a data NACK at all. On a read the master sent the acknowledge itself, so there is nothing to check — which is worth stating because it means the data-NACK path is exercised by exactly one direction.

2a. A data NACK ends the transfer

The master stops sending and frames the bus. A master that carried on would be writing bytes the target has already refused — and Chapter 16.1 §6a argued why that is worse than stopping: the remaining bytes land somewhere the host never named, because the target's pointer and the master's intent have diverged.

So txn_bytes reports bytes actually moved, not bytes requested. A four-byte write refused at byte two moved one byte, and a host that cannot discover that has no idea what state the target's register map is in.

3. The Layering

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
host command   (17.2)
  -> transaction controller   this chapter: which byte, which direction, how many
     -> byte engine           (17.7)  nine slots, the ownership flip
        -> bit engine          (17.6)  one bit
           -> SCL gen (17.3) + SDA ctrl (17.4) + framer (17.5)

Each layer knows one thing the layer below does not. The byte engine does not know whether a byte is an address; this block does. This block does not know how long a phase is; the generator does.

A transaction with no data bytes is not a degenerate case to reject. It is how a driver probes whether anything is at an address at all: START, address, look at the acknowledge, STOP.

That is the bus-scan operation every I²C driver has, and a controller that treats a zero length as an error cannot perform it.

5. A Write and a Read, End to End

A four-byte read — the master's ACK policy across the payload

7 cycles
Seven intervals at byte resolution. The first is a start condition, the second the address byte with the read direction which the target acknowledges. The next four intervals each carry one data byte from the target; the master acknowledges the first three and not-acknowledges the fourth. The final interval is a stop condition. A row beneath shows the count of bytes remaining, going four, three, two, one, and the acknowledge decision for each byte.payload — master ACKspayload — master ACKsSTOPSTOPdirection bit: readdirection bit: readn_left is 1 — NACK decided firstn_left is 1 — NACK decidedfirstphaseSADDR+RD0D1D2D3Pwho ACKs0targetmastermastermastermastermastermaster sends00ACKACKACKNACKNACKn_left0443211t0t1t2t3t4t5t6
Figure 1 — a four-byte read, at byte resolution, showing where the master's acknowledge decision falls. The master ACKs bytes one to three and NACKs the fourth, and each decision was made before that byte was clocked. Conceptual figure: one interval per byte slot, not per clock.

Read the bottom two rows together. The acknowledge the master sends in byte D3's ninth slot was decided when n_left reached 1 — before D3 was clocked. Nothing about D3's contents influenced it, and nothing could have.

6. The Transaction Controller, in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_ctrl.sv — address phase, data phase, and the master ACK policy
   // -----------------------------------------------------------------------------
   // i2c_txn_ctrl.sv
   // Address phase, data phase, and the one decision only the master can make.
   //
   // THE MASTER ACK POLICY. Everything else in this block is bookkeeping; this is the part
   // that is a genuine protocol obligation and is genuinely awkward.
   //
   // §3.1.10 format 3: "If a master-receiver sends a repeated START condition, it sends a
   // not-acknowledge (A) just before the repeated START condition."
   //
   // So on a READ, the master acknowledges every byte it wants another after, and
   // NOT-acknowledges the last one. That decision has to be made BEFORE the byte is
   // clocked out, because the acknowledge is the ninth slot of the same byte -- there is no
   // opportunity to see the byte and then decide. A master reading N bytes therefore has to
   // know N in advance, or it has to read one byte too many and throw it away.
   //
   // That is a real constraint on the interface, not an implementation detail: it is why
   // the command register of Chapter 17.2 carries a LENGTH, and why a "read until the device
   // stops" transaction is not expressible on this bus at all.
   //
   // A NACK MEANS DIFFERENT THINGS IN THE TWO PHASES, and the block reports them as
   // different failures because Chapter 17.11's taxonomy needs them separated:
   //
   //   on the ADDRESS byte  nothing is at that address, or the device is busy. Chapter
   //                        16.4 shows those are indistinguishable, so they share a code.
   //   on a DATA byte       the device is there and declined this byte -- a write-protect
   //                        pin, or a read-only location (Chapter 16.1 question 4).
   //
   // AND A DATA NACK ENDS THE TRANSFER. The master stops sending and frames the bus. A
   // master that carried on would be writing bytes the target has already refused, which
   // Chapter 16.1 §6a argued is worse than stopping: the remaining bytes land somewhere
   // the host never named.
   //
   // THE CLOCK HANDOVER lives here too, because this is the only block that knows whether
   // the framer or the generator should own SCL at any instant. The handover overlaps by
   // one cycle in both directions -- see the `scl_yield` comment in i2c_framer.
   // -----------------------------------------------------------------------------

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

      // ---- from the host ------------------------------------------------------
      input  logic            cmd_valid,
      input  logic [6:0]      cmd_addr,
      input  logic            cmd_read,
      input  logic [3:0]      cmd_len,
      input  logic            cmd_stop,

      // ---- the framer ---------------------------------------------------------
      output logic            do_start,
      output logic            do_restart,
      output logic            do_stop,
      output logic            scl_yield,
      input  logic            frame_done,
      input  logic            frame_busy,
      input  logic            bus_free,
      // Is a transfer already open? If it is, this command is a CONTINUATION and needs a
      // repeated START rather than a START -- and must NOT wait for the bus to be free,
      // because this master is the one holding it. A controller that required `bus_free`
      // for every command can never issue the second phase of a combined transfer at all:
      // it waits forever for a bus it is itself holding.
      input  logic            frame_started,

      // ---- the clock generator ------------------------------------------------
      output logic           gen_enable,
      output logic            gen_idle_low,

      // ---- the byte engine ----------------------------------------------------
      output logic            byte_go,
      output logic            byte_dir_write,
      output logic [7:0]      byte_tx,
      output logic            byte_ack_send,
      input  logic            byte_done,
      input  logic            byte_busy,
      input  logic            byte_ack,
      input  logic [7:0]      byte_rx,

      // ---- the payload buffers ------------------------------------------------
      // `tx_index` names the byte to be sent NEXT, and is advanced when a byte is ISSUED
      // rather than when it completes. That ordering is not cosmetic: the buffer's output is
      // combinational from this index, so advancing it at completion means the very next
      // `byte_tx <= tx_data` in the same cycle reads the STALE byte -- and a four-byte write
      // sends the third byte twice and never sends the fourth.
      input  logic [7:0]      tx_data,
      output logic [3:0]      tx_index,
      output logic [7:0]      rx_data,
      output logic [3:0]      rx_index,
      output logic            rx_we,

      // ---- feedback -----------------------------------------------------------
      input  logic            arb_lost,

      // ---- results ------------------------------------------------------------
      output logic            txn_done,
      output logic            txn_ok,
      output logic [5:0]      txn_err,
      output logic [3:0]      txn_bytes,
      output logic            txn_busy,
      output logic            addr_nack,
      output logic            data_nack,
      output logic [3:0]      state,
      output logic [CNT_W-1:0] transactions
   );

      localparam integer E_ADDR_NACK = 0, E_DATA_NACK = 1, E_ARB_LOST = 2;

      localparam [3:0] S_IDLE   = 4'd0,
                       S_START  = 4'd1,   // framer is producing the START
                       S_ADDR   = 4'd2,   // clocking the address byte
                       S_AACK   = 4'd3,   // the address answer has arrived
                       S_DATA   = 4'd4,   // clocking a data byte
                       S_DACK   = 4'd5,   // the data answer has arrived
                       S_TOFRM  = 4'd6,   // taking the clock back from the generator
                       S_STOP   = 4'd7,   // framer is producing the STOP
                       S_DONE   = 4'd8,
                       S_ABORT  = 4'd9;   // arbitration lost: get off the bus

      logic [3:0] n_left;
      logic       is_read;
      logic       want_stop;
      // A separate receive pointer, because `tx_index` now runs one ahead of the byte in
      // flight and cannot double as the place to store a received byte.
      logic [3:0] rx_ptr;

      // 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.
      assign gen_enable = scl_yield && (byte_busy || byte_go);

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            state          <= S_IDLE;
            do_start       <= 1'b0;
            do_restart     <= 1'b0;
            do_stop        <= 1'b0;
            scl_yield      <= 1'b0;
            gen_idle_low   <= 1'b0;
            byte_go        <= 1'b0;
            byte_dir_write <= 1'b1;
            byte_tx        <= 8'h00;
            byte_ack_send  <= 1'b1;
            tx_index       <= 4'd0;
            rx_data        <= 8'h00;
            rx_index       <= 4'd0;
            rx_ptr         <= 4'd0;
            rx_we          <= 1'b0;
            txn_done       <= 1'b0;
            txn_ok         <= 1'b0;
            txn_err        <= 6'd0;
            txn_bytes      <= 4'd0;
            txn_busy       <= 1'b0;
            addr_nack      <= 1'b0;
            data_nack      <= 1'b0;
            n_left         <= 4'd0;
            is_read        <= 1'b0;
            want_stop      <= 1'b1;
            transactions   <= {CNT_W{1'b0}};
         end else begin
            do_start   <= 1'b0;
            do_restart <= 1'b0;
            do_stop    <= 1'b0;
            byte_go    <= 1'b0;
            rx_we      <= 1'b0;
            txn_done   <= 1'b0;
            addr_nack  <= 1'b0;
            data_nack  <= 1'b0;

            // Arbitration loss pre-empts everything except being idle. §3.1.8 obligation 2
            // is discharged by the bit engine's `abort`; this block's job is obligation 4,
            // which is to stop and retry later rather than to carry on.
            if (arb_lost && state != S_IDLE && state != S_DONE && state != S_ABORT) begin
               state <= S_ABORT;
            end else begin
               case (state)

                  S_IDLE: begin
                     txn_busy <= 1'b0;
                     if (cmd_valid && (bus_free || frame_started)) begin
                        is_read   <= cmd_read;
                        n_left    <= cmd_len;
                        want_stop <= cmd_stop;
                        byte_tx   <= {cmd_addr, cmd_read};
                        tx_index  <= 4'd0;
                        rx_index  <= 4'd0;
                        rx_ptr    <= 4'd0;
                        txn_bytes <= 4'd0;
                        txn_err   <= 6'd0;
                        txn_ok    <= 1'b0;
                        txn_busy  <= 1'b1;
                        // §3.1.10 format 3: a direction change within a transfer repeats
                        // the START, it does not STOP and start again. On a shared bus a
                        // STOP here would release the arbitration this master already won.
                        if (frame_started) do_restart <= 1'b1;
                        else               do_start   <= 1'b1;
                        state     <= S_START;
                     end
                  end

                  S_START: begin
                     if (frame_done) begin
                        // THE HANDOVER, forwards. Yielding and enabling the generator in
                        // the same cycle means the framer lets go of SCL and the generator
                        // takes it in the same clock edge, so the line never rises in
                        // between -- a rise there would be a clock pulse nobody meant.
                        scl_yield      <= 1'b1;
                        gen_idle_low   <= 1'b1;
                        byte_dir_write <= 1'b1;   // the address is always transmitted
                        byte_go        <= 1'b1;
                        state          <= S_ADDR;
                     end
                  end

                  S_ADDR: begin
                     if (byte_done) state <= S_AACK;
                  end

                  S_AACK: begin
                     if (!byte_ack) begin
                        // Nothing at that address, or a device too busy to answer. Chapter
                        // 16.4: indistinguishable, so one code.
                        addr_nack <= 1'b1;
                        txn_err   <= txn_err | (6'd1 << E_ADDR_NACK);
                        txn_ok    <= 1'b0;
                        state     <= S_TOFRM;
                     end else if (n_left == 4'd0) begin
                        // A zero-length transaction. Legal, and useful: it is how a driver
                        // probes whether anything is at an address at all.
                        txn_ok <= 1'b1;
                        state  <= S_TOFRM;
                     end else begin
                        byte_dir_write <= ~is_read;
                        byte_tx        <= tx_data;
                        tx_index       <= 4'd1;
                        // THE ACK POLICY, decided before the byte is clocked. On a read the
                        // master acknowledges every byte it wants another after and
                        // not-acknowledges the last, so with one byte left the answer is
                        // already a NACK.
                        byte_ack_send  <= (n_left != 4'd1);
                        byte_go        <= 1'b1;
                        state          <= S_DATA;
                     end
                  end

                  S_DATA: begin
                     if (byte_done) begin
                        if (is_read) begin
                           rx_data  <= byte_rx;
                           rx_index <= rx_ptr;
                           rx_ptr   <= rx_ptr + 4'd1;
                           rx_we    <= 1'b1;
                        end
                        state <= S_DACK;
                     end
                  end

                  S_DACK: begin
                     // On a WRITE the answer came from the target. On a READ the master
                     // sent the answer itself, so there is nothing to check -- which is why
                     // only the write path can produce a data NACK.
                     if (!is_read && !byte_ack) begin
                        data_nack <= 1'b1;
                        txn_err   <= txn_err | (6'd1 << E_DATA_NACK);
                        txn_ok    <= 1'b0;
                        // A refused byte ends the transfer. Carrying on would write bytes
                        // the target has already declined, and they would land somewhere
                        // the host never named.
                        state     <= S_TOFRM;
                     end else begin
                        txn_bytes <= txn_bytes + 4'd1;
                        if (n_left == 4'd1) begin
                           txn_ok <= 1'b1;
                           state  <= S_TOFRM;
                        end else begin
                           n_left        <= n_left - 4'd1;
                           byte_tx       <= tx_data;   // already the NEXT byte
                           tx_index      <= tx_index + 4'd1;
                           byte_ack_send <= (n_left != 4'd2);
                           byte_go       <= 1'b1;
                           state         <= S_DATA;
                        end
                     end
                  end

                  S_TOFRM: begin
                     // THE HANDOVER, backwards. The generator is parked holding SCL low, so
                     // the framer picks the hold up first and the generator lets go after.
                     // In that order the line never rises, which matters because the next
                     // thing the framer does is pull SDA low -- and SDA falling while SCL
                     // is high is a START, not the STOP that was intended.
                     scl_yield <= 1'b0;
                     if (!byte_busy) begin
                        gen_idle_low <= 1'b0;
                        if (want_stop) begin
                           do_stop <= 1'b1;
                           state   <= S_STOP;
                        end else begin
                           // Leave the bus held for a repeated START. Chapter 17.9 chains
                           // the next phase onto this one.
                           state <= S_DONE;
                        end
                     end
                  end

                  S_STOP: begin
                     if (frame_done) state <= S_DONE;
                  end

                  S_ABORT: begin
                     // §3.1.8 obligation 4: restart when the bus is free. This block
                     // reports the loss and stops; retrying is the host's decision, because
                     // only the host knows whether the transaction is still wanted.
                     scl_yield    <= 1'b0;
                     gen_idle_low <= 1'b0;
                     txn_err      <= txn_err | (6'd1 << E_ARB_LOST);
                     txn_ok       <= 1'b0;
                     state        <= S_DONE;
                  end

                  S_DONE: begin
                     txn_done     <= 1'b1;
                     txn_busy     <= 1'b0;
                     transactions <= transactions + 1'b1;
                     state        <= S_IDLE;
                  end

                  default: state <= S_IDLE;
               endcase
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_ctrl.v — the same design in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_txn_ctrl.sv
   // Address phase, data phase, and the one decision only the master can make.
   //
   // THE MASTER ACK POLICY. Everything else in this block is bookkeeping; this is the part
   // that is a genuine protocol obligation and is genuinely awkward.
   //
   // §3.1.10 format 3: "If a master-receiver sends a repeated START condition, it sends a
   // not-acknowledge (A) just before the repeated START condition."
   //
   // So on a READ, the master acknowledges every byte it wants another after, and
   // NOT-acknowledges the last one. That decision has to be made BEFORE the byte is
   // clocked out, because the acknowledge is the ninth slot of the same byte -- there is no
   // opportunity to see the byte and then decide. A master reading N bytes therefore has to
   // know N in advance, or it has to read one byte too many and throw it away.
   //
   // That is a real constraint on the interface, not an implementation detail: it is why
   // the command register of Chapter 17.2 carries a LENGTH, and why a "read until the device
   // stops" transaction is not expressible on this bus at all.
   //
   // A NACK MEANS DIFFERENT THINGS IN THE TWO PHASES, and the block reports them as
   // different failures because Chapter 17.11's taxonomy needs them separated:
   //
   //   on the ADDRESS byte  nothing is at that address, or the device is busy. Chapter
   //                        16.4 shows those are indistinguishable, so they share a code.
   //   on a DATA byte       the device is there and declined this byte -- a write-protect
   //                        pin, or a read-only location (Chapter 16.1 question 4).
   //
   // AND A DATA NACK ENDS THE TRANSFER. The master stops sending and frames the bus. A
   // master that carried on would be writing bytes the target has already refused, which
   // Chapter 16.1 §6a argued is worse than stopping: the remaining bytes land somewhere
   // the host never named.
   //
   // THE CLOCK HANDOVER lives here too, because this is the only block that knows whether
   // the framer or the generator should own SCL at any instant. The handover overlaps by
   // one cycle in both directions -- see the `scl_yield` comment in i2c_framer.
   // -----------------------------------------------------------------------------

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

      // ---- from the host ------------------------------------------------------
      input  wire            cmd_valid,
      input  wire [6:0]      cmd_addr,
      input  wire            cmd_read,
      input  wire [3:0]      cmd_len,
      input  wire            cmd_stop,

      // ---- the framer ---------------------------------------------------------
      output reg             do_start,
      output reg             do_restart,
      output reg             do_stop,
      output reg             scl_yield,
      input  wire            frame_done,
      input  wire            frame_busy,
      input  wire            bus_free,
      // Is a transfer already open? If it is, this command is a CONTINUATION and needs a
      // repeated START rather than a START -- and must NOT wait for the bus to be free,
      // because this master is the one holding it. A controller that required `bus_free`
      // for every command can never issue the second phase of a combined transfer at all:
      // it waits forever for a bus it is itself holding.
      input  wire            frame_started,

      // ---- the clock generator ------------------------------------------------
      output wire            gen_enable,
      output reg             gen_idle_low,

      // ---- the byte engine ----------------------------------------------------
      output reg             byte_go,
      output reg             byte_dir_write,
      output reg  [7:0]      byte_tx,
      output reg             byte_ack_send,
      input  wire            byte_done,
      input  wire            byte_busy,
      input  wire            byte_ack,
      input  wire [7:0]      byte_rx,

      // ---- the payload buffers ------------------------------------------------
      // `tx_index` names the byte to be sent NEXT, and is advanced when a byte is ISSUED
      // rather than when it completes. That ordering is not cosmetic: the buffer's output is
      // combinational from this index, so advancing it at completion means the very next
      // `byte_tx <= tx_data` in the same cycle reads the STALE byte -- and a four-byte write
      // sends the third byte twice and never sends the fourth.
      input  wire [7:0]      tx_data,
      output reg  [3:0]      tx_index,
      output reg  [7:0]      rx_data,
      output reg  [3:0]      rx_index,
      output reg             rx_we,

      // ---- feedback -----------------------------------------------------------
      input  wire            arb_lost,

      // ---- results ------------------------------------------------------------
      output reg             txn_done,
      output reg             txn_ok,
      output reg  [5:0]      txn_err,
      output reg  [3:0]      txn_bytes,
      output reg             txn_busy,
      output reg             addr_nack,
      output reg             data_nack,
      output reg  [3:0]      state,
      output reg  [CNT_W-1:0] transactions
   );

      localparam integer E_ADDR_NACK = 0, E_DATA_NACK = 1, E_ARB_LOST = 2;

      localparam [3:0] S_IDLE   = 4'd0,
                       S_START  = 4'd1,   // framer is producing the START
                       S_ADDR   = 4'd2,   // clocking the address byte
                       S_AACK   = 4'd3,   // the address answer has arrived
                       S_DATA   = 4'd4,   // clocking a data byte
                       S_DACK   = 4'd5,   // the data answer has arrived
                       S_TOFRM  = 4'd6,   // taking the clock back from the generator
                       S_STOP   = 4'd7,   // framer is producing the STOP
                       S_DONE   = 4'd8,
                       S_ABORT  = 4'd9;   // arbitration lost: get off the bus

      reg [3:0] n_left;
      reg       is_read;
      reg       want_stop;
      // A separate receive pointer, because `tx_index` now runs one ahead of the byte in
      // flight and cannot double as the place to store a received byte.
      reg [3:0] rx_ptr;

      // 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.
      assign gen_enable = scl_yield && (byte_busy || byte_go);

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            state          <= S_IDLE;
            do_start       <= 1'b0;
            do_restart     <= 1'b0;
            do_stop        <= 1'b0;
            scl_yield      <= 1'b0;
            gen_idle_low   <= 1'b0;
            byte_go        <= 1'b0;
            byte_dir_write <= 1'b1;
            byte_tx        <= 8'h00;
            byte_ack_send  <= 1'b1;
            tx_index       <= 4'd0;
            rx_data        <= 8'h00;
            rx_index       <= 4'd0;
            rx_ptr         <= 4'd0;
            rx_we          <= 1'b0;
            txn_done       <= 1'b0;
            txn_ok         <= 1'b0;
            txn_err        <= 6'd0;
            txn_bytes      <= 4'd0;
            txn_busy       <= 1'b0;
            addr_nack      <= 1'b0;
            data_nack      <= 1'b0;
            n_left         <= 4'd0;
            is_read        <= 1'b0;
            want_stop      <= 1'b1;
            transactions   <= {CNT_W{1'b0}};
         end else begin
            do_start   <= 1'b0;
            do_restart <= 1'b0;
            do_stop    <= 1'b0;
            byte_go    <= 1'b0;
            rx_we      <= 1'b0;
            txn_done   <= 1'b0;
            addr_nack  <= 1'b0;
            data_nack  <= 1'b0;

            // Arbitration loss pre-empts everything except being idle. §3.1.8 obligation 2
            // is discharged by the bit engine's `abort`; this block's job is obligation 4,
            // which is to stop and retry later rather than to carry on.
            if (arb_lost && state != S_IDLE && state != S_DONE && state != S_ABORT) begin
               state <= S_ABORT;
            end else begin
               case (state)

                  S_IDLE: begin
                     txn_busy <= 1'b0;
                     if (cmd_valid && (bus_free || frame_started)) begin
                        is_read   <= cmd_read;
                        n_left    <= cmd_len;
                        want_stop <= cmd_stop;
                        byte_tx   <= {cmd_addr, cmd_read};
                        tx_index  <= 4'd0;
                        rx_index  <= 4'd0;
                        rx_ptr    <= 4'd0;
                        txn_bytes <= 4'd0;
                        txn_err   <= 6'd0;
                        txn_ok    <= 1'b0;
                        txn_busy  <= 1'b1;
                        // §3.1.10 format 3: a direction change within a transfer repeats
                        // the START, it does not STOP and start again. On a shared bus a
                        // STOP here would release the arbitration this master already won.
                        if (frame_started) do_restart <= 1'b1;
                        else               do_start   <= 1'b1;
                        state     <= S_START;
                     end
                  end

                  S_START: begin
                     if (frame_done) begin
                        // THE HANDOVER, forwards. Yielding and enabling the generator in
                        // the same cycle means the framer lets go of SCL and the generator
                        // takes it in the same clock edge, so the line never rises in
                        // between -- a rise there would be a clock pulse nobody meant.
                        scl_yield      <= 1'b1;
                        gen_idle_low   <= 1'b1;
                        byte_dir_write <= 1'b1;   // the address is always transmitted
                        byte_go        <= 1'b1;
                        state          <= S_ADDR;
                     end
                  end

                  S_ADDR: begin
                     if (byte_done) state <= S_AACK;
                  end

                  S_AACK: begin
                     if (!byte_ack) begin
                        // Nothing at that address, or a device too busy to answer. Chapter
                        // 16.4: indistinguishable, so one code.
                        addr_nack <= 1'b1;
                        txn_err   <= txn_err | (6'd1 << E_ADDR_NACK);
                        txn_ok    <= 1'b0;
                        state     <= S_TOFRM;
                     end else if (n_left == 4'd0) begin
                        // A zero-length transaction. Legal, and useful: it is how a driver
                        // probes whether anything is at an address at all.
                        txn_ok <= 1'b1;
                        state  <= S_TOFRM;
                     end else begin
                        byte_dir_write <= ~is_read;
                        byte_tx        <= tx_data;
                        tx_index       <= 4'd1;
                        // THE ACK POLICY, decided before the byte is clocked. On a read the
                        // master acknowledges every byte it wants another after and
                        // not-acknowledges the last, so with one byte left the answer is
                        // already a NACK.
                        byte_ack_send  <= (n_left != 4'd1);
                        byte_go        <= 1'b1;
                        state          <= S_DATA;
                     end
                  end

                  S_DATA: begin
                     if (byte_done) begin
                        if (is_read) begin
                           rx_data  <= byte_rx;
                           rx_index <= rx_ptr;
                           rx_ptr   <= rx_ptr + 4'd1;
                           rx_we    <= 1'b1;
                        end
                        state <= S_DACK;
                     end
                  end

                  S_DACK: begin
                     // On a WRITE the answer came from the target. On a READ the master
                     // sent the answer itself, so there is nothing to check -- which is why
                     // only the write path can produce a data NACK.
                     if (!is_read && !byte_ack) begin
                        data_nack <= 1'b1;
                        txn_err   <= txn_err | (6'd1 << E_DATA_NACK);
                        txn_ok    <= 1'b0;
                        // A refused byte ends the transfer. Carrying on would write bytes
                        // the target has already declined, and they would land somewhere
                        // the host never named.
                        state     <= S_TOFRM;
                     end else begin
                        txn_bytes <= txn_bytes + 4'd1;
                        if (n_left == 4'd1) begin
                           txn_ok <= 1'b1;
                           state  <= S_TOFRM;
                        end else begin
                           n_left        <= n_left - 4'd1;
                           byte_tx       <= tx_data;   // already the NEXT byte
                           tx_index      <= tx_index + 4'd1;
                           byte_ack_send <= (n_left != 4'd2);
                           byte_go       <= 1'b1;
                           state         <= S_DATA;
                        end
                     end
                  end

                  S_TOFRM: begin
                     // THE HANDOVER, backwards. The generator is parked holding SCL low, so
                     // the framer picks the hold up first and the generator lets go after.
                     // In that order the line never rises, which matters because the next
                     // thing the framer does is pull SDA low -- and SDA falling while SCL
                     // is high is a START, not the STOP that was intended.
                     scl_yield <= 1'b0;
                     if (!byte_busy) begin
                        gen_idle_low <= 1'b0;
                        if (want_stop) begin
                           do_stop <= 1'b1;
                           state   <= S_STOP;
                        end else begin
                           // Leave the bus held for a repeated START. Chapter 17.9 chains
                           // the next phase onto this one.
                           state <= S_DONE;
                        end
                     end
                  end

                  S_STOP: begin
                     if (frame_done) state <= S_DONE;
                  end

                  S_ABORT: begin
                     // §3.1.8 obligation 4: restart when the bus is free. This block
                     // reports the loss and stops; retrying is the host's decision, because
                     // only the host knows whether the transaction is still wanted.
                     scl_yield    <= 1'b0;
                     gen_idle_low <= 1'b0;
                     txn_err      <= txn_err | (6'd1 << E_ARB_LOST);
                     txn_ok       <= 1'b0;
                     state        <= S_DONE;
                  end

                  S_DONE: begin
                     txn_done     <= 1'b1;
                     txn_busy     <= 1'b0;
                     transactions <= transactions + 1'b1;
                     state        <= S_IDLE;
                  end

                  default: state <= S_IDLE;
               endcase
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_ctrl.vhd — the same design in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_txn_ctrl.vhd
   -- Address phase, data phase, and the one decision only the master can make.
   -- Behavioural twin of i2c_txn_ctrl.sv / .v.
   --
   -- THE MASTER ACK POLICY. Everything else here is bookkeeping; this is the part that is a
   -- genuine protocol obligation and is genuinely awkward.
   --
   -- §3.1.10 format 3: "If a master-receiver sends a repeated START condition, it sends a
   -- not-acknowledge (A) just before the repeated START condition."
   --
   -- So on a READ the master acknowledges every byte it wants another after, and NOT-acknowledges
   -- the last one -- and that decision has to be made BEFORE the byte is clocked, because the
   -- acknowledge is the ninth slot of the same byte. A master reading N bytes therefore has to
   -- know N in advance, or read one byte too many and throw it away. That is a real constraint on
   -- the interface, not an implementation detail: it is why Chapter 17.2's command register
   -- carries a LENGTH, and why "read until the device stops" is not expressible on this bus.
   --
   -- A NACK MEANS DIFFERENT THINGS IN THE TWO PHASES, reported as different failures:
   --   on the ADDRESS byte  nothing is at that address, or the device is busy (Chapter 16.4
   --                        shows those are indistinguishable, so they share a code)
   --   on a DATA byte       the device is there and declined this byte
   --
   -- AND A DATA NACK ENDS THE TRANSFER, because carrying on would write bytes the target has
   -- already refused -- and they would land somewhere the host never named.
   --
   -- THE CLOCK HANDOVER lives here, because this is the only block that knows whether the framer
   -- or the generator should own SCL at any instant.
   -- ---------------------------------------------------------------------------

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

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

         cmd_valid : in std_logic;
         cmd_addr  : in std_logic_vector(6 downto 0);
         cmd_read  : in std_logic;
         cmd_len   : in unsigned(3 downto 0);
         cmd_stop  : in std_logic;

         do_start   : out std_logic;
         do_restart : out std_logic;
         do_stop    : out std_logic;
         scl_yield  : out std_logic;
         frame_done : in  std_logic;
         frame_busy : in  std_logic;
         bus_free   : in  std_logic;
         -- Is a transfer already open? If it is, this command is a CONTINUATION and needs a
         -- repeated START rather than a START -- and must NOT wait for the bus to be free,
         -- because this master is the one holding it. A controller that required `bus_free` for
         -- every command can never issue the second phase of a combined transfer at all: it
         -- waits forever for a bus it is itself holding.
         frame_started : in std_logic;

         gen_enable   : out std_logic;
         gen_idle_low : out std_logic;

         byte_go        : out std_logic;
         byte_dir_write : out std_logic;
         byte_tx        : out std_logic_vector(7 downto 0);
         byte_ack_send  : out std_logic;
         byte_done      : in  std_logic;
         byte_busy      : in  std_logic;
         byte_ack       : in  std_logic;
         byte_rx        : in  std_logic_vector(7 downto 0);

         -- `tx_index` names the byte to be sent NEXT, and is advanced when a byte is ISSUED
         -- rather than when it completes. That ordering is not cosmetic: the buffer's output is
         -- combinational from this index, so advancing it at completion means the very next
         -- `byte_tx <= tx_data` in the same cycle reads the STALE byte -- and a four-byte write
         -- sends the third byte twice and never sends the fourth.
         tx_data  : in  std_logic_vector(7 downto 0);
         tx_index : out unsigned(3 downto 0);
         rx_data  : out std_logic_vector(7 downto 0);
         rx_index : out unsigned(3 downto 0);
         rx_we    : out std_logic;

         arb_lost : in std_logic;

         txn_done     : out std_logic;
         txn_ok       : out std_logic;
         txn_err      : out std_logic_vector(5 downto 0);
         txn_bytes    : out unsigned(3 downto 0);
         txn_busy     : out std_logic;
         addr_nack    : out std_logic;
         data_nack    : out std_logic;
         state        : out unsigned(3 downto 0);
         transactions : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_txn_ctrl;

   architecture rtl of i2c_txn_ctrl is

      constant E_ADDR_NACK : integer := 0;
      constant E_DATA_NACK : integer := 1;
      constant E_ARB_LOST  : integer := 2;

      constant S_IDLE  : integer := 0;
      constant S_START : integer := 1;
      constant S_ADDR  : integer := 2;
      constant S_AACK  : integer := 3;
      constant S_DATA  : integer := 4;
      constant S_DACK  : integer := 5;
      constant S_TOFRM : integer := 6;
      constant S_STOP  : integer := 7;
      constant S_DONE  : integer := 8;
      constant S_ABORT : integer := 9;

      signal st       : integer range 0 to 9 := S_IDLE;
      signal n_left   : unsigned(3 downto 0) := (others => '0');
      signal is_read  : std_logic := '0';
      signal want_stp : std_logic := '1';
      -- A separate receive pointer, because `tx_index` now runs one ahead of the byte in flight
      -- and cannot double as the place to store a received byte.
      signal rx_ptr   : unsigned(3 downto 0) := (others => '0');

      signal yld, gil, bsy : std_logic := '0';
      signal e_i    : std_logic_vector(5 downto 0) := (others => '0');
      signal nb     : unsigned(3 downto 0) := (others => '0');
      signal txi    : unsigned(3 downto 0) := (others => '0');
      signal n_txn  : unsigned(CNT_W-1 downto 0) := (others => '0');

   begin

      scl_yield    <= yld;
      gen_idle_low <= gil;
      txn_busy     <= bsy;
      txn_err      <= e_i;
      txn_bytes    <= nb;
      tx_index     <= txi;
      state        <= to_unsigned(st, 4);
      transactions <= n_txn;

      -- 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 <= yld and (byte_busy or byte_go);

      process (clk, rst_n)
      begin
         if rst_n = '0' then
            st             <= S_IDLE;
            do_start       <= '0';
            do_restart     <= '0';
            do_stop        <= '0';
            yld            <= '0';
            gil            <= '0';
            byte_go        <= '0';
            byte_dir_write <= '1';
            byte_tx        <= (others => '0');
            byte_ack_send  <= '1';
            txi            <= (others => '0');
            rx_data        <= (others => '0');
            rx_index       <= (others => '0');
            rx_ptr         <= (others => '0');
            rx_we          <= '0';
            txn_done       <= '0';
            txn_ok         <= '0';
            e_i            <= (others => '0');
            nb             <= (others => '0');
            bsy            <= '0';
            addr_nack      <= '0';
            data_nack      <= '0';
            n_left         <= (others => '0');
            is_read        <= '0';
            want_stp       <= '1';
            n_txn          <= (others => '0');
         elsif rising_edge(clk) then
            do_start   <= '0';
            do_restart <= '0';
            do_stop    <= '0';
            byte_go    <= '0';
            rx_we      <= '0';
            txn_done   <= '0';
            addr_nack  <= '0';
            data_nack  <= '0';

            -- Arbitration loss pre-empts everything except being idle. §3.1.8 obligation 2 is
            -- discharged by the bit engine's `abort`; this block's job is obligation 4, which is
            -- to stop and retry later rather than to carry on.
            if arb_lost = '1' and st /= S_IDLE and st /= S_DONE and st /= S_ABORT then
               st <= S_ABORT;
            else
               case st is

                  when S_IDLE =>
                     bsy <= '0';
                     if cmd_valid = '1' and (bus_free = '1' or frame_started = '1') then
                        is_read  <= cmd_read;
                        n_left   <= cmd_len;
                        want_stp <= cmd_stop;
                        byte_tx  <= cmd_addr & cmd_read;
                        txi      <= (others => '0');
                        rx_index <= (others => '0');
                        rx_ptr   <= (others => '0');
                        nb       <= (others => '0');
                        e_i      <= (others => '0');
                        txn_ok   <= '0';
                        bsy      <= '1';
                        -- §3.1.10 format 3: a direction change within a transfer REPEATS the
                        -- START, it does not STOP and start again. On a shared bus a STOP here
                        -- would release the arbitration this master already won.
                        if frame_started = '1' then do_restart <= '1';
                        else                        do_start   <= '1';
                        end if;
                        st <= S_START;
                     end if;

                  when S_START =>
                     if frame_done = '1' then
                        -- THE HANDOVER, forwards. Yielding and enabling the generator in the
                        -- same cycle means the framer lets go of SCL and the generator takes it
                        -- at the same clock edge, so the line never rises in between.
                        yld            <= '1';
                        gil            <= '1';
                        byte_dir_write <= '1';   -- the address is always transmitted
                        byte_go        <= '1';
                        st             <= S_ADDR;
                     end if;

                  when S_ADDR =>
                     if byte_done = '1' then st <= S_AACK; end if;

                  when S_AACK =>
                     if byte_ack = '0' then
                        -- Nothing at that address, or a device too busy to answer.
                        addr_nack <= '1';
                        e_i(E_ADDR_NACK) <= '1';
                        txn_ok    <= '0';
                        st        <= S_TOFRM;
                     elsif n_left = 0 then
                        -- A zero-length transaction. Legal, and useful: it is how a driver
                        -- probes whether anything is at an address at all.
                        txn_ok <= '1';
                        st     <= S_TOFRM;
                     else
                        byte_dir_write <= not is_read;
                        byte_tx        <= tx_data;
                        txi            <= to_unsigned(1, 4);
                        -- THE ACK POLICY, decided before the byte is clocked. With one byte left
                        -- the answer is already a NACK.
                        if n_left /= 1 then byte_ack_send <= '1';
                        else                byte_ack_send <= '0';
                        end if;
                        byte_go <= '1';
                        st      <= S_DATA;
                     end if;

                  when S_DATA =>
                     if byte_done = '1' then
                        if is_read = '1' then
                           rx_data  <= byte_rx;
                           rx_index <= rx_ptr;
                           rx_ptr   <= rx_ptr + 1;
                           rx_we    <= '1';
                        end if;
                        st <= S_DACK;
                     end if;

                  when S_DACK =>
                     -- On a WRITE the answer came from the target. On a READ the master sent the
                     -- answer itself, so there is nothing to check -- which is why only the
                     -- write path can produce a data NACK.
                     if is_read = '0' and byte_ack = '0' then
                        data_nack <= '1';
                        e_i(E_DATA_NACK) <= '1';
                        txn_ok    <= '0';
                        st        <= S_TOFRM;
                     else
                        nb <= nb + 1;
                        if n_left = 1 then
                           txn_ok <= '1';
                           st     <= S_TOFRM;
                        else
                           n_left  <= n_left - 1;
                           byte_tx <= tx_data;      -- already the NEXT byte
                           txi     <= txi + 1;
                           if n_left /= 2 then byte_ack_send <= '1';
                           else                byte_ack_send <= '0';
                           end if;
                           byte_go <= '1';
                           st      <= S_DATA;
                        end if;
                     end if;

                  when S_TOFRM =>
                     -- THE HANDOVER, backwards. The generator is parked holding SCL low, so the
                     -- framer picks the hold up first and the generator lets go after. In that
                     -- order the line never rises, which matters because the next thing the
                     -- framer does is pull SDA low -- and SDA falling while SCL is high is a
                     -- START, not the STOP that was intended.
                     yld <= '0';
                     if byte_busy = '0' then
                        gil <= '0';
                        if want_stp = '1' then
                           do_stop <= '1';
                           st      <= S_STOP;
                        else
                           -- Leave the bus held for a repeated START.
                           st <= S_DONE;
                        end if;
                     end if;

                  when S_STOP =>
                     if frame_done = '1' then st <= S_DONE; end if;

                  when S_ABORT =>
                     -- §3.1.8 obligation 4: restart when the bus is free. This block reports the
                     -- loss and stops; retrying is the host's decision, because only the host
                     -- knows whether the transaction is still wanted.
                     yld    <= '0';
                     gil    <= '0';
                     e_i(E_ARB_LOST) <= '1';
                     txn_ok <= '0';
                     st     <= S_DONE;

                  when S_DONE =>
                     txn_done <= '1';
                     bsy      <= '0';
                     n_txn    <= n_txn + 1;
                     st       <= S_IDLE;

                  when others =>
                     st <= S_IDLE;

               end case;
            end if;
         end if;
      end process;

   end architecture rtl;

6a. The testbenches

Twelve tests, and the bench carries two target models: one that acknowledges normally at 0x50, and one at 0x52 that acknowledges its address and then refuses its second data byte. The second one was added after mutation testing; see §7.

#TestProperty
T1a single-byte write, end to end from one host command
T2a multi-byte write, payload in order
T3an address NACK at 0x51reported as its own code
T4a zero-length transactionlegal, and how a driver probes an address
T5a single-byte read from the target's own memory
T6the ACK policy as a sequencefour bytes: ACK, ACK, ACK, NACK
T7a one-byte read NACKs immediatelythe decision precedes the byte
T8a data NACK ends the write, and is a different coderewritten; see §7
T9a stretching targetthe transaction still works, data intact
T10no STOP, for a combined transactionthe bus is left held
T11and then a second phase on the same transaction
T12the invariants over every transactionnobody fought for SDA, no stray framing
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_ctrl_tb.sv — the self-checking testbench
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_txn_ctrl_tb.sv
   // Independent oracle for i2c_txn_ctrl, driving a complete master against a pin-level target.
   //
   // This is the first bench in the module that issues a whole transaction from a host
   // command and checks it on the wire. Everything below the controller is the real thing:
   // the framer, the generator, the bit and byte engines, the SDA owner. The target finds
   // its own edges, and the protocol monitor sees only the two lines.
   //
   // The four tests that matter most are the ones about the ACK POLICY, because that is the
   // only decision in the block that the specification actually constrains -- §3.1.10
   // format 3 requires the master-receiver to NACK the last byte -- and it has to be made
   // one byte ahead of the evidence.
   // -----------------------------------------------------------------------------
   module i2c_txn_ctrl_tb;

      localparam integer NL = 8, NH = 4, NSU = 2, NSMP = 2;
      localparam integer NHD = 3, NSUA = 3, NSUO = 3, NBUF = 3;
      localparam [6:0]   TADDR = 7'h50;
      localparam [3:0]   S_IDLE = 4'd0, S_DONE = 4'd8;

      logic clk = 1'b0, rst_n = 1'b0;
      logic cmd_valid = 1'b0, cmd_read = 1'b0, cmd_stop = 1'b1;
      logic [6:0] cmd_addr = TADDR;
      logic [3:0] cmd_len = 4'd1;
      logic [7:0] payload [0:7];

      // ---- the master ---------------------------------------------------------
      logic do_start, do_restart, do_stop, scl_yield, gen_enable, gen_idle_low;
      logic byte_go, byte_dir_write, byte_ack_send;
      logic [7:0] byte_tx;
      logic [3:0] tx_index, rx_index, tstate;
      logic [7:0] rx_data;
      logic rx_we, txn_done, txn_ok, txn_busy, addr_nack, data_nack;
      logic [5:0] txn_err;
      logic [3:0] txn_bytes;
      logic [15:0] n_txn;

      logic g_scl_low, drive_point, sample_point, g_rise, g_fall, g_stretch;
      logic [15:0] g_scyc, 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, b_busy, b_ack, b_ackv, b_done;
      logic [7:0] b_rx;
      logic [3:0] b_bidx;
      logic [15:0] b_bytes, b_acks, b_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_conf;
      logic [3:0] grant;
      logic [15:0] n_conf, n_arb;

      wire m_scl_low = f_scl_low | g_scl_low;

      logic t_scl_low, t_sda_low, 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; reg [7:0] load_addr = 8'h00, load_data = 8'h00;
      logic tgt_hold_scl = 1'b0;

      // Which target to talk to in a given test: the acknowledging one, or one configured
      // to refuse. Both sit on the bus; only one of them owns TADDR at a time, selected by
      // the parameterised address of the second instance.
      localparam [6:0] TADDR_NACKDATA = 7'h52;
      logic t2_scl_low, t2_sda_low, t2_sel, t2_dirrd, t2_wv;
      logic [7:0] t2_lw;
      logic [15:0] t2_rx, t2_tx, t2_nsta, t2_nsto;
      logic [3:0]  t2_st;

      i2c_line_model #(.N_DEV(4)) bus (
         .scl_drive_low({t2_scl_low, tgt_hold_scl, t_scl_low, m_scl_low}),
         .sda_drive_low({t2_sda_low, 1'b0,         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_scyc),
         .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)) u_by (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .go(byte_go), .dir_write(byte_dir_write), .tx_byte(byte_tx),
         .ack_to_send(byte_ack_send),
         .sda_in(sda_in[0]), .scl_high(scl_in[0]), .abort(arb_lost),
         .sda_req(b_sda_req), .sda_bit(b_sda_bit),
         .rx_byte(b_rx), .ack(b_ack), .ack_valid(b_ackv), .byte_done(b_done),
         .busy(b_busy), .bit_index(b_bidx), .driving(b_driving),
         .bytes_done(b_bytes), .acks(b_acks), .nacks(b_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_conf), .conflicts(n_conf),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(n_arb),
         .arb_clear(txn_done));

      i2c_txn_ctrl #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .cmd_valid(cmd_valid), .cmd_addr(cmd_addr), .cmd_read(cmd_read),
         .cmd_len(cmd_len), .cmd_stop(cmd_stop),
         .do_start(do_start), .do_restart(do_restart), .do_stop(do_stop),
         .scl_yield(scl_yield), .frame_done(f_done), .frame_busy(f_busy),
         .bus_free(f_bus_free), .frame_started(f_started),
         .gen_enable(gen_enable), .gen_idle_low(gen_idle_low),
         .byte_go(byte_go), .byte_dir_write(byte_dir_write), .byte_tx(byte_tx),
         .byte_ack_send(byte_ack_send), .byte_done(b_done), .byte_busy(b_busy),
         .byte_ack(b_ack), .byte_rx(b_rx),
         .tx_data(payload[tx_index[2:0]]), .tx_index(tx_index),
         .rx_data(rx_data), .rx_index(rx_index), .rx_we(rx_we),
         .arb_lost(arb_lost),
         .txn_done(txn_done), .txn_ok(txn_ok), .txn_err(txn_err),
         .txn_bytes(txn_bytes), .txn_busy(txn_busy),
         .addr_nack(addr_nack), .data_nack(data_nack),
         .state(tstate), .transactions(n_txn));

      logic t_sel, t_dirrd, t_wv;
      logic [7:0] t_lw;
      logic [15:0] t_rx, t_tx, t_nsta, t_nsto;
      logic [2:0] t_st;

      // A SECOND target, which acknowledges its own address and then REFUSES its second
      // data byte. Without it there is no way to produce a data NACK at all, and the
      // address-NACK target at 0x51 cannot stand in: an address NACK and a data NACK are
      // different codes reported at different points, which is the property T8 exists for.
      i2c_target_model #(.MY_ADDR(TADDR_NACKDATA), .ACK_ADDR(1'b1), .STRETCH_AFTER(0),
                         .NACK_AT(2), .N_MEM(16), .CNT_W(16)) u_tgt2 (
         .clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
         .scl_drive_low(t2_scl_low), .sda_drive_low(t2_sda_low),
         .load_en(1'b0), .load_addr(4'd0), .load_data(8'h00),
         .selected(t2_sel), .dir_read(t2_dirrd), .last_written(t2_lw), .write_valid(t2_wv),
         .bytes_rx(t2_rx), .bytes_tx(t2_tx), .n_starts(t2_nsta), .n_stops(t2_nsto),
         .state(t2_st));

      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_lw), .write_valid(t_wv),
         .bytes_rx(t_rx), .bytes_tx(t_tx), .n_starts(t_nsta), .n_stops(t_nsto),
         .state(t_st));

      logic m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv, m_intr, m_mid;
      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;

      // Record the acknowledge value of every byte the monitor sees, so the ACK POLICY can
      // be checked as a SEQUENCE rather than one byte at a time.
      logic [15:0] ack_hist;
      integer n_acks_seen;
      logic [7:0] rxlog [0:7];
      integer n_rx;

      always @(negedge clk) begin
         if (rst_n) begin
            if (m_ack) begin
               ack_hist = {ack_hist[14:0], m_ackv};
               n_acks_seen = n_acks_seen + 1;
            end
            // Index the log by the DUT's own rx_index rather than by a bench counter,
            // so a controller that writes a received byte to the wrong buffer slot is
            // caught. With a private counter the index is never checked at all.
            if (rx_we) begin rxlog[rx_index[2:0]] = rx_data; n_rx = n_rx + 1; end
         end
      end

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

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; cmd_valid = 1'b0; cmd_read = 1'b0; cmd_stop = 1'b1;
            cmd_addr = TADDR; cmd_len = 4'd1; load_en = 1'b0; tgt_hold_scl = 1'b0;
            ack_hist = 16'h0000; n_acks_seen = 0; n_rx = 0;
            for (k = 0; k < 8; k = k + 1) payload[k] = 8'h00;
            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 issue (input [6:0] a, input rd, input [3:0] len, input stp);
         begin
            @(negedge clk);
            cmd_addr = a; cmd_read = rd; cmd_len = len; cmd_stop = stp; cmd_valid = 1'b1;
            @(posedge clk); @(negedge clk); cmd_valid = 1'b0;
         end
      endtask

      task wait_txn (input integer max_cycles);
         begin
            n = 0;
            while (!txn_done && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_txn: stuck in state %0d (byte bit %0d)", tstate, b_bidx);
               errors = errors + 1;
            end
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d (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

      // `addr_nack` and `data_nack` are ONE-CYCLE pulses, cleared every cycle by default.
      // Sampling them after a transaction completes therefore always reads zero -- so the
      // bench latches them. This is also the only thing that verifies the pulses at all:
      // without it a controller that sets the error bits and never pulses would pass.
      reg saw_addr_nack = 1'b0, saw_data_nack = 1'b0;
      always @(posedge clk) begin
         if (!rst_n) begin saw_addr_nack <= 1'b0; saw_data_nack <= 1'b0; end
         else begin
            if (addr_nack) saw_addr_nack <= 1'b1;
            if (data_nack) saw_data_nack <= 1'b1;
         end
      end

      initial begin
         $display("=== i2c_txn_ctrl: phases, and the decision made one byte ahead ===");

         // ----------------------------------------------------------------
         // T1. A single-byte write, end to end from one host command.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'h5A;
         issue(TADDR, 1'b0, 4'd1, 1'b1);
         wait_txn(3000);
         $display("T1  a one-byte write, from a host command to the wire and back");
         ck_bit("T1 succeeded", txn_ok, 1'b1);
         ck_int("T1 one byte moved", txn_bytes, 1);
         ck_int("T1 no errors", txn_err, 0);
         ck_int("T1 the target received it", t_lw, 8'h5A);
         ck_int("T1 the monitor saw two bytes: address and data", m_nbyte, 2);
         ck_int("T1 one START and one STOP", m_nsta + m_nsto, 2);
         ck_bit("T1 the bus is idle", m_intr, 1'b0);

         // ----------------------------------------------------------------
         // T2. A multi-byte write, and the payload arrives in order.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 4; k = k + 1) payload[k] = 8'hB0 + k[7:0];
         issue(TADDR, 1'b0, 4'd4, 1'b1);
         wait_txn(6000);
         $display("T2  a four-byte write, delivered in order");
         ck_bit("T2 succeeded", txn_ok, 1'b1);
         ck_int("T2 four bytes moved", txn_bytes, 4);
         ck_int("T2 the target received four", t_rx, 4);
         ck_int("T2 the last of them was 0xB3", t_lw, 8'hB3);
         ck_int("T2 five bytes on the wire: address plus four", m_nbyte, 5);

         // ----------------------------------------------------------------
         // T3. AN ADDRESS NACK. Nothing is at 0x51, and the controller must report that
         //     without sending any data.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'hFF;
         issue(7'h51, 1'b0, 4'd2, 1'b1);
         wait_txn(3000);
         $display("T3  an address NACK stops the transaction before any data is sent");
         ck_bit("T3 did not succeed", txn_ok, 1'b0);
         ck_bit("T3 reported as an address NACK", txn_err[0], 1'b1);
         ck_bit("T3 and not as a data NACK", txn_err[1], 1'b0);
         ck_int("T3 zero bytes moved", txn_bytes, 0);
         ck_int("T3 only the address was on the wire", m_nbyte, 1);
         ck_int("T3 and the bus was still framed properly", m_nsto, 1);

         // ----------------------------------------------------------------
         // T4. A ZERO-LENGTH TRANSACTION is legal and useful: it is how a driver probes
         //     whether anything is at an address at all.
         // ----------------------------------------------------------------
         do_reset;
         issue(TADDR, 1'b0, 4'd0, 1'b1);
         wait_txn(3000);
         $display("T4  a zero-length transaction is an address probe, and succeeds");
         ck_bit("T4 succeeded", txn_ok, 1'b1);
         ck_int("T4 no bytes moved", txn_bytes, 0);
         ck_int("T4 one byte on the wire: just the address", m_nbyte, 1);
         // `selected` is cleared by the STOP, so the durable evidence is the acknowledge
         // the target put on the wire in answer to the address.
         ck_int("T4 two framing events and one acknowledge slot", n_acks_seen, 1);
         ck_bit("T4 and the target acknowledged the address", ack_hist[0], 1'b0);

         // ----------------------------------------------------------------
         // T5. A single-byte READ, from the target's own memory.
         // ----------------------------------------------------------------
         do_reset;
         preload(8'h00, 8'h3C);
         issue(TADDR, 1'b1, 4'd1, 1'b1);
         wait_txn(3000);
         $display("T5  a one-byte read returns the target's data");
         ck_bit("T5 succeeded", txn_ok, 1'b1);
         ck_int("T5 one byte moved", txn_bytes, 1);
         ck_int("T5 and it was stored", rxlog[0], 8'h3C);
         ck_bit("T5 the target knew it was a read", t_dirrd, 1'b1);

         // ----------------------------------------------------------------
         // T6. THE ACK POLICY, as a sequence. Reading four bytes, the master must
         //     acknowledge the first three and NOT-acknowledge the fourth. §3.1.10 format 3.
         //     The acknowledge values are read off the WIRE, where a 0 is an ACK.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 4; k = k + 1) preload(k[7:0], 8'h70 + k[7:0]);
         issue(TADDR, 1'b1, 4'd4, 1'b1);
         wait_txn(6000);
         $display("T6  reading four bytes: ACK, ACK, ACK, NACK -- read off the wire");
         ck_int("T6 five acknowledge slots: address plus four data", n_acks_seen, 5);
         // The history is shifted in, so the most recent is bit 0. Address ACK is bit 4.
         ck_bit("T6 the address was acknowledged by the target", ack_hist[4], 1'b0);
         ck_bit("T6 data byte 1 acknowledged by the master", ack_hist[3], 1'b0);
         ck_bit("T6 data byte 2 acknowledged", ack_hist[2], 1'b0);
         ck_bit("T6 data byte 3 acknowledged", ack_hist[1], 1'b0);
         ck_bit("T6 and data byte 4 NOT acknowledged", ack_hist[0], 1'b1);
         ck_int("T6 four bytes were stored", n_rx, 4);
         ck_int("T6 the first", rxlog[0], 8'h70);
         ck_int("T6 the last", rxlog[3], 8'h73);

         // ----------------------------------------------------------------
         // T7. A ONE-BYTE READ NACKS IMMEDIATELY. The decision is made before the byte is
         //     clocked, so with one byte requested the very first answer is a NACK -- there
         //     is no opportunity to see the byte and then decide.
         // ----------------------------------------------------------------
         do_reset;
         preload(8'h00, 8'h99);
         issue(TADDR, 1'b1, 4'd1, 1'b1);
         wait_txn(3000);
         $display("T7  a one-byte read not-acknowledges its only byte");
         ck_int("T7 two acknowledge slots", n_acks_seen, 2);
         ck_bit("T7 the address was acknowledged", ack_hist[1], 1'b0);
         ck_bit("T7 and the single data byte was not", ack_hist[0], 1'b1);
         ck_int("T7 the byte still arrived", rxlog[0], 8'h99);

         // ----------------------------------------------------------------
         // T8. A DATA NACK ends the write, and is a DIFFERENT code from an address NACK.
         //     The target here acknowledges its address and refuses the second data byte.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 4; k = k + 1) payload[k] = 8'hD0 + k[7:0];
         // The refusing target ACKNOWLEDGES its address and then NACKs its SECOND data
         // byte, so this exercises the data-NACK path rather than the address-NACK path.
         issue(TADDR_NACKDATA, 1'b0, 4'd4, 1'b1);
         wait_txn(3000);
         ck_bit("T8 a data NACK sets bit 1", txn_err[1], 1'b1);
         ck_bit("T8 and leaves the address-NACK bit clear", txn_err[0], 1'b0);
         ck_bit("T8 the transfer did not succeed", txn_ok, 1'b0);
         ck_bit("T8 and it pulsed data_nack exactly once", saw_data_nack, 1'b1);
         ck_bit("T8 and never pulsed addr_nack", saw_addr_nack, 1'b0);
         // A refused byte ENDS the transfer: one byte was accepted, the second refused,
         // and bytes three and four must never have been sent.
         ck_int("T8 only the accepted byte counted", txn_bytes, 1);
         ck_int("T8 the target received exactly two bytes", t2_rx, 2);
         $display("T8  a data NACK and an address NACK are different codes");

         // And the contrast, on the same checks: an address NACK sets the OTHER bit.
         do_reset;
         issue(7'h51, 1'b0, 4'd4, 1'b1);
         wait_txn(3000);
         ck_bit("T8 an address NACK sets bit 0", txn_err[0], 1'b1);
         ck_bit("T8 and leaves the data-NACK bit clear", txn_err[1], 1'b0);
         ck_int("T8 nothing was transferred", txn_bytes, 0);
         ck_bit("T8 and that one pulsed addr_nack", saw_addr_nack, 1'b1);
         ck_bit("T8 and not data_nack", saw_data_nack, 1'b0);

         // ----------------------------------------------------------------
         // T9. A STRETCHING TARGET. The whole transaction still works and the data is
         //     intact; only the duration changes. Nothing in the controller counts cycles.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'h77;
         issue(TADDR, 1'b0, 4'd1, 1'b1);
         // Hold SCL for a while in the middle of the transfer.
         n = 0;
         while (m_nbyte < 1 && n < 3000) begin step; n = n + 1; end
         @(negedge clk); tgt_hold_scl = 1'b1;
         for (k = 0; k < 40; k = k + 1) step;
         @(negedge clk); tgt_hold_scl = 1'b0;
         wait_txn(6000);
         $display("T9  a stretching bus changes the duration and nothing else");
         ck_bit("T9 still succeeded", txn_ok, 1'b1);
         ck_int("T9 the byte arrived intact", t_lw, 8'h77);
         // Fewer than the forty cycles the bench held SCL, and correctly so: part of the
         // hold overlaps the generator's OWN low phase, during which it is driving SCL low
         // itself and is not waiting for anybody. Only the cycles spent released-and-low
         // are a stretch.
         if (g_scyc < 20) begin
            $display("  FAIL T9 only %0d stretch cycles were counted", g_scyc);
            errors = errors + 1;
         end
         ck_int("T9 and no spurious errors", txn_err, 0);

         // ----------------------------------------------------------------
         // T10. NO STOP, for a combined transaction. The controller leaves the bus HELD so
         //      Chapter 17.9 can chain a repeated START onto it.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'h11;
         issue(TADDR, 1'b0, 4'd1, 1'b0);   // cmd_stop = 0
         wait_txn(3000);
         $display("T10 with no STOP requested, the bus is left held for a repeated START");
         ck_bit("T10 succeeded", txn_ok, 1'b1);
         ck_int("T10 no STOP was emitted", m_nsto, 0);
         ck_bit("T10 the transfer is still open", m_intr, 1'b1);
         ck_bit("T10 and the framer still says it started", f_started, 1'b1);

         // ----------------------------------------------------------------
         // T11. AND THEN A SECOND PHASE ON THE SAME TRANSACTION. Issuing another command
         //      without an intervening STOP produces a repeated START -- which is what
         //      Chapter 17.9 formalises, demonstrated here as the controller's own
         //      composition.
         // ----------------------------------------------------------------
         preload(8'h00, 8'hE5);
         issue(TADDR, 1'b1, 4'd1, 1'b1);
         wait_txn(4000);
         $display("T11 a second phase without a STOP in between");
         ck_bit("T11 the read succeeded", txn_ok, 1'b1);
         ck_int("T11 two STARTs on the wire", m_nsta, 2);
         ck_int("T11 and exactly one STOP, at the end", m_nsto, 1);
         ck_int("T11 the read byte arrived", rxlog[n_rx-1], 8'hE5);
         ck_bit("T11 the bus is idle now", m_intr, 1'b0);
         ck_int("T11 no mid-byte framing at all", m_nmid, 0);

         // ----------------------------------------------------------------
         // T12. THE INVARIANTS, over every transaction so far: nobody fought for SDA, no
         //      spurious arbitration losses, and the transaction count matches.
         // ----------------------------------------------------------------
         $display("T12 no owner conflicts and no spurious arbitration losses, throughout");
         ck_int("T12 no SDA owner conflicts", n_conf, 0);
         ck_int("T12 no arbitration losses on a single-master bus", n_arb, 0);
         ck_int("T12 two transactions since reset", n_txn, 2);
         ck_bit("T12 idle", txn_busy, 1'b0);
         ck_int("T12 and the controller is back at rest", tstate, S_IDLE);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_ctrl_tb.v — the same tests in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_txn_ctrl_tb.sv
   // Independent oracle for i2c_txn_ctrl, driving a complete master against a pin-level target.
   //
   // This is the first bench in the module that issues a whole transaction from a host
   // command and checks it on the wire. Everything below the controller is the real thing:
   // the framer, the generator, the bit and byte engines, the SDA owner. The target finds
   // its own edges, and the protocol monitor sees only the two lines.
   //
   // The four tests that matter most are the ones about the ACK POLICY, because that is the
   // only decision in the block that the specification actually constrains -- §3.1.10
   // format 3 requires the master-receiver to NACK the last byte -- and it has to be made
   // one byte ahead of the evidence.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_txn_ctrl_tb;

      localparam integer NL = 8, NH = 4, NSU = 2, NSMP = 2;
      localparam integer NHD = 3, NSUA = 3, NSUO = 3, NBUF = 3;
      localparam [6:0]   TADDR = 7'h50;
      localparam [3:0]   S_IDLE = 4'd0, S_DONE = 4'd8;

      reg clk = 1'b0, rst_n = 1'b0;
      reg cmd_valid = 1'b0, cmd_read = 1'b0, cmd_stop = 1'b1;
      reg [6:0] cmd_addr = TADDR;
      reg [3:0] cmd_len = 4'd1;
      reg [7:0] payload [0:7];

      // ---- the master ---------------------------------------------------------
      wire do_start, do_restart, do_stop, scl_yield, gen_enable, gen_idle_low;
      wire byte_go, byte_dir_write, byte_ack_send;
      wire [7:0] byte_tx;
      wire [3:0] tx_index, rx_index, tstate;
      wire [7:0] rx_data;
      wire rx_we, txn_done, txn_ok, txn_busy, addr_nack, data_nack;
      wire [5:0] txn_err;
      wire [3:0] txn_bytes;
      wire [15:0] n_txn;

      wire g_scl_low, drive_point, sample_point, g_rise, g_fall, g_stretch;
      wire [15:0] g_scyc, 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, b_busy, b_ack, b_ackv, b_done;
      wire [7:0] b_rx;
      wire [3:0] b_bidx;
      wire [15:0] b_bytes, b_acks, b_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_conf;
      wire [3:0] grant;
      wire [15:0] n_conf, n_arb;

      wire m_scl_low = f_scl_low | g_scl_low;

      wire t_scl_low, t_sda_low, 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;
      reg tgt_hold_scl = 1'b0;

      // Which target to talk to in a given test: the acknowledging one, or one configured
      // to refuse. Both sit on the bus; only one of them owns TADDR at a time, selected by
      // the parameterised address of the second instance.
      // A SECOND target, which acknowledges its own address and then REFUSES its second
      // data byte. Without it there is no way to produce a data NACK at all, and the
      // address-NACK target at 0x51 cannot stand in: an address NACK and a data NACK are
      // different codes reported at different points, which is the property T8 exists for.
      localparam [6:0] TADDR_NACKDATA = 7'h52;
      wire t2_scl_low, t2_sda_low, t2_sel, t2_dirrd, t2_wv;
      wire [7:0] t2_lw;
      wire [15:0] t2_rx, t2_tx, t2_nsta, t2_nsto;
      wire [3:0]  t2_st;

      i2c_line_model #(.N_DEV(4)) bus (
         .scl_drive_low({t2_scl_low, tgt_hold_scl, t_scl_low, m_scl_low}),
         .sda_drive_low({t2_sda_low, 1'b0,         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_scyc),
         .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)) u_by (
         .clk(clk), .rst_n(rst_n),
         .drive_point(drive_point), .sample_point(sample_point),
         .go(byte_go), .dir_write(byte_dir_write), .tx_byte(byte_tx),
         .ack_to_send(byte_ack_send),
         .sda_in(sda_in[0]), .scl_high(scl_in[0]), .abort(arb_lost),
         .sda_req(b_sda_req), .sda_bit(b_sda_bit),
         .rx_byte(b_rx), .ack(b_ack), .ack_valid(b_ackv), .byte_done(b_done),
         .busy(b_busy), .bit_index(b_bidx), .driving(b_driving),
         .bytes_done(b_bytes), .acks(b_acks), .nacks(b_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_conf), .conflicts(n_conf),
         .arb_loss_now(arb_now), .arb_lost(arb_lost), .arb_losses(n_arb),
         .arb_clear(txn_done));

      i2c_txn_ctrl #(.CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n),
         .cmd_valid(cmd_valid), .cmd_addr(cmd_addr), .cmd_read(cmd_read),
         .cmd_len(cmd_len), .cmd_stop(cmd_stop),
         .do_start(do_start), .do_restart(do_restart), .do_stop(do_stop),
         .scl_yield(scl_yield), .frame_done(f_done), .frame_busy(f_busy),
         .bus_free(f_bus_free), .frame_started(f_started),
         .gen_enable(gen_enable), .gen_idle_low(gen_idle_low),
         .byte_go(byte_go), .byte_dir_write(byte_dir_write), .byte_tx(byte_tx),
         .byte_ack_send(byte_ack_send), .byte_done(b_done), .byte_busy(b_busy),
         .byte_ack(b_ack), .byte_rx(b_rx),
         .tx_data(payload[tx_index[2:0]]), .tx_index(tx_index),
         .rx_data(rx_data), .rx_index(rx_index), .rx_we(rx_we),
         .arb_lost(arb_lost),
         .txn_done(txn_done), .txn_ok(txn_ok), .txn_err(txn_err),
         .txn_bytes(txn_bytes), .txn_busy(txn_busy),
         .addr_nack(addr_nack), .data_nack(data_nack),
         .state(tstate), .transactions(n_txn));

      wire t_sel, t_dirrd, t_wv;
      wire [7:0] t_lw;
      wire [15:0] t_rx, t_tx, t_nsta, t_nsto;
      wire [2:0] t_st;

      i2c_target_model #(.MY_ADDR(TADDR_NACKDATA), .ACK_ADDR(1'b1), .STRETCH_AFTER(0),
                         .NACK_AT(2), .N_MEM(16), .CNT_W(16)) u_tgt2 (
         .clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
         .scl_drive_low(t2_scl_low), .sda_drive_low(t2_sda_low),
         .load_en(1'b0), .load_addr(4'd0), .load_data(8'h00),
         .selected(t2_sel), .dir_read(t2_dirrd), .last_written(t2_lw), .write_valid(t2_wv),
         .bytes_rx(t2_rx), .bytes_tx(t2_tx), .n_starts(t2_nsta), .n_stops(t2_nsto),
         .state(t2_st));

      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_lw), .write_valid(t_wv),
         .bytes_rx(t_rx), .bytes_tx(t_tx), .n_starts(t_nsta), .n_stops(t_nsto),
         .state(t_st));

      wire m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv, m_intr, m_mid;
      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;

      // Record the acknowledge value of every byte the monitor sees, so the ACK POLICY can
      // be checked as a SEQUENCE rather than one byte at a time.
      reg [15:0] ack_hist;
      integer n_acks_seen;
      reg [7:0] rxlog [0:7];
      integer n_rx;

      always @(negedge clk) begin
         if (rst_n) begin
            if (m_ack) begin
               ack_hist = {ack_hist[14:0], m_ackv};
               n_acks_seen = n_acks_seen + 1;
            end
            // Index the log by the DUT's own rx_index rather than by a bench counter,
            // so a controller that writes a received byte to the wrong slot is caught.
            if (rx_we) begin rxlog[rx_index[2:0]] = rx_data; n_rx = n_rx + 1; end
         end
      end

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

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; cmd_valid = 1'b0; cmd_read = 1'b0; cmd_stop = 1'b1;
            cmd_addr = TADDR; cmd_len = 4'd1; load_en = 1'b0; tgt_hold_scl = 1'b0;
            ack_hist = 16'h0000; n_acks_seen = 0; n_rx = 0;
            for (k = 0; k < 8; k = k + 1) payload[k] = 8'h00;
            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 issue (input [6:0] a, input rd, input [3:0] len, input stp);
         begin
            @(negedge clk);
            cmd_addr = a; cmd_read = rd; cmd_len = len; cmd_stop = stp; cmd_valid = 1'b1;
            @(posedge clk); @(negedge clk); cmd_valid = 1'b0;
         end
      endtask

      task wait_txn (input integer max_cycles);
         begin
            n = 0;
            while (!txn_done && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_txn: stuck in state %0d (byte bit %0d)", tstate, b_bidx);
               errors = errors + 1;
            end
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d (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

      // `addr_nack` and `data_nack` are ONE-CYCLE pulses, cleared every cycle by default.
      // Sampling them after a transaction completes therefore always reads zero -- so the
      // bench latches them. This is also the only thing that verifies the pulses at all:
      // without it a controller that sets the error bits and never pulses would pass.
      reg saw_addr_nack, saw_data_nack;
      always @(posedge clk) begin
         if (!rst_n) begin saw_addr_nack <= 1'b0; saw_data_nack <= 1'b0; end
         else begin
            if (addr_nack) saw_addr_nack <= 1'b1;
            if (data_nack) saw_data_nack <= 1'b1;
         end
      end

      initial begin
         $display("=== i2c_txn_ctrl: phases, and the decision made one byte ahead ===");

         // ----------------------------------------------------------------
         // T1. A single-byte write, end to end from one host command.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'h5A;
         issue(TADDR, 1'b0, 4'd1, 1'b1);
         wait_txn(3000);
         $display("T1  a one-byte write, from a host command to the wire and back");
         ck_bit("T1 succeeded", txn_ok, 1'b1);
         ck_int("T1 one byte moved", txn_bytes, 1);
         ck_int("T1 no errors", txn_err, 0);
         ck_int("T1 the target received it", t_lw, 8'h5A);
         ck_int("T1 the monitor saw two bytes: address and data", m_nbyte, 2);
         ck_int("T1 one START and one STOP", m_nsta + m_nsto, 2);
         ck_bit("T1 the bus is idle", m_intr, 1'b0);

         // ----------------------------------------------------------------
         // T2. A multi-byte write, and the payload arrives in order.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 4; k = k + 1) payload[k] = 8'hB0 + k[7:0];
         issue(TADDR, 1'b0, 4'd4, 1'b1);
         wait_txn(6000);
         $display("T2  a four-byte write, delivered in order");
         ck_bit("T2 succeeded", txn_ok, 1'b1);
         ck_int("T2 four bytes moved", txn_bytes, 4);
         ck_int("T2 the target received four", t_rx, 4);
         ck_int("T2 the last of them was 0xB3", t_lw, 8'hB3);
         ck_int("T2 five bytes on the wire: address plus four", m_nbyte, 5);

         // ----------------------------------------------------------------
         // T3. AN ADDRESS NACK. Nothing is at 0x51, and the controller must report that
         //     without sending any data.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'hFF;
         issue(7'h51, 1'b0, 4'd2, 1'b1);
         wait_txn(3000);
         $display("T3  an address NACK stops the transaction before any data is sent");
         ck_bit("T3 did not succeed", txn_ok, 1'b0);
         ck_bit("T3 reported as an address NACK", txn_err[0], 1'b1);
         ck_bit("T3 and not as a data NACK", txn_err[1], 1'b0);
         ck_int("T3 zero bytes moved", txn_bytes, 0);
         ck_int("T3 only the address was on the wire", m_nbyte, 1);
         ck_int("T3 and the bus was still framed properly", m_nsto, 1);

         // ----------------------------------------------------------------
         // T4. A ZERO-LENGTH TRANSACTION is legal and useful: it is how a driver probes
         //     whether anything is at an address at all.
         // ----------------------------------------------------------------
         do_reset;
         issue(TADDR, 1'b0, 4'd0, 1'b1);
         wait_txn(3000);
         $display("T4  a zero-length transaction is an address probe, and succeeds");
         ck_bit("T4 succeeded", txn_ok, 1'b1);
         ck_int("T4 no bytes moved", txn_bytes, 0);
         ck_int("T4 one byte on the wire: just the address", m_nbyte, 1);
         // `selected` is cleared by the STOP, so the durable evidence is the acknowledge
         // the target put on the wire in answer to the address.
         ck_int("T4 two framing events and one acknowledge slot", n_acks_seen, 1);
         ck_bit("T4 and the target acknowledged the address", ack_hist[0], 1'b0);

         // ----------------------------------------------------------------
         // T5. A single-byte READ, from the target's own memory.
         // ----------------------------------------------------------------
         do_reset;
         preload(8'h00, 8'h3C);
         issue(TADDR, 1'b1, 4'd1, 1'b1);
         wait_txn(3000);
         $display("T5  a one-byte read returns the target's data");
         ck_bit("T5 succeeded", txn_ok, 1'b1);
         ck_int("T5 one byte moved", txn_bytes, 1);
         ck_int("T5 and it was stored", rxlog[0], 8'h3C);
         ck_bit("T5 the target knew it was a read", t_dirrd, 1'b1);

         // ----------------------------------------------------------------
         // T6. THE ACK POLICY, as a sequence. Reading four bytes, the master must
         //     acknowledge the first three and NOT-acknowledge the fourth. §3.1.10 format 3.
         //     The acknowledge values are read off the WIRE, where a 0 is an ACK.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 4; k = k + 1) preload(k[7:0], 8'h70 + k[7:0]);
         issue(TADDR, 1'b1, 4'd4, 1'b1);
         wait_txn(6000);
         $display("T6  reading four bytes: ACK, ACK, ACK, NACK -- read off the wire");
         ck_int("T6 five acknowledge slots: address plus four data", n_acks_seen, 5);
         // The history is shifted in, so the most recent is bit 0. Address ACK is bit 4.
         ck_bit("T6 the address was acknowledged by the target", ack_hist[4], 1'b0);
         ck_bit("T6 data byte 1 acknowledged by the master", ack_hist[3], 1'b0);
         ck_bit("T6 data byte 2 acknowledged", ack_hist[2], 1'b0);
         ck_bit("T6 data byte 3 acknowledged", ack_hist[1], 1'b0);
         ck_bit("T6 and data byte 4 NOT acknowledged", ack_hist[0], 1'b1);
         ck_int("T6 four bytes were stored", n_rx, 4);
         ck_int("T6 the first", rxlog[0], 8'h70);
         ck_int("T6 the last", rxlog[3], 8'h73);

         // ----------------------------------------------------------------
         // T7. A ONE-BYTE READ NACKS IMMEDIATELY. The decision is made before the byte is
         //     clocked, so with one byte requested the very first answer is a NACK -- there
         //     is no opportunity to see the byte and then decide.
         // ----------------------------------------------------------------
         do_reset;
         preload(8'h00, 8'h99);
         issue(TADDR, 1'b1, 4'd1, 1'b1);
         wait_txn(3000);
         $display("T7  a one-byte read not-acknowledges its only byte");
         ck_int("T7 two acknowledge slots", n_acks_seen, 2);
         ck_bit("T7 the address was acknowledged", ack_hist[1], 1'b0);
         ck_bit("T7 and the single data byte was not", ack_hist[0], 1'b1);
         ck_int("T7 the byte still arrived", rxlog[0], 8'h99);

         // ----------------------------------------------------------------
         // T8. A DATA NACK ends the write, and is a DIFFERENT code from an address NACK.
         //     The target here acknowledges its address and refuses the second data byte.
         // ----------------------------------------------------------------
         do_reset;
         for (k = 0; k < 4; k = k + 1) payload[k] = 8'hD0 + k[7:0];
         // The refusing target ACKNOWLEDGES its address and then NACKs its SECOND data
         // byte, so this exercises the data-NACK path rather than the address-NACK path.
         issue(TADDR_NACKDATA, 1'b0, 4'd4, 1'b1);
         wait_txn(3000);
         ck_bit("T8 a data NACK sets bit 1", txn_err[1], 1'b1);
         ck_bit("T8 and leaves the address-NACK bit clear", txn_err[0], 1'b0);
         ck_bit("T8 the transfer did not succeed", txn_ok, 1'b0);
         ck_bit("T8 and it pulsed data_nack exactly once", saw_data_nack, 1'b1);
         ck_bit("T8 and never pulsed addr_nack", saw_addr_nack, 1'b0);
         // A refused byte ENDS the transfer: one byte was accepted, the second refused,
         // and bytes three and four must never have been sent.
         ck_int("T8 only the accepted byte counted", txn_bytes, 1);
         ck_int("T8 the target received exactly two bytes", t2_rx, 2);
         $display("T8  a data NACK and an address NACK are different codes");

         // And the contrast, on the same checks: an address NACK sets the OTHER bit.
         do_reset;
         issue(7'h51, 1'b0, 4'd4, 1'b1);
         wait_txn(3000);
         ck_bit("T8 an address NACK sets bit 0", txn_err[0], 1'b1);
         ck_bit("T8 and leaves the data-NACK bit clear", txn_err[1], 1'b0);
         ck_int("T8 nothing was transferred", txn_bytes, 0);
         ck_bit("T8 and that one pulsed addr_nack", saw_addr_nack, 1'b1);
         ck_bit("T8 and not data_nack", saw_data_nack, 1'b0);

         // ----------------------------------------------------------------
         // T9. A STRETCHING TARGET. The whole transaction still works and the data is
         //     intact; only the duration changes. Nothing in the controller counts cycles.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'h77;
         issue(TADDR, 1'b0, 4'd1, 1'b1);
         // Hold SCL for a while in the middle of the transfer.
         n = 0;
         while (m_nbyte < 1 && n < 3000) begin step; n = n + 1; end
         @(negedge clk); tgt_hold_scl = 1'b1;
         for (k = 0; k < 40; k = k + 1) step;
         @(negedge clk); tgt_hold_scl = 1'b0;
         wait_txn(6000);
         $display("T9  a stretching bus changes the duration and nothing else");
         ck_bit("T9 still succeeded", txn_ok, 1'b1);
         ck_int("T9 the byte arrived intact", t_lw, 8'h77);
         // Fewer than the forty cycles the bench held SCL, and correctly so: part of the
         // hold overlaps the generator's OWN low phase, during which it is driving SCL low
         // itself and is not waiting for anybody. Only the cycles spent released-and-low
         // are a stretch.
         if (g_scyc < 20) begin
            $display("  FAIL T9 only %0d stretch cycles were counted", g_scyc);
            errors = errors + 1;
         end
         ck_int("T9 and no spurious errors", txn_err, 0);

         // ----------------------------------------------------------------
         // T10. NO STOP, for a combined transaction. The controller leaves the bus HELD so
         //      Chapter 17.9 can chain a repeated START onto it.
         // ----------------------------------------------------------------
         do_reset;
         payload[0] = 8'h11;
         issue(TADDR, 1'b0, 4'd1, 1'b0);   // cmd_stop = 0
         wait_txn(3000);
         $display("T10 with no STOP requested, the bus is left held for a repeated START");
         ck_bit("T10 succeeded", txn_ok, 1'b1);
         ck_int("T10 no STOP was emitted", m_nsto, 0);
         ck_bit("T10 the transfer is still open", m_intr, 1'b1);
         ck_bit("T10 and the framer still says it started", f_started, 1'b1);

         // ----------------------------------------------------------------
         // T11. AND THEN A SECOND PHASE ON THE SAME TRANSACTION. Issuing another command
         //      without an intervening STOP produces a repeated START -- which is what
         //      Chapter 17.9 formalises, demonstrated here as the controller's own
         //      composition.
         // ----------------------------------------------------------------
         preload(8'h00, 8'hE5);
         issue(TADDR, 1'b1, 4'd1, 1'b1);
         wait_txn(4000);
         $display("T11 a second phase without a STOP in between");
         ck_bit("T11 the read succeeded", txn_ok, 1'b1);
         ck_int("T11 two STARTs on the wire", m_nsta, 2);
         ck_int("T11 and exactly one STOP, at the end", m_nsto, 1);
         ck_int("T11 the read byte arrived", rxlog[n_rx-1], 8'hE5);
         ck_bit("T11 the bus is idle now", m_intr, 1'b0);
         ck_int("T11 no mid-byte framing at all", m_nmid, 0);

         // ----------------------------------------------------------------
         // T12. THE INVARIANTS, over every transaction so far: nobody fought for SDA, no
         //      spurious arbitration losses, and the transaction count matches.
         // ----------------------------------------------------------------
         $display("T12 no owner conflicts and no spurious arbitration losses, throughout");
         ck_int("T12 no SDA owner conflicts", n_conf, 0);
         ck_int("T12 no arbitration losses on a single-master bus", n_arb, 0);
         ck_int("T12 two transactions since reset", n_txn, 2);
         ck_bit("T12 idle", txn_busy, 1'b0);
         ck_int("T12 and the controller is back at rest", tstate, S_IDLE);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_ctrl_tb.vhd — the same tests in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_txn_ctrl_tb.vhd
   -- Independent oracle for i2c_txn_ctrl, driving a complete master against a pin-level target.
   -- Behavioural twin of the SV and Verilog benches.
   --
   -- This is the first bench in the module that issues a whole transaction from a host command
   -- and checks it on the wire. Everything below the controller is the real thing: the framer,
   -- the generator, the bit and byte engines, the SDA owner. The target finds its own edges, and
   -- the protocol monitor sees only the two lines.
   --
   -- The tests that matter most are about the ACK POLICY, because that is the only decision in
   -- the block the specification actually constrains -- §3.1.10 format 3 requires the
   -- master-receiver to NACK the last byte -- and it has to be made one byte ahead of the
   -- evidence.
   -- ---------------------------------------------------------------------------

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

   entity i2c_txn_ctrl_tb is
   end entity i2c_txn_ctrl_tb;

   architecture sim of i2c_txn_ctrl_tb is

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

      signal clk, rst_n : std_logic := '0';
      signal cmd_valid, cmd_read, cmd_stop : std_logic := '0';
      signal cmd_addr : std_logic_vector(6 downto 0) := TADDR;
      signal cmd_len : unsigned(3 downto 0) := to_unsigned(1, 4);

      type pay_t is array (0 to 7) of std_logic_vector(7 downto 0);
      signal payload : pay_t := (others => (others => '0'));

      signal do_start, do_restart, do_stop, scl_yield, gen_enable, gen_idle_low : std_logic;
      signal byte_go, byte_dir_write, byte_ack_send : std_logic;
      signal byte_tx : std_logic_vector(7 downto 0);
      signal tx_index, rx_index, tstate, txn_bytes : unsigned(3 downto 0);
      signal rx_data : std_logic_vector(7 downto 0);
      signal rx_we, txn_done, txn_ok, txn_busy, addr_nack, data_nack : std_logic;
      signal txn_err : std_logic_vector(5 downto 0);
      signal n_txn : unsigned(15 downto 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 b_rx : std_logic_vector(7 downto 0);
      signal b_bidx : unsigned(3 downto 0);
      signal b_bytes, b_acks, b_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_conf : std_logic;
      signal n_conf, n_arb : unsigned(15 downto 0);

      signal m_scl_low : std_logic;
      signal tgt_hold_scl : std_logic := '0';

      signal t_scl_low, t_sda_low : std_logic;
      signal scl_drv, sda_drv : std_logic_vector(3 downto 0);

      -- A SECOND target, which acknowledges its own address and then REFUSES its second
      -- data byte. Without it there is no way to produce a data NACK at all, and the
      -- address-NACK target at 0x51 cannot stand in: an address NACK and a data NACK are
      -- different codes reported at different points, which is what T8 exists for.
      constant TADDR_NACKDATA : std_logic_vector(6 downto 0) := "1010010";  -- 0x52
      signal t2_scl_low, t2_sda_low, t2_sel, t2_dirrd, t2_wv : std_logic;
      signal t2_lw : std_logic_vector(7 downto 0);
      signal t2_rx, t2_tx, t2_nsta, t2_nsto : unsigned(15 downto 0);
      signal t2_st : unsigned(2 downto 0);

      -- addr_nack and data_nack are ONE-CYCLE pulses, cleared every cycle by default, so
      -- sampling them after a transaction always reads zero. Latching them is also the
      -- only thing that verifies the pulses at all.
      signal saw_addr_nack, saw_data_nack : std_logic := '0';
      signal scl, sda : std_logic;
      signal scl_in, sda_in, scl_rbl, sda_rbl : std_logic_vector(3 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_wv : std_logic;
      signal t_lw : std_logic_vector(7 downto 0);
      signal t_rx, t_tx, t_nsta, t_nsto : unsigned(15 downto 0);
      signal t_st : 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);

      -- Records the acknowledge value of every byte the monitor sees, so the ACK POLICY can be
      -- checked as a SEQUENCE rather than one byte at a time.
      signal ack_hist : std_logic_vector(15 downto 0) := (others => '0');
      signal n_acks_seen, n_rx : integer := 0;
      type log_t is array (0 to 7) of std_logic_vector(7 downto 0);
      signal rxlog : log_t := (others => (others => '0'));


      -- A metavalue-safe index. At time zero, before any reset has propagated, an unsigned
      -- signal still reads 'U' -- and `to_integer` on that emits a NUMERIC_STD warning and
      -- returns 0 anyway. Converting explicitly keeps the transcript clean and says what is
      -- meant: an index that is not yet valid selects slot zero, which nothing reads.
      function safe_idx (v : unsigned) return integer is
      begin
         for i in v'range loop
            if v(i) /= '0' and v(i) /= '1' then return 0; end if;
         end loop;
         return to_integer(v);
      end function;

      signal halt : boolean := false;

   begin

      m_scl_low <= f_scl_low or g_scl_low;
      scl_drv   <= t2_scl_low & tgt_hold_scl & t_scl_low & m_scl_low;
      sda_drv   <= t2_sda_low & '0' & t_sda_low & m_sda_low;

      bus_m : entity work.i2c_line_model
         generic map (N_DEV => 4)
         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);

      u_by : 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 => byte_go, dir_write => byte_dir_write, tx_byte => byte_tx,
            ack_to_send => byte_ack_send,
            sda_in => sda_in(0), scl_high => scl_in(0), abort => arb_lost,
            sda_req => b_sda_req, sda_bit => b_sda_bit,
            rx_byte => b_rx, ack => b_ack, ack_valid => b_ackv, byte_done => b_done,
            busy => b_busy, bit_index => b_bidx, driving => b_driving,
            bytes_done => b_bytes, acks => b_acks, nacks => b_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_conf, conflicts => n_conf,
            arb_loss_now => arb_now, arb_lost => arb_lost, arb_losses => n_arb,
            arb_clear => txn_done);

      dut : entity work.i2c_txn_ctrl
         generic map (CNT_W => 16)
         port map (clk => clk, rst_n => rst_n,
            cmd_valid => cmd_valid, cmd_addr => cmd_addr, cmd_read => cmd_read,
            cmd_len => cmd_len, cmd_stop => cmd_stop,
            do_start => do_start, do_restart => do_restart, do_stop => do_stop,
            scl_yield => scl_yield, frame_done => f_done, frame_busy => f_busy,
            bus_free => f_bus_free, frame_started => f_started,
            gen_enable => gen_enable, gen_idle_low => gen_idle_low,
            byte_go => byte_go, byte_dir_write => byte_dir_write, byte_tx => byte_tx,
            byte_ack_send => byte_ack_send, byte_done => b_done, byte_busy => b_busy,
            byte_ack => b_ack, byte_rx => b_rx,
            tx_data => payload(safe_idx(tx_index(2 downto 0))), tx_index => tx_index,
            rx_data => rx_data, rx_index => rx_index, rx_we => rx_we,
            arb_lost => arb_lost,
            txn_done => txn_done, txn_ok => txn_ok, txn_err => txn_err,
            txn_bytes => txn_bytes, txn_busy => txn_busy,
            addr_nack => addr_nack, data_nack => data_nack,
            state => tstate, transactions => n_txn);

      u_tgt2 : entity work.i2c_target_model
         generic map (MY_ADDR => TADDR_NACKDATA, ACK_ADDR => '1', STRETCH_AFTER => 0,
                      NACK_AT => 2, N_MEM => 16, CNT_W => 16)
         port map (clk => clk, rst_n => rst_n, scl => scl, sda => sda,
            scl_drive_low => t2_scl_low, sda_drive_low => t2_sda_low,
            load_en => '0', load_addr => (others => '0'), load_data => (others => '0'),
            selected => t2_sel, dir_read => t2_dirrd, last_written => t2_lw,
            write_valid => t2_wv, bytes_rx => t2_rx, bytes_tx => t2_tx,
            n_starts => t2_nsta, n_stops => t2_nsto, state => t2_st);

      nack_obs : process (clk, rst_n)
      begin
         if rst_n = '0' then
            saw_addr_nack <= '0'; saw_data_nack <= '0';
         elsif rising_edge(clk) then
            if addr_nack = '1' then saw_addr_nack <= '1'; end if;
            if data_nack = '1' then saw_data_nack <= '1'; end if;
         end if;
      end process;

      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_lw,
            write_valid => t_wv, bytes_rx => t_rx, bytes_tx => t_tx,
            n_starts => t_nsta, n_stops => t_nsto, state => t_st);

      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;

      logp : process (clk, rst_n)
      begin
         if rst_n = '0' then
            ack_hist <= (others => '0');
            n_acks_seen <= 0;
            n_rx <= 0;
         elsif falling_edge(clk) then
            if mo_ack = '1' then
               ack_hist    <= ack_hist(14 downto 0) & mo_ackv;
               n_acks_seen <= n_acks_seen + 1;
            end if;
            if rx_we = '1' then
               -- Index by the DUT's own rx_index, not a bench counter, so a controller
               -- that writes a received byte to the wrong slot is caught.
               rxlog(to_integer(rx_index) mod 8) <= rx_data;
               n_rx <= n_rx + 1;
            end if;
         end if;
      end process;

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

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

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

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

         procedure do_reset is
         begin
            wait until falling_edge(clk);
            rst_n <= '0'; cmd_valid <= '0'; cmd_read <= '0'; cmd_stop <= '1';
            cmd_addr <= TADDR; cmd_len <= to_unsigned(1, 4);
            load_en <= '0'; tgt_hold_scl <= '0';
            payload <= (others => (others => '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 issue (a : std_logic_vector(6 downto 0); rdd : std_logic;
                          len : integer; stp : std_logic) is
         begin
            wait until falling_edge(clk);
            cmd_addr <= a; cmd_read <= rdd; cmd_len <= to_unsigned(len, 4);
            cmd_stop <= stp; cmd_valid <= '1';
            wait until rising_edge(clk); wait until falling_edge(clk); cmd_valid <= '0';
         end procedure;

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

      begin
         report "=== i2c_txn_ctrl: phases, and the decision made one byte ahead ==="
                severity note;

         -- T1. A single-byte write, end to end from one host command.
         do_reset;
         payload(0) <= x"5A";   -- no edge needed: `issue` waits for one itself
         issue(TADDR, '0', 1, '1');
         wait_txn(3000);
         report "T1  a one-byte write, from a host command to the wire and back" severity note;
         ck_bit("T1 succeeded", txn_ok, '1');
         ck_int("T1 one byte moved", to_integer(txn_bytes), 1);
         ck_int("T1 no errors", to_integer(unsigned(txn_err)), 0);
         ck_int("T1 the target received it", to_integer(unsigned(t_lw)), 16#5A#);
         ck_int("T1 the monitor saw two bytes: address and data",
                to_integer(mo_nbyte), 2);
         ck_int("T1 one START and one STOP",
                to_integer(mo_nsta) + to_integer(mo_nsto), 2);
         ck_bit("T1 the bus is idle", mo_intr, '0');

         -- T2. A multi-byte write, and the payload arrives in order.
         do_reset;
         for j in 0 to 3 loop
            payload(j) <= std_logic_vector(to_unsigned(16#B0# + j, 8));
         end loop;
         issue(TADDR, '0', 4, '1');
         wait_txn(6000);
         report "T2  a four-byte write, delivered in order" severity note;
         ck_bit("T2 succeeded", txn_ok, '1');
         ck_int("T2 four bytes moved", to_integer(txn_bytes), 4);
         ck_int("T2 the target received four", to_integer(t_rx), 4);
         ck_int("T2 the last of them was 0xB3", to_integer(unsigned(t_lw)), 16#B3#);
         ck_int("T2 five bytes on the wire: address plus four",
                to_integer(mo_nbyte), 5);

         -- T3. AN ADDRESS NACK stops the transaction before any data is sent.
         do_reset;
         payload(0) <= x"FF";
         issue("1010001", '0', 2, '1');
         wait_txn(3000);
         report "T3  an address NACK stops the transaction before any data is sent"
                severity note;
         ck_bit("T3 did not succeed", txn_ok, '0');
         ck_bit("T3 reported as an address NACK", txn_err(0), '1');
         ck_bit("T3 and not as a data NACK", txn_err(1), '0');
         ck_int("T3 zero bytes moved", to_integer(txn_bytes), 0);
         ck_int("T3 only the address was on the wire", to_integer(mo_nbyte), 1);
         ck_int("T3 and the bus was still framed properly", to_integer(mo_nsto), 1);

         -- T4. A ZERO-LENGTH TRANSACTION is an address probe, and succeeds.
         do_reset;
         issue(TADDR, '0', 0, '1');
         wait_txn(3000);
         report "T4  a zero-length transaction is an address probe, and succeeds"
                severity note;
         ck_bit("T4 succeeded", txn_ok, '1');
         ck_int("T4 no bytes moved", to_integer(txn_bytes), 0);
         ck_int("T4 one byte on the wire: just the address", to_integer(mo_nbyte), 1);
         -- `selected` is cleared by the STOP, so the durable evidence is the acknowledge the
         -- target put on the wire in answer to the address.
         ck_int("T4 two framing events and one acknowledge slot", n_acks_seen, 1);
         ck_bit("T4 and the target acknowledged the address", ack_hist(0), '0');

         -- T5. A single-byte READ, from the target's own memory.
         do_reset;
         preload(0, 16#3C#);
         issue(TADDR, '1', 1, '1');
         wait_txn(3000);
         report "T5  a one-byte read returns the target's data" severity note;
         ck_bit("T5 succeeded", txn_ok, '1');
         ck_int("T5 one byte moved", to_integer(txn_bytes), 1);
         ck_int("T5 and it was stored", to_integer(unsigned(rxlog(0))), 16#3C#);
         ck_bit("T5 the target knew it was a read", t_dirrd, '1');

         -- T6. THE ACK POLICY, as a sequence: ACK, ACK, ACK, NACK. §3.1.10 format 3. The
         --     acknowledge values are read off the WIRE, where a 0 is an ACK.
         do_reset;
         for j in 0 to 3 loop preload(j, 16#70# + j); end loop;
         issue(TADDR, '1', 4, '1');
         wait_txn(6000);
         report "T6  reading four bytes: ACK, ACK, ACK, NACK -- read off the wire"
                severity note;
         ck_int("T6 five acknowledge slots: address plus four", n_acks_seen, 5);
         ck_bit("T6 the address was acknowledged by the target", ack_hist(4), '0');
         ck_bit("T6 data byte 1 acknowledged by the master", ack_hist(3), '0');
         ck_bit("T6 data byte 2 acknowledged", ack_hist(2), '0');
         ck_bit("T6 data byte 3 acknowledged", ack_hist(1), '0');
         ck_bit("T6 and data byte 4 NOT acknowledged", ack_hist(0), '1');
         ck_int("T6 four bytes were stored", n_rx, 4);
         ck_int("T6 the first", to_integer(unsigned(rxlog(0))), 16#70#);
         ck_int("T6 the last", to_integer(unsigned(rxlog(3))), 16#73#);

         -- T7. A ONE-BYTE READ NACKS IMMEDIATELY: the decision is made before the byte is
         --     clocked, so there is no opportunity to see it and then decide.
         do_reset;
         preload(0, 16#99#);
         issue(TADDR, '1', 1, '1');
         wait_txn(3000);
         report "T7  a one-byte read not-acknowledges its only byte" severity note;
         ck_int("T7 two acknowledge slots", n_acks_seen, 2);
         ck_bit("T7 the address was acknowledged", ack_hist(1), '0');
         ck_bit("T7 and the single data byte was not", ack_hist(0), '1');
         ck_int("T7 the byte still arrived", to_integer(unsigned(rxlog(0))), 16#99#);

         -- T8. A DATA NACK and an ADDRESS NACK are different codes.
         do_reset;
         for j in 0 to 3 loop
            payload(j) <= std_logic_vector(to_unsigned(16#D0# + j, 8));
         end loop;
         -- The refusing target ACKNOWLEDGES its address and then NACKs its SECOND data
         -- byte, so this exercises the data-NACK path rather than the address-NACK path.
         issue(TADDR_NACKDATA, '0', 4, '1');
         wait_txn(3000);
         ck_bit("T8 a data NACK sets bit 1", txn_err(1), '1');
         ck_bit("T8 and leaves the address-NACK bit clear", txn_err(0), '0');
         ck_bit("T8 the transfer did not succeed", txn_ok, '0');
         ck_bit("T8 and it pulsed data_nack exactly once", saw_data_nack, '1');
         ck_bit("T8 and never pulsed addr_nack", saw_addr_nack, '0');
         -- A refused byte ENDS the transfer: one accepted, the second refused, and bytes
         -- three and four must never have been sent.
         ck_int("T8 only the accepted byte counted", to_integer(txn_bytes), 1);
         ck_int("T8 the target received exactly two bytes", to_integer(t2_rx), 2);
         report "T8  a data NACK and an address NACK are different codes" severity note;

         -- And the contrast, on the same checks: an address NACK sets the OTHER bit.
         do_reset;
         issue("1010001", '0', 4, '1');
         wait_txn(3000);
         ck_bit("T8 an address NACK sets bit 0", txn_err(0), '1');
         ck_bit("T8 and leaves the data-NACK bit clear", txn_err(1), '0');
         ck_int("T8 nothing was transferred", to_integer(txn_bytes), 0);
         ck_bit("T8 and that one pulsed addr_nack", saw_addr_nack, '1');
         ck_bit("T8 and not data_nack", saw_data_nack, '0');

         -- T9. A STRETCHING TARGET changes the duration and nothing else.
         do_reset;
         payload(0) <= x"77";
         issue(TADDR, '0', 1, '1');
         n := 0;
         while to_integer(mo_nbyte) < 1 and n < 3000 loop step; n := n + 1; end loop;
         wait until falling_edge(clk); tgt_hold_scl <= '1';
         for j in 0 to 39 loop step; end loop;
         wait until falling_edge(clk); tgt_hold_scl <= '0';
         wait_txn(6000);
         report "T9  a stretching bus changes the duration and nothing else" severity note;
         ck_bit("T9 still succeeded", txn_ok, '1');
         ck_int("T9 the byte arrived intact", to_integer(unsigned(t_lw)), 16#77#);
         -- Fewer than the forty cycles the bench held SCL, and correctly so: part of the hold
         -- overlaps the generator's OWN low phase, during which it is driving SCL low itself.
         if to_integer(g_scyc) < 20 then
            report "  FAIL T9 only " & integer'image(to_integer(g_scyc))
                   & " stretch cycles were counted" severity note;
            err := err + 1;
         end if;
         ck_int("T9 and no spurious errors", to_integer(unsigned(txn_err)), 0);

         -- T10. NO STOP: the controller leaves the bus HELD so a repeated START can follow.
         do_reset;
         payload(0) <= x"11";
         issue(TADDR, '0', 1, '0');
         wait_txn(3000);
         report "T10 with no STOP requested, the bus is left held for a repeated START"
                severity note;
         ck_bit("T10 succeeded", txn_ok, '1');
         ck_int("T10 no STOP was emitted", to_integer(mo_nsto), 0);
         ck_bit("T10 the transfer is still open", mo_intr, '1');
         ck_bit("T10 and the framer still says it started", f_started, '1');

         -- T11. AND THEN A SECOND PHASE ON THE SAME TRANSACTION.
         preload(0, 16#E5#);
         issue(TADDR, '1', 1, '1');
         wait_txn(4000);
         report "T11 a second phase without a STOP in between" severity note;
         ck_bit("T11 the read succeeded", txn_ok, '1');
         ck_int("T11 two STARTs on the wire", to_integer(mo_nsta), 2);
         ck_int("T11 and exactly one STOP, at the end", to_integer(mo_nsto), 1);
         ck_int("T11 the read byte arrived",
                to_integer(unsigned(rxlog((n_rx - 1) mod 8))), 16#E5#);
         ck_bit("T11 the bus is idle now", mo_intr, '0');
         ck_int("T11 no mid-byte framing at all", to_integer(mo_nmid), 0);

         -- T12. THE INVARIANTS, over every transaction so far.
         report "T12 no owner conflicts and no spurious arbitration losses, throughout"
                severity note;
         ck_int("T12 no SDA owner conflicts", to_integer(n_conf), 0);
         ck_int("T12 no arbitration losses on a single-master bus", to_integer(n_arb), 0);
         ck_int("T12 two transactions since reset", to_integer(n_txn), 2);
         ck_bit("T12 idle", txn_busy, '0');
         ck_int("T12 and the controller is back at rest", to_integer(tstate), S_IDLE);

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

   end architecture sim;

6b. Execution

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_txn_ctrlPASS 12/12PASS 12/12PASS 12/1234140 ns, all three

7. Mutation Testing — A Test That Was Labelled for the Wrong Thing

Ten defects. Three survived the original suite, and diagnosing them found one test that did not test what its own comment claimed.

#Injected defectExpected detectionResult
M1the master ACKs the last byte of a readT6, T7KILLED (4)
M2the ACK policy one byte out on continuationT6KILLED (2)
M3a data NACK no longer ends the writeT8 after rewriteKILLED (2)
M4address NACK and data NACK share a codeT8 after rewriteKILLED (3)
M5the address byte is not always transmittedT1, T5KILLED (8)
M6a zero-length transaction treated as a transferT4KILLED (3)
M7tx_index names the byte already sentT2KILLED (2)
M8the received byte written to the wrong slotT5, T6 after fixKILLED (6)
M9success reported despite an address NACKT3KILLED (3)
M10the data_nack pulse never emittedT8 after fixKILLED (2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
killed: 10   survived: 0   score: 10/10
restored: PASS

T8 claimed to test a data NACK and tested an address NACK twice

The original T8's comment read "The target here acknowledges its address and refuses the second data byte." Its code issued the transaction to 0x51 — which is the address-NACK target from T3.

So there was no data-NACK test anywhere in the suite. That single gap hid two defects: M3 (a data NACK no longer ending the write) and M4 (the two NACKs sharing an error code). Both are squarely in §2's subject matter, and both were believed covered.

The reason the gap survived review is worth noting: the test passed, its assertions were about the right signals, and its comment described the right scenario. Only the address literal was wrong, and nothing about a passing result points at it.

The fix required a second target model. The existing one has a NACK_AT parameter — acknowledge the address, then NACK the Nth data byte — which the bench instantiated as NACK_AT(0), meaning never. A refusing target at 0x52 makes the path reachable, and T8 now checks both sides of the contrast:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
data NACK:     txn_err bit 1 set, bit 0 clear, txn_ok 0, txn_bytes 1,
               and the target received exactly TWO bytes -- the accepted
               one and the refused one, and never bytes three and four
address NACK:  txn_err bit 0 set, bit 1 clear, txn_bytes 0

And two more things the rewrite caught

rx_index was never checked. The bench logged each received byte into its own array indexed by a bench-local counter, ignoring the DUT's rx_index output entirely. So M8 — writing the received byte to the wrong buffer slot — was invisible. Indexing the log by rx_index makes the output load-bearing.

addr_nack and data_nack are one-cycle pulses and were sampled after the transaction had completed, where they always read zero. Latching them in the bench both fixes the check and verifies the pulses at all — M10 sets the error bit while never pulsing, and is now caught.

8. Verification Connection — What Belongs in a Transaction Item

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_txn_item.sv — the boundary of a transaction object
   // This is the layer where a UVM environment finally has something worth calling a
   // transaction, and the useful question is what goes IN the item and what does not.
   //
   // WHAT BELONGS -- because it is decided before the transfer and is what a
   // scoreboard needs to predict the outcome:
   //
   //     rand bit [6:0] addr;
   //     rand bit       is_read;
   //     rand int       length;          // and section 1 explains why this is
   //                                     // MANDATORY on a read rather than optional
   //     rand byte      wdata[];
   //     rand bit       end_with_stop;   // 17.2's CTRL bit; 17.9 needs it
   //
   // WHAT BELONGS AS A RESULT, written by the monitor/predictor, not randomised:
   //
   //     byte  rdata[];
   //     bit   ok;
   //     int   bytes_moved;              // NOT `length` -- section 2a
   //     enum  { NONE, ADDR_NACK, DATA_NACK, ARB_LOST } err;
   //
   // WHAT DOES NOT BELONG, and each has a reason:
   //
   //     the ACK the master sent per byte. It is DERIVABLE from `length` -- ACK for
   //     every byte but the last -- so putting it in the item creates a second source
   //     of truth that can disagree with the first. A sequence that sets them
   //     independently can express a transaction the protocol forbids, and then the
   //     scoreboard has to decide which field it believes.
   //
   //     any timing. Phase widths belong to the generator's parameters. An item that
   //     carries tLOW is describing the configuration, not the transaction.
   //
   //     the repeated START. A combined transfer is TWO phases of ONE transaction, and
   //     Chapter 17.9 argues it should be a sequence of items with the bus held rather
   //     than one item with a flag -- because the phases have independent addresses,
   //     directions and lengths.
   //
   // THE PREDICTOR'S HARDEST CASE is a data NACK, and section 2a is why: on a refusal
   // the transfer ends early, so the predicted `bytes_moved` is not `length` and the
   // target's state reflects a PREFIX of wdata. A scoreboard that models the target's
   // register map must apply that prefix and stop -- applying all of wdata leaves the
   // model and the device disagreeing about every later access, and the mismatch
   // surfaces on a completely unrelated transaction.

9. FPGA and ASIC Implications

On an FPGA this block is small — a state register, a four-bit counter and two pointers — and the interesting property is what it does not contain. There is no data storage: tx_data comes from 17.2's buffer through tx_index, and received bytes go straight back with rx_we. That keeps the controller's area independent of the payload size, so raising N_BUF from 8 to 256 changes the register file and nothing here.

The one timing consideration is the tx_index convention: the index names the byte to be sent next, and it is advanced when a byte is issued rather than when it completes. That gives the host buffer a full byte time — tens of microseconds — to present data, which is why a registered block-RAM read is absorbed without difficulty. Mutation M7 breaks exactly this convention and repeats a byte.

On an ASIC, the error taxonomy is the firmware contract, and the fact that an address NACK cannot distinguish "nothing there" from "busy" is a property of the bus that firmware must be told about rather than a limitation to fix. Chapter 16.4 established it; the register map inherits it, and a driver that retries an address NACK is implementing acknowledge polling whether it calls it that or not.

The clock handover is the ASIC-relevant detail in this block's implementation: two sources drive SCL at different times, and the overlap is deliberate. A synthesis flow that sees two drivers on one internal net will complain unless the handover is expressed as a mux with a registered select — which is what scl_yield and gen_idle_low are.

10. Debugging — The Burst Write That Landed in the Wrong Places

Symptom

A driver writes a six-byte configuration block to a sensor with a write-protect pin. With the pin deasserted the block is written correctly. With the pin asserted the driver reports success, and the sensor's registers afterwards contain the first byte of the block in the right place and bytes four, five and six in the positions of bytes two, three and four. Nothing in the driver's return path indicates a problem.

Root Cause

Two defects compounding, and either alone would have been survivable. First, a data NACK did not end the transfer, so the master wrote bytes the target had already declined -- and since the target's pointer only advances on accepted bytes, every later byte landed one position low. Second, the byte count reported bytes REQUESTED rather than bytes MOVED, so the divergence was invisible to software. The write-protect pin was working exactly as designed and the sensor was conforming throughout: it refused one byte and said so, in the only way the bus provides.

Fix
End the transfer on a data NACK, which is what the published controller does, and report txn_bytes as bytes actually accepted. Then note that the second half is the more important one: a master that stops correctly but reports the requested count still tells a driver that six bytes were written when five were. The host needs the count to know what state the target is in. For the regression, a target model with a configurable refusal -- the NACK_AT parameter -- and a test asserting all three facts together: the transfer did not succeed, the error is a DATA nack and not an address nack, and the count is the accepted count. Testing only that the master stopped would have missed the reporting half entirely.

Three generalisations.

The device was conforming and the master was not. A NACK on a data byte is a legitimate, specified response, and the write-protect pin was doing its job. Everything unusual on the bus came from the master ignoring an answer it had asked for.

Two mild defects produced one severe failure. Not ending on a NACK misplaces data; reporting the requested count hides it. Either alone is debuggable in an afternoon — together they produce silent corruption that surfaces as a sensor misconfiguration weeks later.

The reported count is part of the error path, not part of the success path. It is easy to treat bytes_moved as telemetry. On any transfer that ends early it is the only thing that tells software what the target now contains.

11. Common Misconceptions

"A master can read until the device stops sending." It cannot. The master owns the acknowledge and must decide before each byte is clocked, so the length must be known in advance. That transaction is not expressible on this bus. §1.

"The master's NACK on the last byte is optional politeness." It is how the master says stop. §3.1.10 format 3 requires it before a repeated START, and without it the target keeps sourcing bytes. §1.

"A NACK is a NACK." On an address it means nothing is there or the device is busy; on a data byte it means the device declined that byte. Different causes, different recovery, different codes. §2.

"A NACK on a data byte is an error to be retried." It may be a write-protect pin behaving correctly. It is the target's legitimate answer, and the master's obligation is to stop. §2a.

"Report the byte count that was requested." Report the count actually moved. On a transfer that ended early, that is the only thing that tells software what the target contains. §2a and §10.

"A zero-length transaction is a degenerate case to reject." It is the bus scan every driver has: address, look at the acknowledge, stop. §4.

"A read can produce a data NACK." Only a write can. On a read the master sent the acknowledge itself, so there is nothing to check. §2.

"The clock handover order does not matter." The wrong order lets SCL rise before the framer takes it, and the framer's next act is to pull SDA low — producing a START where a STOP was intended. §3.

"A test that passes and asserts the right signals tests the right thing." T8 asserted correct signals about a correct property and addressed the wrong device, so the property was never exercised. §7.

"Per-byte ACK decisions belong in a transaction item." They are derivable from the length, and duplicating them creates a second source of truth that can express a transaction the protocol forbids. §8.

12. Reason It Through

Why can a master not read a variable number of bytes, deciding as it goes?

Because the acknowledge is the ninth slot of the same byte, so the decision precedes the data. The master would have to know whether to ask for another byte before seeing the one it has. §1.

The ACK policy uses n_left != 1 in one place and n_left != 2 in another. Why the difference?

Because at the second point n_left has not yet been decremented, so "one byte left after this" reads as 2. A sequence test catches a mix-up here; a single-byte test cannot. §1.

A four-byte write is NACKed on byte two. What should txn_bytes report, and why does it matter?

One — the number accepted. It matters because the target's pointer advanced only on accepted bytes, so software needs the accepted count to know what the register map now contains. §2a and §10.

Why can a data NACK only occur on a write?

Because on a read the master sends the acknowledge itself, so there is no answer from the target to check. §2.

Why must the framer take the SCL hold before the generator releases it?

Because if the generator releases first, SCL rises; the framer's next act is to pull SDA low, and SDA falling while SCL is high is a START rather than the intended STOP. §3.

T8's comment described a data NACK and its code addressed 0x51. Why did no gate catch that?

Because the test passed, its assertions were about the right signals, and only the address literal was wrong. Nothing in a pass rate or a coverage number distinguishes a test that exercises a path from one that exercises a different path correctly. §7.

Why did indexing the received-byte log by a bench counter hide a real defect?

Because it made the DUT's rx_index output unused, so a controller writing the byte to the wrong slot produced an identical log. Indexing by the DUT's own output makes it load-bearing. §7.

Why should a transaction item not carry the per-byte acknowledge values?

Because they are derivable from the length, and an independent field lets a sequence express an illegal transaction — leaving the scoreboard to choose which field to believe. §8.

13. Understanding Check

14. Summary

The master ACK policy is the one genuine protocol obligation in this block. On a read the master acknowledges every byte it wants another after and not-acknowledges the last, per §3.1.10 format 3.

That decision precedes the byte, because the acknowledge is the ninth slot of the same byte — so a read length must be known in advance, and "read until the device stops" is not expressible on this bus.

Which is why the command register carries a length. The constraint propagates up into the host interface rather than being absorbed here.

A NACK means different things in the two phases and is reported as different failures: on an address, nothing there or busy — indistinguishable; on a data byte, the device declined that byte.

Only a write can produce a data NACK, because on a read the master sent the acknowledge itself.

A data NACK ends the transfer, and txn_bytes reports bytes moved, not requested — because the target's pointer advanced only on accepted bytes, and the accepted count is the only thing that tells software what the target contains.

Zero length is legal and is the bus scan every driver performs.

The clock handover belongs here and overlaps deliberately. On the way back the framer must take the hold before the generator releases it, or SCL rises and the framer's next SDA fall becomes a START instead of a STOP.

Ten mutants, ten killed — after three survived. All three were hidden by one test that addressed the wrong device while claiming to test a data NACK, plus a log indexed by the bench instead of the DUT, plus two pulses sampled after they had gone.

In all three cases the test ran, passed, and was about the right property. None is visible in a pass rate or a coverage figure; only injecting the defect showed that nothing was watching.

15. What Comes Next

A transaction now runs from a host command to a STOP, in either direction, reporting what actually happened. One thing it deliberately does not do: keep the bus.

Chapter 17.9 adds the repeated START, and with it the combined transaction that Module 16's register-map devices require. The point of that chapter is that a combined transfer is not a write, a STOP and a read — it is one transaction whose direction changes without the bus ever going free, and the reason it must be that way is that anything else lets another master take the bus between the two halves and move the pointer.

It is also where 17.5's repeated-START sequence is finally driven by something other than a testbench, and where the byte-engine and transaction state have to survive a turnaround that resets the bus framing but not the transfer.

Continue learning

Related tutorials