Skip to content
VLSI Mentor

I²C · Module 7

Reading a Complete I²C Frame

Every chapter so far has worked on a fragment — one edge, one byte, one slot. This one assembles them: a method for reading a raw capture by eye, and a passive decoder that turns two wires into framing, address, direction, data and every acknowledge.

Seven modules have built this bus one mechanism at a time. Framing, addressing, the byte, the acknowledge — each derived on its own, each with its own hardware. This chapter puts them together, because the thing an engineer actually faces is never a fragment. It is a capture, or a monitor log, containing a whole transfer at once.

The deliverable here is twofold: a reading method that works on a screen with no tooling, and a decoder that does the same job in hardware and is assembled entirely from blocks already built.

1. The Frame Map

Every I²C frame has the same skeleton, and knowing its shape is most of the reading skill.

positionwhat it iswho drives the 8 bitswho drives the 9thestablished in
Sframing: STARTmaster—5.2
byte 0address + R/Wmasteraddressed slave6.1
byte 1..ndatadepends on R/Wthe receiver7.3
Srframing: repeated START — optional, may appear anywheremaster—5.4
Pframing: STOPmaster—5.3

Three structural facts follow from that table, and they are what make a capture readable.

The byte count is not in the frame. There is no length field anywhere. A frame ends when framing says it does, which means the only way to know how many bytes it contained is to count them.

A repeated START can appear between any two bytes, and when it does, the byte following it is an address again — not data. That is the single most important thing to spot when reading a capture, because mistaking a post-Sr address byte for data shifts the interpretation of everything after it.

The direction can change mid-frame, but only at an Sr. Within one addressing, the direction bit is fixed. Module 10 builds the combined transactions that exploit this.

2. Reading a Capture by Eye

Given a screen showing SDA and SCL and nothing else, the following order works and any other order costs time. It is the same discipline as Chapter 4.3's passes, applied to content rather than timing.

Pass 1 — find the framing first, and only the framing. Scan SDA for edges that occur while SCL is high. Ignore everything else on the first pass. Every such edge is a framing event: falling is S or Sr, rising is P. This partitions the whole capture into frames before any byte is decoded, and it is the pass people skip.

Pass 2 — count nine clocks from each S or Sr. The nine pulses after a framing fall are the address byte and its acknowledge. Mark the boundary. Then nine more, and nine more, until the next framing event.

Pass 3 — read the address byte and split it. Seven bits, then the direction bit. Shift right by one to get the seven-bit address the datasheet uses, and read bit 0 as the direction — 0 is a write, 1 is a read. Chapter 6.1 §2 is the conversion, and getting it backwards is the most common reading error.

Pass 4 — decide who owns each byte, then read the data. With the direction known, Chapter 7.3's table says who drove each byte and who answered it. This matters for interpretation: a byte the slave drove is device data, and a byte the master drove is a command or a register pointer.

Pass 5 — read every ninth bit. Not just the failures. The acknowledge pattern is diagnostic on its own, and §3 is a table of what each pattern means.

Pass 6 — check the ending. A read should end with a NACK on its final byte (Chapter 7.4); a write should end with every byte acknowledged. An ending that does not match the direction is the finding.

3. The Acknowledge Pattern Is a Diagnosis

Pass 5 deserves expanding, because the ninth bits taken together identify the failure faster than the payload does.

patternreadingmost likely cause
address NACKed, nothing followsnobody answereddevice absent, unpowered, or at another address — 6.4
address ACKed, first data byte NACKedpresent but declined the contentwrong register pointer, or the device is busy
address ACKed, byte k NACKed mid-writeit stopped acceptingbuffer full, or it did not understand byte k
a write with every byte ACKedstructurally healthythe frame is fine; a data problem is elsewhere
a read whose last byte is ACKedthe master forgot its NACK7.4 — expect the bus to hang next
a read whose last byte is NACKednormal terminationcorrect, condition 5 — not an error
a read with an early NACKshort readthe master stopped before it meant to

The fifth row is the one worth memorising, because it is a prediction: a read ending in an acknowledge means the slave was asked for another byte, so the next thing in the capture will be the slave transmitting and the bus failing to release. Spotting it in the acknowledge column tells you what you are about to see.

Address byte, read direction — the slave still answers

9 cycles
Nine clock periods. SDA carries one zero zero one zero zero zero for the seven address bits and a one for the read direction bit, all driven by the master, then a low ninth interval driven by the slave as its acknowledge. A driver row shows M for the first eight intervals and S for the ninth.address 0x48address 0x48R/WR/Wslave ACKslave ACKseven address bits, MSB firstseven address bits, MSBfirstR/W = 1: a read followsR/W = 1: a read followsthe slave acknowledgesthe slave acknowledgessclsda100100010driverMMMMMMMMSt0t1t2t3t4t5t6t7t8
Figure 1 — the address byte of a read, with the slave's acknowledge. The master drives all eight bits, including the direction bit that says the data will flow the other way, and the addressed slave answers in the ninth slot. Note that the direction bit describes the bytes that follow, not this one — the address byte is always master-to-slave, which is why the slave acknowledges it even on a read.

4. What a Decoder Must Record, and What It Must Not Infer

Doing the same job in hardware raises a design question that is easy to get wrong: what should the output be?

The temptation is to publish a verdict — transfer succeeded or transfer failed. That is the wrong shape, for the reason Chapter 7.2 §3 established: one acknowledge bit has five possible meanings and the decoder cannot distinguish them. A decoder that collapses the frame to a boolean has thrown away the information needed to diagnose it.

So the decoder records observations, not conclusions:

recordedwhy it cannot be inferred later
the seven-bit addressneeded to know which device the frame was for
the direction bitdetermines who owned every subsequent byte
whether the address was acknowledgedseparates "absent device" from "present but declined"
each data bytethe payload
each data byte's acknowledgethe pattern of §3 is the diagnosis
the count of data bytesthere is no length field; counting is the only source
that a STOP ended the framedistinguishes a completed frame from an abandoned one
that a byte arrived with no framinga capture that is not a frame at all

And one deliberate non-behaviour: a STOP does not clear the decoded fields. A consumer reads them after the frame completes, so clearing on the STOP would make the decoder useless to everything downstream. §7 injects a fault that clears them and the testbench catches it.

5. The Decoder in Three Languages

It instantiates Chapter 7.1's byte shifter with tx_enable tied low and Chapter 7.2's acknowledge slot with we_acknowledge tied low — a passive observer built from two blocks that can both drive, simply by not asking them to. All it adds is the sequencing that says which byte is the address.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_frame_decoder.sv — SYNTHESIZABLE RTL. A passive observer, assembled from the shifter and the acknowledge slot.
   // A passive frame observer: it decodes a complete I2C frame off the two wires and
   // never drives either of them. ASSEMBLED from the byte shifter and the acknowledge
   // slot built earlier in this module -- it adds only the sequencing that says which
   // byte is the address and which are data.
   module i2c_frame_decoder (
       input  logic clk,
       input  logic rst_n,
       input  logic scl_in,              // observed bus level
       input  logic sda_in,              // observed bus level
       input  logic frame_start,         // pulse: S or Sr observed (Module 5)
       input  logic frame_stop,          // pulse: P observed (Module 5)
       output logic [6:0] addr,          // the seven address bits of this frame
       output logic       dir_is_read,   // the latched R/W
       output logic       addr_acked,    // did anybody answer the address?
       output logic       addr_valid,    // pulse: the address phase is decoded
       output logic [7:0] data_byte,     // the most recent data byte
       output logic       data_acked,    // was that byte acknowledged?
       output logic       data_valid,    // pulse: a data byte is decoded
       output logic [7:0] byte_count,    // data bytes seen so far in this frame
       output logic       frame_complete,// pulse: a STOP ended the frame
       output logic       frame_error    // a byte arrived with no framing START
   );
       // ---- the two blocks from earlier in this module ----
       logic sh_rx_valid, sh_ack_slot, sh_byte_done;
       logic [7:0] sh_rx_data;
       logic [3:0] sh_bit_index;
       logic sh_drive_low;               // tied off: an observer never drives
       logic begin_byte;

       i2c_byte_shifter u_shift (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
           .begin_byte(begin_byte), .tx_enable(1'b0), .tx_data(8'h00),
           .sda_drive_low(sh_drive_low), .rx_data(sh_rx_data), .rx_valid(sh_rx_valid),
           .ack_slot(sh_ack_slot), .byte_done(sh_byte_done), .bit_index(sh_bit_index)
       );

       logic ak_drive_low;               // tied off: an observer never drives
       logic ak_bit, ak_received, ak_nack, ak_valid;

       i2c_ack_slot u_ack (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
           .ack_slot(sh_ack_slot), .we_acknowledge(1'b0), .send_ack(1'b0),
           .sda_drive_low(ak_drive_low), .ack_bit(ak_bit), .ack_received(ak_received),
           .nack_received(ak_nack), .ack_valid(ak_valid)
       );

       // ---- the sequencing this block adds ----
       typedef enum logic [1:0] {
           FD_IDLE,      // no frame in progress
           FD_ADDR,      // the next byte to complete is the address
           FD_DATA       // subsequent bytes are data
       } state_e;

       state_e state;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               state          <= FD_IDLE;
               addr           <= 7'h00;
               dir_is_read    <= 1'b0;
               addr_acked     <= 1'b0;
               addr_valid     <= 1'b0;
               data_byte      <= 8'h00;
               data_acked     <= 1'b0;
               data_valid     <= 1'b0;
               byte_count     <= 8'h00;
               frame_complete <= 1'b0;
               frame_error    <= 1'b0;
               begin_byte     <= 1'b0;
           end else begin
               addr_valid     <= 1'b0;
               data_valid     <= 1'b0;
               frame_complete <= 1'b0;
               begin_byte     <= 1'b0;

               if (frame_stop) begin
                   // A STOP ends the frame. Note it does NOT clear the decoded fields:
                   // a consumer reads them after the frame, so they must survive it.
                   if (state != FD_IDLE) frame_complete <= 1'b1;
                   state <= FD_IDLE;
               end else if (frame_start) begin
                   // S or Sr both open an address phase, and a repeated START restarts
                   // the byte count -- the bytes after it belong to a new addressing.
                   state      <= FD_ADDR;
                   byte_count <= 8'h00;
                   begin_byte <= 1'b1;
               end else if (ak_valid) begin
                   // The ninth bit has been sampled, so the byte AND its answer are both
                   // known. Triggering here rather than on byte_done is what makes the
                   // acknowledge available in the same cycle as the data.
                   case (state)
                       FD_ADDR: begin
                           addr        <= sh_rx_data[7:1];
                           dir_is_read <= sh_rx_data[0];
                           addr_acked  <= ak_received;
                           addr_valid  <= 1'b1;
                           state       <= FD_DATA;
                           begin_byte  <= 1'b1;         // arm for the first data byte
                       end
                       FD_DATA: begin
                           data_byte  <= sh_rx_data;
                           data_acked <= ak_received;
                           data_valid <= 1'b1;
                           byte_count <= byte_count + 8'd1;
                           // A NACKed byte must be followed by a STOP or a repeated
                           // START, so there is no point arming for another byte --
                           // but the bus decides that, not this observer, so it arms
                           // anyway and lets the framing event override.
                           begin_byte <= 1'b1;
                       end
                       default: begin
                           // A byte completed with no framing START before it. That is
                           // not a decodable frame, and reporting it is more useful than
                           // inventing an address for it.
                           frame_error <= 1'b1;
                       end
                   endcase
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_frame_decoder_tb.sv — SELF-CHECKING TESTBENCH, SIMULATION ONLY. Write, read with a final NACK, unanswered address, repeated START, stray byte.
   module i2c_frame_decoder_tb;
       logic clk = 1'b0, rst_n, scl_in, frame_start, frame_stop;
       logic [6:0] addr;
       logic dir_is_read, addr_acked, addr_valid;
       logic [7:0] data_byte;
       logic data_acked, data_valid;
       logic [7:0] byte_count;
       logic frame_complete, frame_error;
       int errors = 0;

       // The testbench is the only driver: the decoder is a passive observer.
       logic tb_drive_low = 1'b0;
       wire  sda_bus = ~tb_drive_low;

       i2c_frame_decoder dut (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_bus),
           .frame_start(frame_start), .frame_stop(frame_stop),
           .addr(addr), .dir_is_read(dir_is_read), .addr_acked(addr_acked),
           .addr_valid(addr_valid), .data_byte(data_byte), .data_acked(data_acked),
           .data_valid(data_valid), .byte_count(byte_count),
           .frame_complete(frame_complete), .frame_error(frame_error));

       always #5 clk = ~clk;
       initial begin #200000; $display("FAIL: watchdog expired"); $finish; end

       int n_addr = 0, n_data = 0, n_frame = 0;
       logic [7:0] data_log [0:7];
       logic       ack_log  [0:7];
       always @(posedge clk) if (rst_n) begin
           if (addr_valid) n_addr++;
           if (data_valid) begin
               if (n_data < 8) begin data_log[n_data] = data_byte; ack_log[n_data] = data_acked; end
               n_data++;
           end
           if (frame_complete) n_frame++;
       end

       // Drive one bit, changing SDA only while SCL is low.
       task automatic send_bit(input logic b);
           scl_in = 1'b0;          repeat (1) @(negedge clk);
           tb_drive_low = ~b;      repeat (1) @(negedge clk);
           scl_in = 1'b1;          repeat (2) @(negedge clk);
           scl_in = 1'b0;          repeat (1) @(negedge clk);
       endtask

       // A whole byte plus its acknowledge slot. `ack` is what the far end answers.
       task automatic send_byte(input logic [7:0] v, input logic ack);
           for (int i = 7; i >= 0; i--) send_bit(v[i]);
           send_bit(~ack);          // ACK is a LOW, so an ack of 1 drives a 0 bit
           tb_drive_low = 1'b0;     repeat (1) @(negedge clk);
       endtask

       task automatic pulse_start(); frame_start = 1'b1; @(negedge clk); frame_start = 1'b0; @(negedge clk); endtask
       task automatic pulse_stop();  frame_stop  = 1'b1; @(negedge clk); frame_stop  = 1'b0; @(negedge clk); endtask

       initial begin
           rst_n = 1'b0; scl_in = 1'b1; frame_start = 1'b0; frame_stop = 1'b0;
           repeat (3) @(negedge clk);
           if (frame_error !== 1'b0) begin $display("FAIL: frame_error out of reset"); errors++; end
           rst_n = 1'b1; @(negedge clk);

           // ---- 1: a complete WRITE frame. Address 0x48 + W, two data bytes, all ACKed.
           pulse_start();
           send_byte({7'h48, 1'b0}, 1'b1);       // address byte, acknowledged
           if (n_addr != 1) begin $display("FAIL: address phase not decoded once"); errors++; end
           if (addr !== 7'h48) begin $display("FAIL: decoded addr 0x%02h, expected 0x48", addr); errors++; end
           if (dir_is_read !== 1'b0) begin $display("FAIL: direction should be write"); errors++; end
           if (addr_acked !== 1'b1) begin $display("FAIL: address ACK not recorded"); errors++; end

           send_byte(8'hDE, 1'b1);
           send_byte(8'hAD, 1'b1);
           if (n_data != 2) begin $display("FAIL: expected 2 data bytes, saw %0d", n_data); errors++; end
           if (byte_count !== 8'd2) begin $display("FAIL: byte_count = %0d, expected 2", byte_count); errors++; end
           if (data_log[0] !== 8'hDE || data_log[1] !== 8'hAD) begin
               $display("FAIL: data bytes 0x%02h,0x%02h, expected 0xDE,0xAD", data_log[0], data_log[1]);
               errors++; end
           pulse_stop();
           if (n_frame != 1) begin $display("FAIL: frame_complete not pulsed at the STOP"); errors++; end
           // The decoded fields must SURVIVE the STOP -- a consumer reads them after.
           if (addr !== 7'h48 || byte_count !== 8'd2) begin
               $display("FAIL: decoded fields were cleared by the STOP"); errors++; end

           // ---- 2: a READ frame, and the final byte NACKed -- Chapter 7.4's pattern.
           n_data = 0;
           pulse_start();
           send_byte({7'h50, 1'b1}, 1'b1);       // address + R, acknowledged by the slave
           if (dir_is_read !== 1'b1) begin $display("FAIL: direction should be read"); errors++; end
           send_byte(8'h11, 1'b1);               // master ACKs -- another byte please
           send_byte(8'h22, 1'b0);               // master NACKs -- that was the last
           if (n_data != 2) begin $display("FAIL: expected 2 read bytes, saw %0d", n_data); errors++; end
           if (ack_log[0] !== 1'b1) begin $display("FAIL: first read byte should be ACKed"); errors++; end
           if (ack_log[1] !== 1'b0) begin
               $display("FAIL: the final read byte must be recorded as NACKed"); errors++; end
           pulse_stop();

           // ---- 3: an address that NOBODY answers. The frame is still well formed;
           //         the decoder must report the NACK rather than hiding it.
           n_data = 0;
           pulse_start();
           send_byte({7'h7A, 1'b0}, 1'b0);       // nothing at this address
           if (addr_acked !== 1'b0) begin
               $display("FAIL: an unanswered address must be recorded as NACKed"); errors++; end
           if (addr !== 7'h7A) begin $display("FAIL: address still decodes on a NACK"); errors++; end
           pulse_stop();

           // ---- 4: a repeated START mid-frame restarts the addressing and the count.
           n_data = 0;
           pulse_start();
           send_byte({7'h48, 1'b0}, 1'b1);
           send_byte(8'h01, 1'b1);
           if (byte_count !== 8'd1) begin $display("FAIL: byte_count should be 1 here"); errors++; end
           pulse_start();                         // Sr
           send_byte({7'h48, 1'b1}, 1'b1);        // re-addressed for a read
           if (dir_is_read !== 1'b1) begin
               $display("FAIL: the repeated START's direction was not latched"); errors++; end
           if (byte_count !== 8'd0) begin
               $display("FAIL: a repeated START must restart the data byte count, got %0d", byte_count);
               errors++; end
           send_byte(8'hFF, 1'b0);
           pulse_stop();

           // ---- 5: a byte with NO framing START is not a frame and must be reported.
           if (frame_error !== 1'b0) begin $display("FAIL: spurious frame_error"); errors++; end
           send_byte(8'h5A, 1'b1);
           if (frame_error !== 1'b1) begin
               $display("FAIL: a byte outside any frame was not reported"); errors++; end

           if (errors == 0)
               $display("PASS: address, direction, data and every acknowledge decoded; Sr restarts; stray byte reported");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_frame_decoder.v — SYNTHESIZABLE RTL. The same decoder in Verilog-2005.
   // A passive frame observer: it decodes a complete I2C frame off the two wires and
   // never drives either of them. ASSEMBLED from the byte shifter and the acknowledge
   // slot built earlier in this module -- it adds only the sequencing that says which
   // byte is the address and which are data.
   module i2c_frame_decoder (
       input  wire clk,
       input  wire rst_n,
       input  wire scl_in,               // observed bus level
       input  wire sda_in,               // observed bus level
       input  wire frame_start,          // pulse: S or Sr observed (Module 5)
       input  wire frame_stop,           // pulse: P observed (Module 5)
       output reg  [6:0] addr,          // the seven address bits of this frame
       output reg        dir_is_read,   // the latched R/W
       output reg        addr_acked,    // did anybody answer the address?
       output reg        addr_valid,    // pulse: the address phase is decoded
       output reg  [7:0] data_byte,     // the most recent data byte
       output reg        data_acked,    // was that byte acknowledged?
       output reg        data_valid,    // pulse: a data byte is decoded
       output reg  [7:0] byte_count,    // data bytes seen so far in this frame
       output reg        frame_complete,// pulse: a STOP ended the frame
       output reg        frame_error    // a byte arrived with no framing START
   );
       // ---- the two blocks from earlier in this module ----
       wire sh_rx_valid, sh_ack_slot, sh_byte_done;
       wire [7:0] sh_rx_data;
       wire [3:0] sh_bit_index;
       wire sh_drive_low;                // tied off: an observer never drives
       reg  begin_byte;

       i2c_byte_shifter u_shift (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
           .begin_byte(begin_byte), .tx_enable(1'b0), .tx_data(8'h00),
           .sda_drive_low(sh_drive_low), .rx_data(sh_rx_data), .rx_valid(sh_rx_valid),
           .ack_slot(sh_ack_slot), .byte_done(sh_byte_done), .bit_index(sh_bit_index)
       );

       wire ak_drive_low;                // tied off: an observer never drives
       wire ak_bit, ak_received, ak_nack, ak_valid;

       i2c_ack_slot u_ack (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
           .ack_slot(sh_ack_slot), .we_acknowledge(1'b0), .send_ack(1'b0),
           .sda_drive_low(ak_drive_low), .ack_bit(ak_bit), .ack_received(ak_received),
           .nack_received(ak_nack), .ack_valid(ak_valid)
       );

       // ---- the sequencing this block adds ----
       localparam FD_IDLE = 2'd0;    // no frame in progress
       localparam FD_ADDR = 2'd1;    // the next byte to complete is the address
       localparam FD_DATA = 2'd2;    // subsequent bytes are data

       reg [1:0] state;

       always @(posedge clk) begin
           if (!rst_n) begin
               state          <= FD_IDLE;
               addr           <= 7'h00;
               dir_is_read    <= 1'b0;
               addr_acked     <= 1'b0;
               addr_valid     <= 1'b0;
               data_byte      <= 8'h00;
               data_acked     <= 1'b0;
               data_valid     <= 1'b0;
               byte_count     <= 8'h00;
               frame_complete <= 1'b0;
               frame_error    <= 1'b0;
               begin_byte     <= 1'b0;
           end else begin
               addr_valid     <= 1'b0;
               data_valid     <= 1'b0;
               frame_complete <= 1'b0;
               begin_byte     <= 1'b0;

               if (frame_stop) begin
                   // A STOP ends the frame. Note it does NOT clear the decoded fields:
                   // a consumer reads them after the frame, so they must survive it.
                   if (state != FD_IDLE) frame_complete <= 1'b1;
                   state <= FD_IDLE;
               end else if (frame_start) begin
                   // S or Sr both open an address phase, and a repeated START restarts
                   // the byte count -- the bytes after it belong to a new addressing.
                   state      <= FD_ADDR;
                   byte_count <= 8'h00;
                   begin_byte <= 1'b1;
               end else if (ak_valid) begin
                   // The ninth bit has been sampled, so the byte AND its answer are both
                   // known. Triggering here rather than on byte_done is what makes the
                   // acknowledge available in the same cycle as the data.
                   case (state)
                       FD_ADDR: begin
                           addr        <= sh_rx_data[7:1];
                           dir_is_read <= sh_rx_data[0];
                           addr_acked  <= ak_received;
                           addr_valid  <= 1'b1;
                           state       <= FD_DATA;
                           begin_byte  <= 1'b1;         // arm for the first data byte
                       end
                       FD_DATA: begin
                           data_byte  <= sh_rx_data;
                           data_acked <= ak_received;
                           data_valid <= 1'b1;
                           byte_count <= byte_count + 8'd1;
                           // A NACKed byte must be followed by a STOP or a repeated
                           // START, so there is no point arming for another byte --
                           // but the bus decides that, not this observer, so it arms
                           // anyway and lets the framing event override.
                           begin_byte <= 1'b1;
                       end
                       default: begin
                           // A byte completed with no framing START before it. That is
                           // not a decodable frame, and reporting it is more useful than
                           // inventing an address for it.
                           frame_error <= 1'b1;
                       end
                   endcase
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_frame_decoder_tb.v — SELF-CHECKING TESTBENCH, SIMULATION ONLY. The same cases in Verilog idiom.
   module i2c_frame_decoder_tb;
       reg clk, rst_n, scl_in, frame_start, frame_stop;
       wire [6:0] addr;
       wire dir_is_read, addr_acked, addr_valid;
       wire [7:0] data_byte;
       wire data_acked, data_valid;
       wire [7:0] byte_count;
       wire frame_complete, frame_error;
       integer errors, n_addr, n_data, n_frame, i;

       // The testbench is the only driver: the decoder is a passive observer.
       reg  tb_drive_low;
       wire sda_bus = ~tb_drive_low;

       i2c_frame_decoder dut (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_bus),
           .frame_start(frame_start), .frame_stop(frame_stop),
           .addr(addr), .dir_is_read(dir_is_read), .addr_acked(addr_acked),
           .addr_valid(addr_valid), .data_byte(data_byte), .data_acked(data_acked),
           .data_valid(data_valid), .byte_count(byte_count),
           .frame_complete(frame_complete), .frame_error(frame_error));

       initial clk = 1'b0;
       always #5 clk = ~clk;
       initial begin #200000; $display("FAIL: watchdog expired"); $finish; end

       reg [7:0] data_log [0:7];
       reg       ack_log  [0:7];
       always @(posedge clk) if (rst_n) begin
           if (addr_valid) n_addr = n_addr + 1;
           if (data_valid) begin
               if (n_data < 8) begin data_log[n_data] = data_byte; ack_log[n_data] = data_acked; end
               n_data = n_data + 1;
           end
           if (frame_complete) n_frame = n_frame + 1;
       end

       // Drive one bit, changing SDA only while SCL is low.
       task send_bit; input b; begin
           scl_in = 1'b0;          repeat (1) @(negedge clk);
           tb_drive_low = ~b;      repeat (1) @(negedge clk);
           scl_in = 1'b1;          repeat (2) @(negedge clk);
           scl_in = 1'b0;          repeat (1) @(negedge clk);
       end endtask

       // A whole byte plus its acknowledge slot. `ack` is what the far end answers.
       task send_byte; input [7:0] v; input ack; begin
           for (i = 7; i >= 0; i = i - 1) send_bit(v[i]);
           send_bit(~ack);          // ACK is a LOW, so an ack of 1 drives a 0 bit
           tb_drive_low = 1'b0;     repeat (1) @(negedge clk);
       end endtask

       task pulse_start; begin frame_start = 1'b1; @(negedge clk); frame_start = 1'b0; @(negedge clk); end endtask
       task pulse_stop;  begin frame_stop  = 1'b1; @(negedge clk); frame_stop  = 1'b0; @(negedge clk); end endtask

       initial begin
           errors = 0; n_addr = 0; n_data = 0; n_frame = 0; tb_drive_low = 1'b0;
           rst_n = 1'b0; scl_in = 1'b1; frame_start = 1'b0; frame_stop = 1'b0;
           repeat (3) @(negedge clk);
           if (frame_error !== 1'b0) begin $display("FAIL: frame_error out of reset"); errors = errors + 1; end
           rst_n = 1'b1; @(negedge clk);

           // ---- 1: a complete WRITE frame. Address 0x48 + W, two data bytes, all ACKed.
           pulse_start();
           send_byte({7'h48, 1'b0}, 1'b1);       // address byte, acknowledged
           if (n_addr != 1) begin $display("FAIL: address phase not decoded once"); errors = errors + 1; end
           if (addr !== 7'h48) begin $display("FAIL: decoded addr 0x%02h, expected 0x48", addr); errors = errors + 1; end
           if (dir_is_read !== 1'b0) begin $display("FAIL: direction should be write"); errors = errors + 1; end
           if (addr_acked !== 1'b1) begin $display("FAIL: address ACK not recorded"); errors = errors + 1; end

           send_byte(8'hDE, 1'b1);
           send_byte(8'hAD, 1'b1);
           if (n_data != 2) begin $display("FAIL: expected 2 data bytes, saw %0d", n_data); errors = errors + 1; end
           if (byte_count !== 8'd2) begin $display("FAIL: byte_count = %0d, expected 2", byte_count); errors = errors + 1; end
           if (data_log[0] !== 8'hDE || data_log[1] !== 8'hAD) begin
               $display("FAIL: data bytes 0x%02h,0x%02h, expected 0xDE,0xAD", data_log[0], data_log[1]);
               errors = errors + 1; end
           pulse_stop();
           if (n_frame != 1) begin $display("FAIL: frame_complete not pulsed at the STOP"); errors = errors + 1; end
           // The decoded fields must SURVIVE the STOP -- a consumer reads them after.
           if (addr !== 7'h48 || byte_count !== 8'd2) begin
               $display("FAIL: decoded fields were cleared by the STOP"); errors = errors + 1; end

           // ---- 2: a READ frame, and the final byte NACKed -- Chapter 7.4's pattern.
           n_data = 0;
           pulse_start();
           send_byte({7'h50, 1'b1}, 1'b1);       // address + R, acknowledged by the slave
           if (dir_is_read !== 1'b1) begin $display("FAIL: direction should be read"); errors = errors + 1; end
           send_byte(8'h11, 1'b1);               // master ACKs -- another byte please
           send_byte(8'h22, 1'b0);               // master NACKs -- that was the last
           if (n_data != 2) begin $display("FAIL: expected 2 read bytes, saw %0d", n_data); errors = errors + 1; end
           if (ack_log[0] !== 1'b1) begin $display("FAIL: first read byte should be ACKed"); errors = errors + 1; end
           if (ack_log[1] !== 1'b0) begin
               $display("FAIL: the final read byte must be recorded as NACKed"); errors = errors + 1; end
           pulse_stop();

           // ---- 3: an address that NOBODY answers. The frame is still well formed;
           //         the decoder must report the NACK rather than hiding it.
           n_data = 0;
           pulse_start();
           send_byte({7'h7A, 1'b0}, 1'b0);       // nothing at this address
           if (addr_acked !== 1'b0) begin
               $display("FAIL: an unanswered address must be recorded as NACKed"); errors = errors + 1; end
           if (addr !== 7'h7A) begin $display("FAIL: address still decodes on a NACK"); errors = errors + 1; end
           pulse_stop();

           // ---- 4: a repeated START mid-frame restarts the addressing and the count.
           n_data = 0;
           pulse_start();
           send_byte({7'h48, 1'b0}, 1'b1);
           send_byte(8'h01, 1'b1);
           if (byte_count !== 8'd1) begin $display("FAIL: byte_count should be 1 here"); errors = errors + 1; end
           pulse_start();                         // Sr
           send_byte({7'h48, 1'b1}, 1'b1);        // re-addressed for a read
           if (dir_is_read !== 1'b1) begin
               $display("FAIL: the repeated START's direction was not latched"); errors = errors + 1; end
           if (byte_count !== 8'd0) begin
               $display("FAIL: a repeated START must restart the data byte count, got %0d", byte_count);
               errors = errors + 1; end
           send_byte(8'hFF, 1'b0);
           pulse_stop();

           // ---- 5: a byte with NO framing START is not a frame and must be reported.
           if (frame_error !== 1'b0) begin $display("FAIL: spurious frame_error"); errors = errors + 1; end
           send_byte(8'h5A, 1'b1);
           if (frame_error !== 1'b1) begin
               $display("FAIL: a byte outside any frame was not reported"); errors = errors + 1; end

           if (errors == 0)
               $display("PASS: address, direction, data and every acknowledge decoded; Sr restarts; stray byte reported");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_frame_decoder.vhd — SYNTHESIZABLE RTL. The same decoder in VHDL, instantiating both sub-entities.
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- A passive frame observer: it decodes a complete I2C frame off the two wires and
   -- never drives either of them. ASSEMBLED from the byte shifter and the acknowledge
   -- slot built earlier in this module -- it adds only the sequencing that says which
   -- byte is the address and which are data.
   entity i2c_frame_decoder is
       port (
           clk            : in  std_logic;
           rst_n          : in  std_logic;
           scl_in         : in  std_logic;                    -- observed bus level
           sda_in         : in  std_logic;                    -- observed bus level
           frame_start    : in  std_logic;                    -- pulse: S or Sr (Module 5)
           frame_stop     : in  std_logic;                    -- pulse: P (Module 5)
           addr           : out std_logic_vector(6 downto 0);
           dir_is_read    : out std_logic;
           addr_acked     : out std_logic;
           addr_valid     : out std_logic;                    -- pulse: address decoded
           data_byte      : out std_logic_vector(7 downto 0);
           data_acked     : out std_logic;
           data_valid     : out std_logic;                    -- pulse: a data byte decoded
           byte_count     : out unsigned(7 downto 0);
           frame_complete : out std_logic;                    -- pulse: a STOP ended it
           frame_error    : out std_logic                     -- byte with no framing START
       );
   end entity;

   architecture rtl of i2c_frame_decoder is
       signal sh_rx_data   : std_logic_vector(7 downto 0);
       signal sh_rx_valid, sh_ack_slot, sh_byte_done, sh_drive_low : std_logic;
       signal sh_bit_index : natural range 0 to 8;
       signal begin_byte   : std_logic := '0';

       signal ak_drive_low, ak_bit, ak_received, ak_nack, ak_valid : std_logic;

       type state_t is (
           FD_IDLE,      -- no frame in progress
           FD_ADDR,      -- the next byte to complete is the address
           FD_DATA       -- subsequent bytes are data
       );
       signal state : state_t := FD_IDLE;
       signal count : unsigned(7 downto 0) := (others => '0');
   begin
       u_shift : entity work.i2c_byte_shifter
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
                     begin_byte => begin_byte, tx_enable => '0', tx_data => x"00",
                     sda_drive_low => sh_drive_low, rx_data => sh_rx_data,
                     rx_valid => sh_rx_valid, ack_slot => sh_ack_slot,
                     byte_done => sh_byte_done, bit_index => sh_bit_index);

       u_ack : entity work.i2c_ack_slot
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
                     ack_slot => sh_ack_slot, we_acknowledge => '0', send_ack => '0',
                     sda_drive_low => ak_drive_low, ack_bit => ak_bit,
                     ack_received => ak_received, nack_received => ak_nack,
                     ack_valid => ak_valid);

       byte_count <= count;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   state          <= FD_IDLE;
                   addr           <= (others => '0');
                   dir_is_read    <= '0';
                   addr_acked     <= '0';
                   addr_valid     <= '0';
                   data_byte      <= (others => '0');
                   data_acked     <= '0';
                   data_valid     <= '0';
                   count          <= (others => '0');
                   frame_complete <= '0';
                   frame_error    <= '0';
                   begin_byte     <= '0';
               else
                   addr_valid     <= '0';
                   data_valid     <= '0';
                   frame_complete <= '0';
                   begin_byte     <= '0';

                   if frame_stop = '1' then
                       -- A STOP ends the frame. It does NOT clear the decoded fields:
                       -- a consumer reads them after the frame, so they must survive it.
                       if state /= FD_IDLE then frame_complete <= '1'; end if;
                       state <= FD_IDLE;
                   elsif frame_start = '1' then
                       -- S or Sr both open an address phase, and a repeated START
                       -- restarts the byte count.
                       state      <= FD_ADDR;
                       count      <= (others => '0');
                       begin_byte <= '1';
                   elsif ak_valid = '1' then
                       -- The ninth bit has been sampled, so the byte AND its answer are
                       -- both known. Triggering here rather than on byte_done is what
                       -- makes the acknowledge available in the same cycle as the data.
                       case state is
                           when FD_ADDR =>
                               addr        <= sh_rx_data(7 downto 1);
                               dir_is_read <= sh_rx_data(0);
                               addr_acked  <= ak_received;
                               addr_valid  <= '1';
                               state       <= FD_DATA;
                               begin_byte  <= '1';      -- arm for the first data byte
                           when FD_DATA =>
                               data_byte  <= sh_rx_data;
                               data_acked <= ak_received;
                               data_valid <= '1';
                               count      <= count + 1;
                               begin_byte <= '1';
                           when others =>
                               -- A byte completed with no framing START before it. That
                               -- is not a decodable frame, and reporting it is more
                               -- useful than inventing an address for it.
                               frame_error <= '1';
                       end case;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_frame_decoder_tb.vhd — SELF-CHECKING TESTBENCH, SIMULATION ONLY. The same cases with assert report severity.
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_frame_decoder_tb is
   end entity;

   architecture sim of i2c_frame_decoder_tb is
       signal clk         : std_logic := '0';
       signal rst_n       : std_logic := '0';
       signal scl_in      : std_logic := '1';
       signal frame_start : std_logic := '0';
       signal frame_stop  : std_logic := '0';
       signal addr        : std_logic_vector(6 downto 0);
       signal dir_is_read, addr_acked, addr_valid : std_logic;
       signal data_byte   : std_logic_vector(7 downto 0);
       signal data_acked, data_valid : std_logic;
       signal byte_count  : unsigned(7 downto 0);
       signal frame_complete, frame_error : std_logic;

       -- The testbench is the only driver: the decoder is a passive observer.
       signal tb_drive_low : std_logic := '0';
       signal sda_bus      : std_logic;

       signal n_addr, n_data, n_frame : natural := 0;
       type byte_log_t is array (0 to 7) of std_logic_vector(7 downto 0);
       type ack_log_t  is array (0 to 7) of std_logic;
       signal data_log : byte_log_t := (others => (others => '0'));
       signal ack_log  : ack_log_t  := (others => '0');
       signal test_done : std_logic := '0';
   begin
       sda_bus <= not tb_drive_low;

       dut : entity work.i2c_frame_decoder
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_bus,
                     frame_start => frame_start, frame_stop => frame_stop,
                     addr => addr, dir_is_read => dir_is_read, addr_acked => addr_acked,
                     addr_valid => addr_valid, data_byte => data_byte,
                     data_acked => data_acked, data_valid => data_valid,
                     byte_count => byte_count, frame_complete => frame_complete,
                     frame_error => frame_error);

       clk <= not clk after 5 ns;

       watchdog : process
       begin
           wait for 200 us;
           if test_done = '0' then
               report "watchdog expired -- the design never reached the expected state"
                   severity failure;
           end if;
           wait;
       end process;

       count : process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '1' then
                   if addr_valid = '1' then n_addr <= n_addr + 1; end if;
                   if data_valid = '1' then
                       if n_data < 8 then
                           data_log(n_data) <= data_byte;
                           ack_log(n_data)  <= data_acked;
                       end if;
                       n_data <= n_data + 1;
                   end if;
                   if frame_complete = '1' then n_frame <= n_frame + 1; end if;
               end if;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable base : natural := 0;

           procedure waitn (n : in positive) is
           begin
               for i in 1 to n loop wait until falling_edge(clk); end loop;
           end procedure;

           procedure send_bit (b : in std_logic) is
           begin
               scl_in <= '0';          waitn(1);
               tb_drive_low <= not b;  waitn(1);   -- change only while SCL is low
               scl_in <= '1';          waitn(2);
               scl_in <= '0';          waitn(1);
           end procedure;

           -- A whole byte plus its acknowledge slot. `ack` is what the far end answers;
           -- ACK is a LOW, so an ack of '1' drives a '0' bit.
           procedure send_byte (v : in std_logic_vector(7 downto 0); ack : in std_logic) is
           begin
               for i in 7 downto 0 loop send_bit(v(i)); end loop;
               send_bit(not ack);
               tb_drive_low <= '0';    waitn(1);
           end procedure;

           procedure pulse_start is
           begin
               frame_start <= '1'; waitn(1); frame_start <= '0'; waitn(1);
           end procedure;

           procedure pulse_stop is
           begin
               frame_stop <= '1'; waitn(1); frame_stop <= '0'; waitn(1);
           end procedure;
       begin
           waitn(3);
           if frame_error /= '0' then
               report "frame_error out of reset" severity error; errs := errs + 1; end if;
           rst_n <= '1'; waitn(1);

           -- 1: a complete WRITE frame. Address 0x48 + W, two data bytes, all ACKed.
           pulse_start;
           send_byte("10010000", '1');            -- 0x48 shifted left, write
           if n_addr /= 1 then
               report "address phase not decoded once" severity error; errs := errs + 1; end if;
           if addr /= "1001000" then
               report "decoded address wrong" severity error; errs := errs + 1; end if;
           if dir_is_read /= '0' then
               report "direction should be write" severity error; errs := errs + 1; end if;
           if addr_acked /= '1' then
               report "address ACK not recorded" severity error; errs := errs + 1; end if;

           send_byte(x"DE", '1');
           send_byte(x"AD", '1');
           if n_data /= 2 then
               report "expected 2 data bytes" severity error; errs := errs + 1; end if;
           if byte_count /= 2 then
               report "byte_count wrong" severity error; errs := errs + 1; end if;
           if data_log(0) /= x"DE" or data_log(1) /= x"AD" then
               report "data bytes wrong" severity error; errs := errs + 1; end if;
           pulse_stop;
           if n_frame /= 1 then
               report "frame_complete not pulsed at the STOP" severity error; errs := errs + 1; end if;
           -- The decoded fields must SURVIVE the STOP.
           if addr /= "1001000" or byte_count /= 2 then
               report "decoded fields were cleared by the STOP" severity error; errs := errs + 1; end if;

           -- 2: a READ frame with the final byte NACKed -- Chapter 7.4's pattern.
           base := n_data;
           pulse_start;
           send_byte("10100001", '1');            -- 0x50 + R
           if dir_is_read /= '1' then
               report "direction should be read" severity error; errs := errs + 1; end if;
           send_byte(x"11", '1');                 -- ACK: another byte please
           send_byte(x"22", '0');                 -- NACK: that was the last
           if (n_data - base) /= 2 then
               report "expected 2 read bytes" severity error; errs := errs + 1; end if;
           if ack_log(2) /= '1' then
               report "first read byte should be ACKed" severity error; errs := errs + 1; end if;
           if ack_log(3) /= '0' then
               report "the final read byte must be recorded as NACKed" severity error;
               errs := errs + 1; end if;
           pulse_stop;

           -- 3: an address NOBODY answers -- report the NACK rather than hide it.
           pulse_start;
           send_byte("11110100", '0');            -- 0x7A + W, unanswered
           if addr_acked /= '0' then
               report "an unanswered address must be recorded as NACKed" severity error;
               errs := errs + 1; end if;
           if addr /= "1111010" then
               report "address must still decode on a NACK" severity error; errs := errs + 1; end if;
           pulse_stop;

           -- 4: a repeated START restarts the addressing and the data byte count.
           pulse_start;
           send_byte("10010000", '1');
           send_byte(x"01", '1');
           if byte_count /= 1 then
               report "byte_count should be 1 here" severity error; errs := errs + 1; end if;
           pulse_start;                            -- Sr
           send_byte("10010001", '1');             -- re-addressed for a read
           if dir_is_read /= '1' then
               report "the repeated START's direction was not latched" severity error;
               errs := errs + 1; end if;
           if byte_count /= 0 then
               report "a repeated START must restart the data byte count" severity error;
               errs := errs + 1; end if;
           send_byte(x"FF", '0');
           pulse_stop;

           -- 5: a byte with NO framing START is not a frame and must be reported.
           if frame_error /= '0' then
               report "spurious frame_error" severity error; errs := errs + 1; end if;
           send_byte(x"5A", '1');
           if frame_error /= '1' then
               report "a byte outside any frame was not reported" severity error;
               errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_frame_decoder self-check complete: address, direction, data and every "
                    & "acknowledge decoded; Sr restarts; stray byte reported" severity note;
           else
               report "i2c_frame_decoder self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

5a. Three Decisions Worth Defending

It triggers on ack_valid, not on byte_done. Both pulse on the ninth rising edge, so the choice looks arbitrary — and §7's mutation confirms they are behaviourally identical. It is still the right choice, because ack_valid names the reason: the byte and its answer are both known at that instant. A reader of the code learns why the trigger is there, which byte_done would not convey. This is a readability decision made deliberately in the knowledge that no test can enforce it.

It re-arms the shifter itself. begin_byte is generated by the decoder on a framing START and after every completed byte, so the shifter never needs to know what a frame is. That keeps the byte-level block free of frame-level concepts and is why it was reusable here at all.

A framing START restarts the byte count. After a repeated START the bytes belong to a new addressing, so counting them with the previous phase's total would misreport both. §7 injects the fault and the testbench catches it with a mid-frame Sr.

5b. Verified Execution

Each language's testbench was compiled with the decoder and both sub-blocks:

languagefiles togethersimulatorresultcompletes at
SystemVerilogdecoder + shifter + ack slot + TBIcarus Verilog, -g2012PASS5740 ns
Verilog-2005decoder + shifter + ack slot + TBIcarus Verilog, -g2005PASS5740 ns
VHDLall three analysed, then elaboratednvc 1.23.0PASS5740 ns
SystemVerilogVerilog-2005VHDL
statestypedef enum logic [1:0]localparam constantstype state_t is (...)
instantiationnamed portsnamed portsentity work.<name> direct
tie-offs1'b0 / 8'h00 in the port mapsame'0' / x"00" in the port map
byte countlogic [7:0]reg [7:0]internal unsigned, driven to the port

i2c_frame_decoder — the address phase resolves

10 cycles
Ten internal clock cycles. The observed SCL input goes high for three cycles, which is the ninth clock pulse of the address byte. An internal acknowledge-valid pulse follows, and one cycle later the address valid output pulses while the address, direction and address-acknowledged outputs take their decoded values and hold them.ninth clock: the ACK is sampledninth clock: the ACK issampledthe answer is knownthe answer is knownaddress phase decodedaddress phase decodedclkscl_inack_validaddr_validaddr--------484848484848dir_is_readaddr_ackedt0t1t2t3t4t5t6t7t8t9
Figure 2 — the address phase being decoded, from the simulation, on the internal clock. The ninth clock pulse of the address byte samples the acknowledge; one cycle later the acknowledge-slot block publishes it, and the decoder latches the address, the direction and whether it was answered. The decoded fields then persist. SIMULATION-DERIVED figure.

i2c_frame_decoder — a data byte and its answer, together

10 cycles
Ten internal clock cycles. The observed SCL input goes high for the ninth clock pulse of a data byte. An internal acknowledge-valid pulse follows and one cycle later the data valid output pulses, the data byte output takes the value hex DE, the data acknowledged output rises, and the byte count advances from zero to one.the answer is knownthe answer is knownbyte and answer published togetherbyte and answer publishedtogetherclkscl_inack_validdata_validdata_byte--------DEDEDEDEDEDEdata_ackedbyte_count0000111111t0t1t2t3t4t5t6t7t8t9
Figure 3 — a data byte being decoded. The same nine-clock structure and the same trigger, now landing in the data state: the byte and its acknowledge are published together and the byte count advances. Publishing them as one event is what stops a consumer having to re-pair a byte with its answer.

6. What the Testbench Proves

Five frames, chosen so that each exercises a different structural case rather than a different payload.

#framewhat only it establishes
1write to 0x48, two bytes, all ACKedthe ordinary case, the byte count, and that the fields survive the STOP
2read from 0x50, two bytes, final one NACKedthe direction latches, and 7.4's pattern is recorded per byte
3address 0x7A, nobody answersthe address still decodes on a NACK — the frame is well formed, the device is absent
4write, one byte, Sr, re-address for reada repeated START restarts the count and re-latches the direction
5a byte with no framing STARTreported as frame_error rather than decoded into an invented frame

Frame 3 is the one that separates a decoder from a verdict machine. The address is decoded, the direction is decoded, and addr_acked is low. All three are true and useful — a decoder that reported only "failed" would have discarded the address, which is the field you need to know which device was absent.

Frame 4 is the case §1 called the most important thing to spot in a capture. After the Sr the byte count returns to zero and the direction is re-read from the new address byte. A decoder that carried the count across would report one frame of three bytes where there were two phases of one byte each.

Frame 5 is a decoder refusing to guess. A byte with no preceding framing is not a frame, and inventing an address for it would produce a plausible-looking wrong answer — which is worse than an error flag.

7. Mutation Testing

Five faults injected into the verified RTL. Four caught; one is provably equivalent.

mutationwhat it breaksresult
treat the address byte as dataevery frame is shifted by one byteFAIL — address never decoded
a repeated START does not restart the counttwo phases reported as oneFAIL — count wrong after the Sr
a STOP clears the decoded fieldsthe consumer reads zerosFAIL — fields cleared by the STOP
a stray byte is not reporteda non-frame decoded as a frameFAIL — no frame_error
trigger on byte_done instead of ack_valid—survived, provably equivalent

The survivor is equivalent by construction, and the proof is two lines of RTL. In the byte shifter, byte_done is set on the ninth scl_rise. In the acknowledge slot, ack_valid is set on the same scl_rise, because ack_slot is combinational and high throughout the ninth slot. Both are registered at the same clock edge, and ack_bit is latched at that same edge — so the acknowledge is readable in the cycle either trigger fires. No stimulus can separate them.

The ack_valid form is still the better code, for the reason in §5a: it names why the trigger exists. But it is a readability choice, and being honest about that is the difference between reporting four of five caught with one equivalence proven, and quietly claiming five.

Module 7 totals: 27 mutations injected, 26 caught, 1 proven equivalent. Two of the 26 required new tests before they could be caught — Chapter 7.1's ninth-slot release, which needed a byte ending in zero, and Chapter 7.2's sampling edge, which needed the far end to release exactly as SCL falls. Both are the same shape: a fault that is only observable under stimulus that makes its condition bite.

8. Verification Connection — The Frame Is the Transaction

Everything in this module has been building toward the object a protocol monitor publishes.

Azvya Education Pvt. Ltd.VLSI Mentor
UVM CONCEPT — VERIFICATION ONLY. The frame as a transaction, with per-byte answers.
   // The frame is the unit a scoreboard reasons about. Note what is stored: every
   // acknowledge, not a summary -- because Chapter 7.2 section 3 established that
   // one bit has five meanings and only POSITION narrows them.
   class i2c_frame extends uvm_sequence_item;
       `uvm_object_utils(i2c_frame)
       bit [6:0]  addr;
       bit        is_read;
       bit        addr_acked;
       bit [7:0]  data [$];        // the payload, in order
       bit        data_acked [$];  // one answer per byte, same indices
       bit        ended_with_stop; // false if an Sr continued the transaction
       bit        malformed;       // a byte arrived outside any framing

       // A frame is well formed if its ENDING matches its direction. This is the
       // check that catches Chapter 7.4's hang, and it cannot be expressed as
       // "no unexpected NACKs occurred".
       function bit ending_is_legal();
           if (data_acked.size() == 0) return 1;              // address-only frame
           if (is_read)  return (data_acked[$] == 0);         // a read MUST end NACKed
           else          return 1;                            // a write may end either way
       endfunction

       function string convert2string();
           string s = $sformatf("%s 0x%02h %s |", is_read ? "RD" : "WR",
                                addr, addr_acked ? "A" : "N");
           foreach (data[i]) s = {s, $sformatf(" %02h%s", data[i], data_acked[i] ? "A" : "N")};
           return {s, ended_with_stop ? " P" : " Sr"};
       endfunction
   endclass

Three observations, and the last is the practical one.

Per-byte acknowledges are stored, not summarised. A boolean "all acknowledged" destroys §3's diagnostic table. Storing the vector costs nothing and is what lets a scoreboard say which byte declined.

ending_is_legal is asymmetric on purpose. A read must end with a NACK; a write may legitimately end either way, because a write's final byte is acknowledged by the slave and a NACK there is condition 3 or 4 rather than a termination. Encoding that asymmetry is what makes the check usable without false positives.

convert2string is the highest-value line in the class. A log reading RD 0x50 A | 11A 22N P is a complete frame in eleven characters — address, direction, every acknowledge, and the ending. An engineer scanning a thousand of those spots the anomalous one instantly, which no amount of structured reporting achieves as quickly.

9. FPGA and ASIC Implications

A passive decoder is the cheapest debug instrument available. The whole thing is the byte shifter, the acknowledge slot and a three-state machine — under a hundred flip-flops. Instantiating one alongside a controller, with its outputs wired to a small FIFO, gives a bring-up engineer the frame log of §8 without an oscilloscope. It is the single highest-return piece of debug hardware on an I²C interface and it is routinely omitted.

Tie-offs are how a bidirectional block becomes an observer. The shifter and the acknowledge slot are both capable of driving; wiring tx_enable and we_acknowledge to zero makes them incapable of it. That is a stronger guarantee than trusting a state machine never to assert them, and it is worth doing deliberately rather than by omission — a reviewer can see a tie-off.

The decoder needs the same synchronised inputs as everything else. It samples SDA and SCL, so Module 19's synchroniser sits in front of it. A monitor with its own unsynchronised sampling path can disagree with the controller about what the bus did, which is the worst possible property in a debug instrument.

A monitor enabled mid-transfer has no valid state, exactly as Chapter 5.4 §9 and Chapter 6.5 established for their state. The decoder's frame_error output is the honest response: a byte with no preceding framing is reported rather than decoded. The clean recovery is to treat the next STOP as a resynchronisation point.

Expose the byte count and the last acknowledge to software. A driver that can read how far a frame got, and whether the last byte was answered, can distinguish a NACK on the address from a NACK on byte twelve — which is the difference between "the device is absent" and "the device stopped accepting", and those lead to different recovery actions.

10. Debugging — The Capture That Read Backwards

A register dump that made sense only if the sensor had two register maps

Pitfall — missing a repeated START and reading an address byte as data
Buggy Code
// An engineer captures a failing sensor access and decodes it by hand from the
// waveform. The transfer on the wire is a standard register-pointer read:
//
//     S  0x90  A  0x00  A  Sr  0x91  A  0x1C  A  0x2F  N  P
//        ^^^^     ^^^^      ^^^^^^^     ^^^^     ^^^^
//        addr+W   pointer   addr+R      data     data
//
// The hand decode, done by counting nine-clock groups from the START and reading
// them off in order, produces:
//
//     addr 0x48 W, data = { 0x00, 0x91, 0x1C, 0x2F }
//                            ^^^^  ^^^^
//                            pointer, then... 0x91 as DATA
//
// The repeated START was not spotted -- it is a single SDA falling edge in the
// middle of what looks like a continuous stream of clock pulses, and the counting
// method never looked for it.
Symptom

The decode is internally consistent and completely wrong.

It says the master wrote four bytes to register 0x00 of device 0x48. The engineer checks the datasheet: register 0x00 is a read-only ID register, and writing four bytes to it is meaningless. So the conclusion is that the DRIVER is writing garbage to a read-only register, and the investigation moves to the driver.

The driver's source clearly performs a register-pointer read -- write the pointer, repeated START, read two bytes. It does not write four bytes to anything. So now the driver and the capture disagree, and the natural assumption is that the peripheral hardware is malfunctioning: generating spurious bytes, or losing the repeated START.

Considerable time goes into the controller's Sr generation, which is working perfectly. The value 0x91 appearing in the middle of the data is the clue that resolves it, and it is sitting in plain sight: 0x91 is 0x48 shifted left with the read bit set -- it is the device's own address, and an address byte has no business being in a data stream.

Root Cause

A repeated START was missed, so one address byte was decoded as data and everything after it shifted.

Section 1 states it as the most important thing to spot in a capture: an Sr can appear between any two bytes, and the byte following it is an ADDRESS, not data. Section 2's pass 1 exists precisely to find these before any byte is counted -- scan for SDA edges during SCL high FIRST, and only then count nine-clock groups within each resulting segment.

The counting method used here inverted that order. It found the first START, counted groups of nine from there, and never re-examined SDA for framing. A repeated START is exactly one falling SDA edge during one high phase, visually indistinguishable at low zoom from a data bit going low -- so a scan that is looking for byte boundaries rather than for framing will pass straight over it.

Note that every individual byte was decoded CORRECTLY. The bits were read right, the nine-clock grouping was right, and the acknowledges were right. The single error was in the frame STRUCTURE, and a structural error relabels everything downstream without corrupting anything -- which is why the result was internally consistent and why it survived scrutiny.

And note the confirming detail that was available immediately: the acknowledge pattern. The decode claimed a WRITE whose last byte was NACKed, which section 3's table lists as "it stopped accepting". But the real frame was a READ, and a read's final NACK is normal termination. A pass-5 reading of the acknowledge column would have flagged the inconsistency between the claimed direction and the ending before anybody opened the datasheet.

Fix
// Decode in the order of section 2, and let the tooling do pass 1:
//
//   PASS 1 first, always. Scan for SDA edges while SCL is HIGH. Mark every one.
//           Falling = S or Sr. Rising = P. Do this before counting a single byte.
//   THEN count nine-clock groups WITHIN each segment between framing events.
//
// The frame decoder of section 5 does exactly this -- frame_start is an input that
// overrides everything, so a repeated START restarts the address phase and the byte
// count unconditionally. Section 7 injects the fault of NOT restarting the count and
// the testbench catches it with a mid-frame Sr, which is this bug in RTL form.
//
// The lessons, ordered by how far they generalise.
//
//   1. STRUCTURE BEFORE CONTENT. A structural misreading produces a self-consistent
//      wrong answer, which is far more expensive than a corrupted one because
//      nothing looks wrong. Find the frame boundaries before decoding what is
//      inside them.
//
//   2. AN ADDRESS BYTE IN A DATA STREAM IS A MISSED Sr. If a decoded data byte
//      equals the device's own address shifted left -- 0x90 or 0x91 for a device at
//      0x48 -- you have almost certainly missed a repeated START. This is a
//      two-second check and it is the single most useful sanity test on a hand
//      decode.
//
//   3. CHECK THE ENDING AGAINST THE DIRECTION. Section 3's table: a read must end
//      with a NACK, a write normally does not. A decode claiming a write that ends
//      in a NACK, or a read that ends in an ACK, is either a real bug or a
//      misreading -- and in both cases it is the next thing to investigate.
//
//   4. USE A DECODER RATHER THAN COUNTING BY HAND. Section 9's point: a passive
//      frame decoder is under a hundred flip-flops and it cannot miss a framing
//      event, because framing is its first input rather than something it infers
//      from byte boundaries. Hand decoding is a skill worth having for when there is
//      no instrument, not a method to prefer when there is one.
//
// The habit: before believing any hand decode, count the framing events and check
// that the number of address bytes equals the number of S and Sr events. This decode
// had two framing falls and claimed one address byte, and that arithmetic alone
// would have found it.

11. Common Misconceptions

"A frame contains a length field." It contains no length anywhere. A frame ends when framing ends it, and the byte count exists only if somebody counted.

"You can find byte boundaries by counting nine clocks from the start." Only within one segment. A repeated START anywhere resets the structure, and a count that ignores framing shifts everything after it — §10 is that failure.

"A repeated START is visually obvious." It is one SDA falling edge during one high phase, and at low zoom it looks like a data bit. Pass 1 exists because it is not obvious.

"After a repeated START the next byte continues the data." It is an address byte. That single fact is what §10's decode got wrong.

"A decoder should report whether the transfer succeeded." One acknowledge bit has five meanings, so a verdict discards the information needed to choose between them. Record every acknowledge and let the consumer diagnose.

"A NACKed address means there is nothing to decode." The address and direction are still there and still useful — knowing which device did not answer is the whole point.

"The decoded frame can be cleared at the STOP." A consumer reads it after the frame completes. Clearing on the STOP makes the decoder useless, and §7 injects exactly that fault.

"A read ending in an acknowledge is a minor anomaly." It is a prediction that the bus is about to hang, because the slave has been asked for another byte and will hold SDA. §3's fifth row.

12. Reason It Through

A capture shows two SDA falling edges during high phases and one rising edge. How many address bytes should your decode contain?

Two. Each framing fall — one START and one repeated START — begins an address phase, so there are two address bytes and one STOP. A decode claiming a single address byte has missed a framing event, and that arithmetic is the fastest check on a hand decode.

You decode a data byte as 0x91 in a transfer with a device at 0x48. What should you suspect immediately?

A missed repeated START. 0x91 is 0x48 shifted left with the read bit set — it is the device's own address, and an address byte appearing in a data stream almost always means a framing event was not spotted, so everything after it has been relabelled.

Your decode says a write whose final byte was NACKed. Why is that worth a second look?

Because a NACK on a write's last byte is condition 3 or 4 — the device did not understand it or could not take it — which is a real anomaly. But it is also exactly what a read looks like if you got the direction bit backwards, since a read's final NACK is normal termination. So it is either a device problem or a decoding error, and checking the direction bit again costs nothing.

Why does the frame decoder keep its decoded fields after the STOP?

Because a consumer reads them once the frame is complete — that is when a monitor publishes a transaction and when software services an interrupt. Clearing on the STOP would mean the outputs are only valid during a window that has already closed by the time anything looks at them.

A monitor is enabled in the middle of a transfer. What should it do with the first byte it completes?

Report it as an error rather than decode it, because it has no preceding framing and therefore no way to know whether it is an address or data. Inventing a frame would produce a plausible wrong answer. The clean recovery is to treat the next STOP as a resynchronisation point and decode confidently from then on.

Why is it worth instantiating a passive frame decoder in a design that already has a working controller?

Because it cannot miss a framing event — framing is its input rather than something it infers — and it produces the frame log of §8 without an oscilloscope. It costs under a hundred flip-flops, and it turns the class of bug in §10 from an afternoon of hand decoding into a line of log output.

13. Understanding Check

14. Summary

Every frame has the same skeleton: START, address plus direction, data bytes each with an acknowledge, optional repeated STARTs each beginning a new address phase, and a STOP. There is no length field.

Read a capture structure-first. Find the framing edges before counting bytes; a count that ignores framing relabels everything after a missed repeated START — and the result is self-consistent and wrong.

After a repeated START the next byte is an address. An address byte appearing in a decoded data stream is the signature of a missed one.

The acknowledge column is a diagnosis. Which byte NACKed, and whether the ending matches the direction, identifies the failure faster than the payload does — and a read ending in an acknowledge predicts a hung bus.

A decoder records observations, not verdicts. Every acknowledge, the byte count, the address even when it was not answered, and whether framing ended the frame. One bit with five meanings cannot be collapsed to a boolean.

The decoded frame must survive the STOP, because that is when anything reads it.

The decoder is assembled from two bidirectional blocks made passive by tie-offs — a stronger and more visible guarantee than logic that merely never drives.

Module 7 totals: 27 mutations, 26 caught, 1 proven equivalent — and two of the 26 needed new tests first, both of the same shape: a fault observable only under stimulus that makes its condition bite.

15. What Comes Next

Module 7 is complete. The bus now has a fully accounted-for byte: nine clock pulses, MSB first, an acknowledge whose owner is determined by direction, a defined way for a master-receiver to end a read, and a decoder that reconstructs the whole frame from the two wires.

What has been assembled in fragments is a transaction. Module 8 — Write Transactions takes the simpler direction first and walks a complete write end to end as one continuous story — START, address plus W, data, acknowledge, STOP — with every bit accounted for, then splits the master-transmitter and slave-receiver responsibilities and computes what a multi-byte burst really costs once the ninth clock is counted.

Browse the full path on the I²C tutorials index. For the blocks this chapter assembles, see Byte and Bit Transmission and The ACK/NACK Cycle; for the framing that gives a frame its boundaries, Bus Idle and the Framing Primitives.

Continue learning