Skip to content
VLSI Mentor

I²C · Module 17

START and STOP Generation — The Framing Sequencer

The edges the bit engine cannot produce, and why that is structural rather than stylistic. Builds the four framing sequences from their Table 10 intervals, shows why every SCL release inside them must wait for the readback or the START is not a START at all, and finds a defect that no bus-level check could have caught.

Chapter 17.1 §3 proved that one block in this master cannot be merged into any other. This is that block.

Both lines now have controllers: 17.3 gives SCL legal phases and announces the two datapath instants, and 17.4 gives SDA one inversion and explicit ownership. Neither of them can produce a START.

1. Why This Cannot Be a Mode of the Bit Engine

Those are contradictory, deliberately. Framing is recognisable precisely because it is the one thing a data bit may never do.

So: the bit engine's whole job is to guarantee SDA is stable while SCL is high. Framing requires the opposite. The two cannot be the same block — not for readability, but because they have opposite postconditions on the same wire in the same phase.

2. The Four Sequences, and the Parameter Each Is Built From

SequenceWhat happensTable 10 parameter
START from idleSDA falls while SCL high, then hold before SCL fallstHD;STA
repeated STARTSDA released, SCL released, wait, SDA falls, then holdtSU;STA then tHD;STA
STOPSDA low, SCL released, wait, SDA risestSU;STO
bus freeafter a STOP, before the next START may be issuedtBUF

Two details in that table do real work.

A repeated START must lift SDA first. Mid-transfer SDA may be sitting low — it was a data zero. A line that is already low cannot produce a HIGH-to-LOW transition, so the sequence has to release SDA before anything else. A framer that skips this emits no edge at all and the bus sees nothing.

tBUF is the one interval that constrains a master while it is doing nothing. It is not a property of a transfer; it is a property of the gap between two of them, and it is the only Table 10 parameter that applies to an idle master.

3. Every SCL Release Waits for the Readback

This is the same rule Chapter 17.3 established for the clock generator, and it applies with more force here.

A repeated START releases SCL and must not begin measuring tSU;STA until the line is actually high — because a target may be stretching, and tSU;STA is a setup time against the SDA falling edge. If SCL never rose, the interval is meaningless.

4. The Sequences, on the Wire

START then STOP — the establishing state, the edge, and the interval that follows

10 cycles
Ten intervals. In the first two both lines are released and high, the bus idle. At interval two SDA is pulled low while SCL is still high: that transition is the START. SDA stays low for the hold interval through interval four, and only at interval five does SCL fall, beginning the transfer. Later, at interval seven, SCL is released and reads high while SDA is still held low; that is the setup interval for the stop. At interval nine SDA is released while SCL is high, which is the STOP, and both lines are then high again.tHD;STAtHD;STAtSU;STOtSU;STObus releasedbus releasedSTART — SDA falls, SCL highSTART — SDA falls, SCL highonly now may SCL fallonly now may SCL fallSCL released and reads highSCL released and reads highSTOP — SDA rises, SCL highSTOP — SDA rises, SCL highscl_bussda_bust0t1t2t3t4t5t6t7t8t9
Figure 1 — the START sequence and the STOP sequence, drawn at the resolution at which they differ. Conceptual figure: interval widths are chosen for legibility and carry no specification proportion. Both begin from a state the framer must establish before the edge, which is the part a first implementation omits.

Read the two intervals as the same shape pointing opposite ways. tHD;STA runs forward from an edge the framer produced to an edge it must not produce too early. tSU;STO runs forward to an edge, from a condition the framer must first establish. One is a hold, the other a setup, and confusing them puts the counter on the wrong side of the edge.

Repeated START — lift SDA, release SCL, wait for it, then fall

10 cycles
Ten intervals. At the start SCL is low and SDA is low, mid-transfer. At interval one SDA is released while SCL is still low, which is ordinary preparation and not framing. At interval two the framer releases SCL but the line stays low because a target is stretching, through interval three. At interval four the line finally reads high and the setup interval begins. At interval seven SDA falls while SCL is high, which is the repeated start, and at interval nine SCL is pulled low again to continue the transfer.stretched — waitingstretched — waitingtSU;STAtSU;STASDA lifted while SCL lowSDA lifted while SCL lowSCL released — still reads lowSCL released — still readslownow high: tSU;STA begins herenow high: tSU;STA beginshereSr — SDA falls, SCL highSr — SDA falls, SCL highscl_drive_lowscl_bussda_busstretch_waitt0t1t2t3t4t5t6t7t8t9
Figure 2 — the repeated START, which is the sequence with the most steps and the only one that can be defeated by a stretching target. SDA must be lifted before it can fall again; SCL must be observed high before the setup interval means anything. Conceptual figure.

Note intervals 2 and 3: scl_drive_low is 0 and scl_bus is still 0. That is the entire content of Chapter 17.1 §4's intent-versus-observation distinction, and it is the reason stretch_wait exists as an output rather than as an internal state.

5. The Framer, in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_framer.sv — the framing sequencer: S, Sr, P and tBUF
   // -----------------------------------------------------------------------------
   // i2c_framer.sv
   // START, repeated START and STOP generation: the edges the bit engine cannot produce.
   //
   // WHY THIS IS A SEPARATE BLOCK, and not a mode of the bit engine. UM10204 §3.1.1:
   //
   //   START: "A HIGH to LOW transition on the SDA line while SCL is HIGH"
   //   STOP:  "A LOW to HIGH transition on the SDA line while SCL is HIGH"
   //
   // and §3.1.2, the data rule: "The data on the SDA line must be stable during the HIGH
   // period of the clock. ... The HIGH or LOW state of the data line can only change when
   // the clock signal on the SCL line is LOW."
   //
   // Those are contradictory, deliberately. Framing is recognisable precisely BECAUSE it
   // is the one thing a data bit may never do. The bit engine's whole job is to guarantee
   // SDA is stable while SCL is high; framing requires the opposite. So the two cannot be
   // the same block, and the separation is structural rather than stylistic.
   //
   // THE FOUR SEQUENCES, and the Table 10 parameter each one is built from:
   //
   //   START from idle      SDA falls while SCL high, then tHD;STA before SCL falls
   //   repeated START       SDA released, SCL released, tSU;STA, then SDA falls, tHD;STA
   //   STOP                 SDA low, SCL released, tSU;STO, then SDA rises
   //   bus free             tBUF after a STOP before the next START may be issued
   //
   // EVERY SCL RELEASE IN THOSE SEQUENCES WAITS FOR THE READBACK. A repeated START
   // releases SCL and must not measure tSU;STA until the line is actually high, because a
   // target may be stretching -- and tSU;STA is a setup time against the SDA falling edge,
   // which is meaningless if SCL has not risen. A framer that counted from its own release
   // would emit a START while SCL was still low, which is not a START at all: it is an
   // ordinary data-bit change, and every device on the bus would read it as one.
   // -----------------------------------------------------------------------------

   module i2c_framer #(
      parameter int N_HD_STA = 5,    // tHD;STA, system-clock cycles
      parameter int N_SU_STA = 6,    // tSU;STA
      parameter int N_SU_STO = 5,    // tSU;STO
      parameter int N_BUF    = 6,    // tBUF, bus free between STOP and START
      parameter int N_SU_DAT = 3,    // tSU;DAT, used before releasing SCL
      parameter int CNT_W    = 16
   ) (
      input  logic            clk,
      input  logic            rst_n,

      // Commands. One-cycle pulses; only one may be asserted at a time.
      input  logic            do_start,      // START from an idle bus
      input  logic            do_restart,    // repeated START from mid-transfer
      input  logic            do_stop,

      // The bus, read back.
      input  logic            scl_in,
      input  logic            sda_in,

      // THE HANDOVER. Between its own sequences the framer holds SCL low whenever a
      // transfer is open, because somebody has to: a released SCL with a transfer in
      // progress is a clock pulse nobody meant to send. `scl_yield` is the controller
      // saying "the SCL generator has it now".
      //
      // The handover has to OVERLAP. If the framer released SCL in one cycle and the
      // generator began driving it in the next, the line would rise for a cycle in
      // between -- a spurious clock pulse that every device on the bus would count as a
      // bit. So `scl_yield` and the generator's `enable` are asserted in the SAME cycle,
      // and the two blocks then drive SCL low simultaneously for that cycle.
      input  logic            scl_yield,

      // What the framer wants. These go to the SDA owner arbiter (framer has priority)
      // and to the SCL merge in Chapter 17.12.
      output logic            sda_req,       // framer wants SDA
      output logic            sda_bit,       // the bit it wants to transmit
      output logic            scl_drive_low, // framer's SCL contribution

      output logic            busy,
      output logic            done,          // one cycle when a sequence completes
      output logic            bus_free,      // tBUF has elapsed since the last STOP
      output logic            started,       // a START has been issued and no STOP since
      output logic            stretch_wait,  // waiting for SCL to actually rise
      output logic [CNT_W-1:0] starts,
      output logic [CNT_W-1:0] restarts,
      output logic [CNT_W-1:0] stops,
      output logic [3:0]       state
   );

      localparam [3:0] S_IDLE    = 4'd0,  // nothing in progress
                       // START from idle: SDA falls while SCL is high, then hold.
                       S_STA_FALL = 4'd1, // pull SDA low -- THIS is the START edge
                       S_STA_HOLD = 4'd2, // tHD;STA, then pull SCL low
                       S_STA_SCL  = 4'd3,
                       // repeated START: release SDA, release SCL, wait, then fall.
                       S_RS_SDA   = 4'd4, // release SDA while SCL is low
                       S_RS_SETUP = 4'd5, // tSU;DAT before releasing SCL
                       S_RS_SCLHI = 4'd6, // release SCL and WAIT for the readback
                       S_RS_SU    = 4'd7, // tSU;STA with both lines high
                       S_RS_FALL  = 4'd8, // pull SDA low -- the repeated START edge
                       S_RS_HOLD  = 4'd9, // tHD;STA, then pull SCL low
                       // STOP: SDA low, release SCL, wait, then release SDA.
                       S_STO_SDA  = 4'd10, // ensure SDA is low while SCL is low
                       S_STO_SCLHI= 4'd11, // release SCL and WAIT for the readback
                       S_STO_SU   = 4'd12, // tSU;STO
                       S_STO_RISE = 4'd13, // release SDA -- THIS is the STOP edge
                       S_BUF      = 4'd14; // tBUF

      logic [CNT_W-1:0] cnt;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            state         <= S_IDLE;
            cnt           <= {CNT_W{1'b0}};
            sda_req       <= 1'b0;
            sda_bit       <= 1'b1;
            scl_drive_low <= 1'b0;
            busy          <= 1'b0;
            done          <= 1'b0;
            bus_free      <= 1'b1;     // a reset master must assume nothing is in flight
            started       <= 1'b0;
            stretch_wait  <= 1'b0;
            starts        <= {CNT_W{1'b0}};
            restarts      <= {CNT_W{1'b0}};
            stops         <= {CNT_W{1'b0}};
         end else begin
            done         <= 1'b0;
            stretch_wait <= 1'b0;

            case (state)

               S_IDLE: begin
                  busy <= 1'b0;
                  // Hold SCL low while a transfer is open, unless yielding to the clock
                  // generator. With no transfer open, release it.
                  scl_drive_low <= started && !scl_yield;
                  if (do_start && bus_free) begin
                     // Take SDA and pull it low while SCL is high. That transition IS the
                     // START condition; nothing else has to happen for the bus to have
                     // seen one, which is why the ordering here is the whole sequence.
                     sda_req <= 1'b1;
                     sda_bit <= 1'b0;
                     busy    <= 1'b1;
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_STA_HOLD;
                  end else if (do_restart) begin
                     // Mid-transfer, SCL is low. Release SDA first so it can be pulled
                     // low again later -- a line that is already low cannot produce a
                     // HIGH-to-LOW transition, so a repeated START from a zero data bit
                     // needs SDA lifted before anything else.
                     sda_req <= 1'b1;
                     sda_bit <= 1'b1;
                     busy    <= 1'b1;
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_RS_SETUP;
                  end else if (do_stop) begin
                     // SDA must be LOW before SCL is released, so that releasing SDA
                     // afterwards is a LOW-to-HIGH transition while SCL is high.
                     sda_req <= 1'b1;
                     sda_bit <= 1'b0;
                     busy    <= 1'b1;
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_STO_SDA;
                  end
               end

               // ---- START from an idle bus -------------------------------------
               S_STA_HOLD: begin
                  // tHD;STA: hold SDA low with SCL still high.
                  if (cnt + 1 >= N_HD_STA) begin
                     scl_drive_low <= 1'b1;      // now pull SCL low: the transfer begins
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_STA_SCL;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_STA_SCL: begin
                  // The START is complete and SCL is low. Hand SDA to the datapath.
                  sda_req  <= 1'b0;
                  started  <= 1'b1;
                  bus_free <= 1'b0;
                  starts   <= starts + 1'b1;
                  done     <= 1'b1;
                  busy     <= 1'b0;
                  state    <= S_IDLE;
               end

               // ---- repeated START ---------------------------------------------
               S_RS_SETUP: begin
                  // tSU;DAT with SDA released and SCL still low, so SDA is set up before
                  // the clock rises. Footnote [5] of Table 10 is explicit that this
                  // set-up applies even when the clock is being stretched.
                  if (cnt + 1 >= N_SU_DAT) begin
                     scl_drive_low <= 1'b0;      // release SCL
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_RS_SCLHI;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_RS_SCLHI: begin
                  // Released. WAIT for the line. A target stretching here is legal and
                  // common -- §3.1.6's byte-level handshake happens exactly at this
                  // boundary -- and tSU;STA cannot begin until SCL is really high.
                  //
                  // Note the counter's entry value. THIS cycle already has both lines
                  // high, so it is the first cycle of tSU;STA and is counted as such.
                  // Entering the setup state with a zero would deliver N_SU_STA + 1
                  // cycles of setup, which is legal -- tSU;STA has only a minimum -- but
                  // would mean the parameter did not mean the Table 10 number it is named
                  // after, and every period derived from it would be a cycle out.
                  if (scl_in) begin
                     cnt   <= {{(CNT_W-1){1'b0}}, 1'b1};
                     state <= S_RS_SU;
                  end else begin
                     stretch_wait <= 1'b1;
                  end
               end

               S_RS_SU: begin
                  // tSU;STA with both lines high.
                  if (cnt + 1 >= N_SU_STA) begin
                     sda_bit <= 1'b0;            // SDA falls: the repeated START edge
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_RS_HOLD;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_RS_HOLD: begin
                  if (cnt + 1 >= N_HD_STA) begin
                     scl_drive_low <= 1'b1;
                     sda_req       <= 1'b0;
                     started       <= 1'b1;
                     bus_free      <= 1'b0;
                     restarts      <= restarts + 1'b1;
                     done          <= 1'b1;
                     busy          <= 1'b0;
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_IDLE;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               // ---- STOP --------------------------------------------------------
               S_STO_SDA: begin
                  // SDA low, SCL low. Give SDA a moment to settle before lifting SCL,
                  // for the same tSU;DAT reason as the repeated START.
                  if (cnt + 1 >= N_SU_DAT) begin
                     scl_drive_low <= 1'b0;
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_STO_SCLHI;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_STO_SCLHI: begin
                  // Same counting rule as the repeated START above: this cycle already
                  // has SCL high with SDA low, so it is the first cycle of tSU;STO.
                  if (scl_in) begin
                     cnt   <= {{(CNT_W-1){1'b0}}, 1'b1};
                     state <= S_STO_SU;
                  end else begin
                     stretch_wait <= 1'b1;
                  end
               end

               S_STO_SU: begin
                  // tSU;STO with SCL high and SDA still low.
                  if (cnt + 1 >= N_SU_STO) begin
                     sda_bit <= 1'b1;            // release SDA: the STOP edge
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_STO_RISE;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_STO_RISE: begin
                  // The STOP is on the wire. Hold SDA released and start counting tBUF.
                  sda_req  <= 1'b0;
                  started  <= 1'b0;
                  bus_free <= 1'b0;              // not free until tBUF has elapsed
                  stops    <= stops + 1'b1;
                  // Both lines are released in THIS cycle, so it is the first cycle of
                  // tBUF. The same counting rule as the two setup states above, and the
                  // third place in this block where it applies: the cycle that makes a
                  // transition is already the first cycle of the interval that follows
                  // it, and starting the count at zero delivers one cycle too many.
                  cnt      <= {{(CNT_W-1){1'b0}}, 1'b1};
                  state    <= S_BUF;
               end

               S_BUF: begin
                  // tBUF. A master that issues a START before this has elapsed produces a
                  // START the receiving devices may not recognise, and on a multi-master
                  // bus it is also how two masters collide without arbitrating.
                  if (cnt + 1 >= N_BUF) begin
                     bus_free <= 1'b1;
                     done     <= 1'b1;
                     busy     <= 1'b0;
                     state    <= S_IDLE;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               default: state <= S_IDLE;
            endcase
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_framer.v — the same design in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_framer.sv
   // START, repeated START and STOP generation: the edges the bit engine cannot produce.
   //
   // WHY THIS IS A SEPARATE BLOCK, and not a mode of the bit engine. UM10204 §3.1.1:
   //
   //   START: "A HIGH to LOW transition on the SDA line while SCL is HIGH"
   //   STOP:  "A LOW to HIGH transition on the SDA line while SCL is HIGH"
   //
   // and §3.1.2, the data rule: "The data on the SDA line must be stable during the HIGH
   // period of the clock. ... The HIGH or LOW state of the data line can only change when
   // the clock signal on the SCL line is LOW."
   //
   // Those are contradictory, deliberately. Framing is recognisable precisely BECAUSE it
   // is the one thing a data bit may never do. The bit engine's whole job is to guarantee
   // SDA is stable while SCL is high; framing requires the opposite. So the two cannot be
   // the same block, and the separation is structural rather than stylistic.
   //
   // THE FOUR SEQUENCES, and the Table 10 parameter each one is built from:
   //
   //   START from idle      SDA falls while SCL high, then tHD;STA before SCL falls
   //   repeated START       SDA released, SCL released, tSU;STA, then SDA falls, tHD;STA
   //   STOP                 SDA low, SCL released, tSU;STO, then SDA rises
   //   bus free             tBUF after a STOP before the next START may be issued
   //
   // EVERY SCL RELEASE IN THOSE SEQUENCES WAITS FOR THE READBACK. A repeated START
   // releases SCL and must not measure tSU;STA until the line is actually high, because a
   // target may be stretching -- and tSU;STA is a setup time against the SDA falling edge,
   // which is meaningless if SCL has not risen. A framer that counted from its own release
   // would emit a START while SCL was still low, which is not a START at all: it is an
   // ordinary data-bit change, and every device on the bus would read it as one.
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_framer #(
      parameter N_HD_STA = 5,    // tHD;STA, system-clock cycles
      parameter N_SU_STA = 6,    // tSU;STA
      parameter N_SU_STO = 5,    // tSU;STO
      parameter N_BUF    = 6,    // tBUF, bus free between STOP and START
      parameter N_SU_DAT = 3,    // tSU;DAT, used before releasing SCL
      parameter CNT_W    = 16
   ) (
      input  wire            clk,
      input  wire            rst_n,

      // Commands. One-cycle pulses; only one may be asserted at a time.
      input  wire            do_start,      // START from an idle bus
      input  wire            do_restart,    // repeated START from mid-transfer
      input  wire            do_stop,

      // The bus, read back.
      input  wire            scl_in,
      input  wire            sda_in,

      // THE HANDOVER. Between its own sequences the framer holds SCL low whenever a
      // transfer is open, because somebody has to: a released SCL with a transfer in
      // progress is a clock pulse nobody meant to send. `scl_yield` is the controller
      // saying "the SCL generator has it now".
      //
      // The handover has to OVERLAP. If the framer released SCL in one cycle and the
      // generator began driving it in the next, the line would rise for a cycle in
      // between -- a spurious clock pulse that every device on the bus would count as a
      // bit. So `scl_yield` and the generator's `enable` are asserted in the SAME cycle,
      // and the two blocks then drive SCL low simultaneously for that cycle.
      input  wire            scl_yield,

      // What the framer wants. These go to the SDA owner arbiter (framer has priority)
      // and to the SCL merge in Chapter 17.12.
      output reg             sda_req,       // framer wants SDA
      output reg             sda_bit,       // the bit it wants to transmit
      output reg             scl_drive_low, // framer's SCL contribution

      output reg             busy,
      output reg             done,          // one cycle when a sequence completes
      output reg             bus_free,      // tBUF has elapsed since the last STOP
      output reg             started,       // a START has been issued and no STOP since
      output reg             stretch_wait,  // waiting for SCL to actually rise
      output reg [CNT_W-1:0] starts,
      output reg [CNT_W-1:0] restarts,
      output reg [CNT_W-1:0] stops,
      output reg [3:0]       state
   );

      localparam [3:0] S_IDLE    = 4'd0,  // nothing in progress
                       // START from idle: SDA falls while SCL is high, then hold.
                       S_STA_FALL = 4'd1, // pull SDA low -- THIS is the START edge
                       S_STA_HOLD = 4'd2, // tHD;STA, then pull SCL low
                       S_STA_SCL  = 4'd3,
                       // repeated START: release SDA, release SCL, wait, then fall.
                       S_RS_SDA   = 4'd4, // release SDA while SCL is low
                       S_RS_SETUP = 4'd5, // tSU;DAT before releasing SCL
                       S_RS_SCLHI = 4'd6, // release SCL and WAIT for the readback
                       S_RS_SU    = 4'd7, // tSU;STA with both lines high
                       S_RS_FALL  = 4'd8, // pull SDA low -- the repeated START edge
                       S_RS_HOLD  = 4'd9, // tHD;STA, then pull SCL low
                       // STOP: SDA low, release SCL, wait, then release SDA.
                       S_STO_SDA  = 4'd10, // ensure SDA is low while SCL is low
                       S_STO_SCLHI= 4'd11, // release SCL and WAIT for the readback
                       S_STO_SU   = 4'd12, // tSU;STO
                       S_STO_RISE = 4'd13, // release SDA -- THIS is the STOP edge
                       S_BUF      = 4'd14; // tBUF

      reg [CNT_W-1:0] cnt;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            state         <= S_IDLE;
            cnt           <= {CNT_W{1'b0}};
            sda_req       <= 1'b0;
            sda_bit       <= 1'b1;
            scl_drive_low <= 1'b0;
            busy          <= 1'b0;
            done          <= 1'b0;
            bus_free      <= 1'b1;     // a reset master must assume nothing is in flight
            started       <= 1'b0;
            stretch_wait  <= 1'b0;
            starts        <= {CNT_W{1'b0}};
            restarts      <= {CNT_W{1'b0}};
            stops         <= {CNT_W{1'b0}};
         end else begin
            done         <= 1'b0;
            stretch_wait <= 1'b0;

            case (state)

               S_IDLE: begin
                  busy <= 1'b0;
                  // Hold SCL low while a transfer is open, unless yielding to the clock
                  // generator. With no transfer open, release it.
                  scl_drive_low <= started && !scl_yield;
                  if (do_start && bus_free) begin
                     // Take SDA and pull it low while SCL is high. That transition IS the
                     // START condition; nothing else has to happen for the bus to have
                     // seen one, which is why the ordering here is the whole sequence.
                     sda_req <= 1'b1;
                     sda_bit <= 1'b0;
                     busy    <= 1'b1;
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_STA_HOLD;
                  end else if (do_restart) begin
                     // Mid-transfer, SCL is low. Release SDA first so it can be pulled
                     // low again later -- a line that is already low cannot produce a
                     // HIGH-to-LOW transition, so a repeated START from a zero data bit
                     // needs SDA lifted before anything else.
                     sda_req <= 1'b1;
                     sda_bit <= 1'b1;
                     busy    <= 1'b1;
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_RS_SETUP;
                  end else if (do_stop) begin
                     // SDA must be LOW before SCL is released, so that releasing SDA
                     // afterwards is a LOW-to-HIGH transition while SCL is high.
                     sda_req <= 1'b1;
                     sda_bit <= 1'b0;
                     busy    <= 1'b1;
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_STO_SDA;
                  end
               end

               // ---- START from an idle bus -------------------------------------
               S_STA_HOLD: begin
                  // tHD;STA: hold SDA low with SCL still high.
                  if (cnt + 1 >= N_HD_STA) begin
                     scl_drive_low <= 1'b1;      // now pull SCL low: the transfer begins
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_STA_SCL;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_STA_SCL: begin
                  // The START is complete and SCL is low. Hand SDA to the datapath.
                  sda_req  <= 1'b0;
                  started  <= 1'b1;
                  bus_free <= 1'b0;
                  starts   <= starts + 1'b1;
                  done     <= 1'b1;
                  busy     <= 1'b0;
                  state    <= S_IDLE;
               end

               // ---- repeated START ---------------------------------------------
               S_RS_SETUP: begin
                  // tSU;DAT with SDA released and SCL still low, so SDA is set up before
                  // the clock rises. Footnote [5] of Table 10 is explicit that this
                  // set-up applies even when the clock is being stretched.
                  if (cnt + 1 >= N_SU_DAT) begin
                     scl_drive_low <= 1'b0;      // release SCL
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_RS_SCLHI;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_RS_SCLHI: begin
                  // Released. WAIT for the line. A target stretching here is legal and
                  // common -- §3.1.6's byte-level handshake happens exactly at this
                  // boundary -- and tSU;STA cannot begin until SCL is really high.
                  //
                  // Note the counter's entry value. THIS cycle already has both lines
                  // high, so it is the first cycle of tSU;STA and is counted as such.
                  // Entering the setup state with a zero would deliver N_SU_STA + 1
                  // cycles of setup, which is legal -- tSU;STA has only a minimum -- but
                  // would mean the parameter did not mean the Table 10 number it is named
                  // after, and every period derived from it would be a cycle out.
                  if (scl_in) begin
                     cnt   <= {{(CNT_W-1){1'b0}}, 1'b1};
                     state <= S_RS_SU;
                  end else begin
                     stretch_wait <= 1'b1;
                  end
               end

               S_RS_SU: begin
                  // tSU;STA with both lines high.
                  if (cnt + 1 >= N_SU_STA) begin
                     sda_bit <= 1'b0;            // SDA falls: the repeated START edge
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_RS_HOLD;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_RS_HOLD: begin
                  if (cnt + 1 >= N_HD_STA) begin
                     scl_drive_low <= 1'b1;
                     sda_req       <= 1'b0;
                     started       <= 1'b1;
                     bus_free      <= 1'b0;
                     restarts      <= restarts + 1'b1;
                     done          <= 1'b1;
                     busy          <= 1'b0;
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_IDLE;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               // ---- STOP --------------------------------------------------------
               S_STO_SDA: begin
                  // SDA low, SCL low. Give SDA a moment to settle before lifting SCL,
                  // for the same tSU;DAT reason as the repeated START.
                  if (cnt + 1 >= N_SU_DAT) begin
                     scl_drive_low <= 1'b0;
                     cnt           <= {CNT_W{1'b0}};
                     state         <= S_STO_SCLHI;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_STO_SCLHI: begin
                  // Same counting rule as the repeated START above: this cycle already
                  // has SCL high with SDA low, so it is the first cycle of tSU;STO.
                  if (scl_in) begin
                     cnt   <= {{(CNT_W-1){1'b0}}, 1'b1};
                     state <= S_STO_SU;
                  end else begin
                     stretch_wait <= 1'b1;
                  end
               end

               S_STO_SU: begin
                  // tSU;STO with SCL high and SDA still low.
                  if (cnt + 1 >= N_SU_STO) begin
                     sda_bit <= 1'b1;            // release SDA: the STOP edge
                     cnt     <= {CNT_W{1'b0}};
                     state   <= S_STO_RISE;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               S_STO_RISE: begin
                  // The STOP is on the wire. Hold SDA released and start counting tBUF.
                  sda_req  <= 1'b0;
                  started  <= 1'b0;
                  bus_free <= 1'b0;              // not free until tBUF has elapsed
                  stops    <= stops + 1'b1;
                  // Both lines are released in THIS cycle, so it is the first cycle of
                  // tBUF. The same counting rule as the two setup states above, and the
                  // third place in this block where it applies: the cycle that makes a
                  // transition is already the first cycle of the interval that follows
                  // it, and starting the count at zero delivers one cycle too many.
                  cnt      <= {{(CNT_W-1){1'b0}}, 1'b1};
                  state    <= S_BUF;
               end

               S_BUF: begin
                  // tBUF. A master that issues a START before this has elapsed produces a
                  // START the receiving devices may not recognise, and on a multi-master
                  // bus it is also how two masters collide without arbitrating.
                  if (cnt + 1 >= N_BUF) begin
                     bus_free <= 1'b1;
                     done     <= 1'b1;
                     busy     <= 1'b0;
                     state    <= S_IDLE;
                  end else begin
                     cnt <= cnt + 1'b1;
                  end
               end

               default: state <= S_IDLE;
            endcase
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_framer.vhd — the same design in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_framer.vhd
   -- START, repeated START and STOP generation: the edges the bit engine cannot produce.
   -- Behavioural twin of i2c_framer.sv / .v.
   --
   -- WHY THIS IS A SEPARATE BLOCK. §3.1.1 defines START as "a HIGH to LOW transition on the
   -- SDA line while SCL is HIGH" and STOP as the reverse. §3.1.2, the data rule, says "The
   -- HIGH or LOW state of the data line can only change when the clock signal on the SCL line
   -- is LOW."
   --
   -- Those are contradictory, deliberately: framing is recognisable precisely BECAUSE it is
   -- the one thing a data bit may never do. The bit engine's whole job is to guarantee SDA is
   -- stable while SCL is high; framing requires the opposite. So the two cannot be the same
   -- block, and the separation is structural rather than stylistic.
   --
   -- EVERY SCL RELEASE IN THESE SEQUENCES WAITS FOR THE READBACK. A repeated START releases
   -- SCL and must not measure tSU;STA until the line is actually high, because tSU;STA is a
   -- setup time against the SDA falling edge and is meaningless if SCL has not risen. A framer
   -- that counted from its own release would emit a START while SCL was still low -- which is
   -- not a START at all but an ordinary data-bit change, and every device would read it as one.
   --
   -- AND THE COUNTING RULE, which cost three off-by-one errors before it was stated: THE CYCLE
   -- THAT MAKES A TRANSITION IS ALREADY THE FIRST CYCLE OF THE INTERVAL THAT FOLLOWS IT. It
   -- applies to tSU;STA, to tSU;STO and to tBUF, and starting any of those counts at zero
   -- delivers one cycle too many.
   -- ---------------------------------------------------------------------------

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

   entity i2c_framer is
      generic (
         N_HD_STA : integer := 5;    -- tHD;STA
         N_SU_STA : integer := 6;    -- tSU;STA
         N_SU_STO : integer := 5;    -- tSU;STO
         N_BUF    : integer := 6;    -- tBUF
         N_SU_DAT : integer := 3;    -- tSU;DAT
         CNT_W    : integer := 16
      );
      port (
         clk   : in std_logic;
         rst_n : in std_logic;

         do_start   : in std_logic;
         do_restart : in std_logic;
         do_stop    : in std_logic;

         scl_in : in std_logic;
         sda_in : in std_logic;

         -- THE HANDOVER. Between its own sequences the framer holds SCL low whenever a
         -- transfer is open, because somebody has to: a released SCL with a transfer in
         -- progress is a clock pulse nobody meant to send. `scl_yield` is the controller
         -- saying "the SCL generator has it now", and it is asserted in the SAME cycle as the
         -- generator's enable so the two drive SCL low together for that cycle and the line
         -- never rises in between.
         scl_yield : in std_logic;

         sda_req       : out std_logic;
         sda_bit       : out std_logic;
         scl_drive_low : out std_logic;

         busy         : out std_logic;
         done         : out std_logic;
         bus_free     : out std_logic;
         started      : out std_logic;
         stretch_wait : out std_logic;
         starts       : out unsigned(CNT_W-1 downto 0);
         restarts     : out unsigned(CNT_W-1 downto 0);
         stops        : out unsigned(CNT_W-1 downto 0);
         state        : out unsigned(3 downto 0)
      );
   end entity i2c_framer;

   architecture rtl of i2c_framer is

      constant S_IDLE     : integer := 0;
      constant S_STA_HOLD : integer := 2;   -- tHD;STA, then pull SCL low
      constant S_STA_SCL  : integer := 3;
      constant S_RS_SETUP : integer := 5;   -- tSU;DAT before releasing SCL
      constant S_RS_SCLHI : integer := 6;   -- release SCL and WAIT for the readback
      constant S_RS_SU    : integer := 7;   -- tSU;STA with both lines high
      constant S_RS_HOLD  : integer := 9;   -- tHD;STA, then pull SCL low
      constant S_STO_SDA  : integer := 10;  -- SDA low while SCL is low
      constant S_STO_SCLHI: integer := 11;  -- release SCL and WAIT for the readback
      constant S_STO_SU   : integer := 12;  -- tSU;STO
      constant S_STO_RISE : integer := 13;  -- release SDA: THIS is the STOP edge
      constant S_BUF      : integer := 14;  -- tBUF

      signal st  : integer range 0 to 14 := S_IDLE;
      signal cnt : integer range 0 to 65535 := 0;

      signal sreq, sbit, sclow, bsy, dn, bfree, strt, swait : std_logic;
      signal nsta, nrs, nsto : unsigned(CNT_W-1 downto 0);

   begin

      sda_req       <= sreq;
      sda_bit       <= sbit;
      scl_drive_low <= sclow;
      busy          <= bsy;
      done          <= dn;
      bus_free      <= bfree;
      started       <= strt;
      stretch_wait  <= swait;
      starts        <= nsta;
      restarts      <= nrs;
      stops         <= nsto;
      state         <= to_unsigned(st, 4);

      process (clk, rst_n)
      begin
         if rst_n = '0' then
            st    <= S_IDLE;
            cnt   <= 0;
            sreq  <= '0';
            sbit  <= '1';
            sclow <= '0';
            bsy   <= '0';
            dn    <= '0';
            bfree <= '1';          -- a reset master must assume nothing is in flight
            strt  <= '0';
            swait <= '0';
            nsta  <= (others => '0');
            nrs   <= (others => '0');
            nsto  <= (others => '0');
         elsif rising_edge(clk) then
            dn    <= '0';
            swait <= '0';

            case st is

               when S_IDLE =>
                  bsy <= '0';
                  -- Hold SCL low while a transfer is open, unless yielding to the generator.
                  if strt = '1' and scl_yield = '0' then sclow <= '1';
                  else                                   sclow <= '0';
                  end if;
                  if do_start = '1' and bfree = '1' then
                     -- Take SDA and pull it low while SCL is high. That transition IS the
                     -- START condition; nothing else has to happen for the bus to have seen
                     -- one, which is why the ordering here is the whole sequence.
                     sreq <= '1';
                     sbit <= '0';
                     bsy  <= '1';
                     cnt  <= 0;
                     st   <= S_STA_HOLD;
                  elsif do_restart = '1' then
                     -- Mid-transfer, SCL is low. Release SDA first: a line that is already low
                     -- cannot produce a HIGH-to-LOW transition, so a repeated START after a
                     -- zero data bit needs SDA lifted before anything else.
                     sreq <= '1';
                     sbit <= '1';
                     bsy  <= '1';
                     cnt  <= 0;
                     st   <= S_RS_SETUP;
                  elsif do_stop = '1' then
                     -- SDA must be LOW before SCL is released, so that releasing SDA
                     -- afterwards is a LOW-to-HIGH transition while SCL is high.
                     sreq <= '1';
                     sbit <= '0';
                     bsy  <= '1';
                     cnt  <= 0;
                     st   <= S_STO_SDA;
                  end if;

               -- ---- START from an idle bus -------------------------------------
               when S_STA_HOLD =>
                  if cnt + 1 >= N_HD_STA then
                     sclow <= '1';          -- now pull SCL low: the transfer begins
                     cnt   <= 0;
                     st    <= S_STA_SCL;
                  else
                     cnt <= cnt + 1;
                  end if;

               when S_STA_SCL =>
                  sreq  <= '0';             -- hand SDA to the datapath
                  strt  <= '1';
                  bfree <= '0';
                  nsta  <= nsta + 1;
                  dn    <= '1';
                  bsy   <= '0';
                  st    <= S_IDLE;

               -- ---- repeated START ---------------------------------------------
               when S_RS_SETUP =>
                  -- tSU;DAT with SDA released and SCL still low. Footnote [5] of Table 10 is
                  -- explicit that this set-up applies even when the clock is being stretched.
                  if cnt + 1 >= N_SU_DAT then
                     sclow <= '0';          -- release SCL
                     cnt   <= 0;
                     st    <= S_RS_SCLHI;
                  else
                     cnt <= cnt + 1;
                  end if;

               when S_RS_SCLHI =>
                  -- Released. WAIT for the line. A target stretching here is legal and common
                  -- -- §3.1.6's byte-level handshake happens exactly at this boundary.
                  --
                  -- Note the counter's entry value: THIS cycle already has both lines high, so
                  -- it is the first cycle of tSU;STA. Entering with zero would deliver
                  -- N_SU_STA + 1 cycles, and the parameter would not mean the Table 10 number
                  -- it is named after.
                  if scl_in = '1' then
                     cnt <= 1;
                     st  <= S_RS_SU;
                  else
                     swait <= '1';
                  end if;

               when S_RS_SU =>
                  if cnt + 1 >= N_SU_STA then
                     sbit <= '0';           -- SDA falls: the repeated START edge
                     cnt  <= 0;
                     st   <= S_RS_HOLD;
                  else
                     cnt <= cnt + 1;
                  end if;

               when S_RS_HOLD =>
                  if cnt + 1 >= N_HD_STA then
                     sclow <= '1';
                     sreq  <= '0';
                     strt  <= '1';
                     bfree <= '0';
                     nrs   <= nrs + 1;
                     dn    <= '1';
                     bsy   <= '0';
                     cnt   <= 0;
                     st    <= S_IDLE;
                  else
                     cnt <= cnt + 1;
                  end if;

               -- ---- STOP --------------------------------------------------------
               when S_STO_SDA =>
                  if cnt + 1 >= N_SU_DAT then
                     sclow <= '0';
                     cnt   <= 0;
                     st    <= S_STO_SCLHI;
                  else
                     cnt <= cnt + 1;
                  end if;

               when S_STO_SCLHI =>
                  -- Same counting rule as the repeated START: this cycle already has SCL high
                  -- with SDA low, so it is the first cycle of tSU;STO.
                  if scl_in = '1' then
                     cnt <= 1;
                     st  <= S_STO_SU;
                  else
                     swait <= '1';
                  end if;

               when S_STO_SU =>
                  if cnt + 1 >= N_SU_STO then
                     sbit <= '1';           -- release SDA: the STOP edge
                     cnt  <= 0;
                     st   <= S_STO_RISE;
                  else
                     cnt <= cnt + 1;
                  end if;

               when S_STO_RISE =>
                  sreq  <= '0';
                  strt  <= '0';
                  bfree <= '0';             -- not free until tBUF has elapsed
                  nsto  <= nsto + 1;
                  -- Both lines are released in THIS cycle, so it is the first cycle of tBUF --
                  -- the third place the counting rule applies.
                  cnt   <= 1;
                  st    <= S_BUF;

               when S_BUF =>
                  -- A master that issues a START before tBUF has elapsed produces a START the
                  -- receiving devices may not recognise, and on a multi-master bus it is also
                  -- how two masters collide without arbitrating.
                  if cnt + 1 >= N_BUF then
                     bfree <= '1';
                     dn    <= '1';
                     bsy   <= '0';
                     st    <= S_IDLE;
                  else
                     cnt <= cnt + 1;
                  end if;

               when others =>
                  st <= S_IDLE;

            end case;
         end if;
      end process;

   end architecture rtl;

5a. The testbenches

Twelve tests, and the bench instantiates a protocol monitor that sees only the two wires. That is deliberate: a framer that sets its internal flags correctly while producing nothing on the bus must fail, and only a wire-level observer can establish that.

#TestProperty
T1reset releases both lines, bus assumed freea reset master must assume nothing is in flight
T2a START — the monitor reports exactly oneflags are not evidence; the wire is
T3tHD;STA — the hold precedes SCL fallingmeasured, not asserted
T4a STOP — SDA rises while SCL is high, tSU;STO precedes it
T5tBUF — the bus is not free the instant the STOP happens
T6a START before tBUF is refused, not queued, not rushedplus the counter cross-check below
T7a repeated START from mid-transfer — SDA lifted first
T8the stretch, on a repeated STARTthe framer waits for the real rise
T9the stretch, on a STOPsame requirement, same mechanism
T10the framer produces no other edgesover a full S, Sr and P sequence
T11counters distinguish an initial START from a repeated one
T12idle means silentno command, nothing driven

Every wait is bounded — wait_done(200), wait_state(…, 400) — so a framer that never completes a sequence reports a failure instead of hanging the regression.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_framer_tb.sv — the self-checking testbench
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_framer_tb.sv
   // Independent oracle for i2c_framer.
   //
   // The framer drives a real wired-AND bus, and the checks are made by i2c_proto_mon --
   // which sees only the two wires and applies §3.1.1 as written. So a framer that reaches
   // the right states by the wrong edges fails here, which is the only interesting way for
   // a framer to be wrong.
   //
   // The bench is the second device on the bus and can stretch SCL, so the two sequences
   // that release SCL mid-way -- the repeated START and the STOP -- are tested against a
   // target that does not let go immediately.
   // -----------------------------------------------------------------------------
   module i2c_framer_tb;

      localparam integer NHD  = 5;   // tHD;STA
      localparam integer NSUA = 6;   // tSU;STA
      localparam integer NSUO = 5;   // tSU;STO
      localparam integer NBUF = 6;   // tBUF
      localparam integer NSUD = 3;   // tSU;DAT

      localparam [3:0] S_IDLE = 4'd0, S_RS_SCLHI = 4'd6, S_STO_SCLHI = 4'd11;

      logic clk = 1'b0, rst_n = 1'b0;
      logic do_start = 1'b0, do_restart = 1'b0, do_stop = 1'b0;

      // the bench as a second device
      logic tgt_scl_low = 1'b0, tgt_sda_low = 1'b0;

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

      // The framer owns SDA alone in this bench, so the inversion is applied here the way
      // i2c_sda_ctrl applies it: a transmitted one releases, a zero pulls down.
      wire f_sda_low = f_sda_req ? ~f_sda_bit : 1'b0;

      logic scl, sda;
      logic [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
      logic [7:0] scl_h, sda_h;

      i2c_line_model #(.N_DEV(2)) bus (
         .scl_drive_low({tgt_scl_low, f_scl_low}),
         .sda_drive_low({tgt_sda_low, f_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_framer #(.N_HD_STA(NHD), .N_SU_STA(NSUA), .N_SU_STO(NSUO),
                   .N_BUF(NBUF), .N_SU_DAT(NSUD), .CNT_W(16)) dut (
         .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(1'b0),
         .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_stretch),
         .starts(n_sta), .restarts(n_rs), .stops(n_sto), .state(fstate));

      // The oracle: it sees only the wires.
      logic m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv, m_intr, m_midbyte;
      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_midbyte), .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, j;

      // Measure the hold between the START edge on the wire and SCL going low.
      integer hd_run, meas_hd;  reg arm_hd;
      // Measure tSU;STO: how long SCL was high with SDA low before the STOP edge.
      integer su_run, meas_su;
      // Measure tBUF: cycles from the STOP edge until bus_free.
      integer buf_run, meas_buf; reg arm_buf;

      // The bench detects the framing edges ITSELF, from the lines, rather than using the
      // monitor's outputs. The monitor is registered, so its start_seen arrives a cycle
      // after the edge and an arming cycle after that -- which would understate every
      // measured interval by two cycles. A measurement of a Table 10 parameter cannot
      // afford that, so the edge detection is local and combinational in intent.
      logic scl_l, sda_l;
      always @(negedge clk) begin
         if (rst_n) begin
            // tHD;STA: from the START edge (SDA falls while SCL high) until SCL falls.
            if (sda_l && !sda && scl) begin arm_hd = 1'b1; hd_run = 1; end
            else if (arm_hd) begin
               if (scl) hd_run = hd_run + 1;
               else begin meas_hd = hd_run; arm_hd = 1'b0; end
            end
            // tSU;STO: cycles with SCL high and SDA low, ending at the STOP edge.
            if (scl && !sda) su_run = su_run + 1;
            else if (!scl)   su_run = 0;
            if (!sda_l && sda && scl) meas_su = su_run;
            // tBUF: from the STOP edge until bus_free is asserted.
            if (!sda_l && sda && scl) begin arm_buf = 1'b1; buf_run = 0; end
            else if (arm_buf) begin
               buf_run = buf_run + 1;
               if (f_bus_free) begin meas_buf = buf_run; arm_buf = 1'b0; end
            end
            scl_l = scl; sda_l = sda;
         end
      end

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

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; do_start = 1'b0; do_restart = 1'b0; do_stop = 1'b0;
            tgt_scl_low = 1'b0; tgt_sda_low = 1'b0;
            hd_run = 0; meas_hd = 0; arm_hd = 1'b0;
            su_run = 0; meas_su = 0;
            buf_run = 0; meas_buf = 0; arm_buf = 1'b0;
            scl_l = 1'b1; sda_l = 1'b1;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task pulse_start;   begin @(negedge clk); do_start   = 1'b1; @(posedge clk); @(negedge clk); do_start   = 1'b0; end endtask
      task pulse_restart; begin @(negedge clk); do_restart = 1'b1; @(posedge clk); @(negedge clk); do_restart = 1'b0; end endtask
      task pulse_stop;    begin @(negedge clk); do_stop    = 1'b1; @(posedge clk); @(negedge clk); do_stop    = 1'b0; end endtask

      task wait_done (input integer max_cycles);
         begin
            n = 0;
            while (!f_done && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_done: no completion after %0d cycles (state %0d)",
                        max_cycles, fstate);
               errors = errors + 1;
            end
         end
      endtask

      task wait_state (input [3:0] want, input integer max_cycles);
         begin
            n = 0;
            while (fstate != want && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_state: state %0d not reached (now %0d)", want, fstate);
               errors = errors + 1;
            end
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d expected %0d", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      task ck_bit (input [200*8:1] what, input g, input e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0b expected %0b", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      initial begin
         $display("=== i2c_framer: the edges a data bit is forbidden to make ===");

         // ----------------------------------------------------------------
         // T1. Reset releases both lines, and the bus is assumed free.
         // ----------------------------------------------------------------
         do_reset;
         $display("T1  a reset framer releases both lines");
         ck_bit("T1 SCL released", f_scl_low, 1'b0);
         ck_bit("T1 SDA released", f_sda_low, 1'b0);
         ck_bit("T1 both lines high", scl & sda, 1'b1);
         ck_bit("T1 the bus is free", f_bus_free, 1'b1);
         ck_bit("T1 not in a transfer", m_intr, 1'b0);
         ck_int("T1 no framing seen", m_nsta + m_nsto, 0);

         // ----------------------------------------------------------------
         // T2. A START. The monitor -- which sees only the wires -- must report one, and
         //     the transfer must be open afterwards.
         // ----------------------------------------------------------------
         pulse_start;
         wait_done(200);
         $display("T2  the monitor sees a START on the wire, having seen only the wires");
         ck_int("T2 exactly one START", m_nsta, 1);
         ck_int("T2 no STOP", m_nsto, 0);
         ck_bit("T2 the transfer is open", m_intr, 1'b1);
         ck_bit("T2 the framer says it started", f_started, 1'b1);
         ck_bit("T2 SCL is now low, ready for the first bit", scl, 1'b0);
         ck_bit("T2 and SDA has been handed back", f_sda_req, 1'b0);

         // ----------------------------------------------------------------
         // T3. tHD;STA. The START edge is followed by the hold time BEFORE SCL falls.
         //     A framer that pulled SCL low immediately would produce an edge the
         //     receiving devices are not obliged to recognise.
         // ----------------------------------------------------------------
         $display("T3  tHD;STA elapses between the START edge and SCL falling");
         ck_int("T3 the hold is N_HD_STA cycles", meas_hd, NHD);

         // ----------------------------------------------------------------
         // T4. A STOP. SDA rises while SCL is high, and tSU;STO precedes it.
         // ----------------------------------------------------------------
         pulse_stop;
         wait_done(200);
         $display("T4  a STOP: SDA rises while SCL is high, after tSU;STO");
         ck_int("T4 exactly one STOP", m_nsto, 1);
         ck_bit("T4 the transfer is closed", m_intr, 1'b0);
         ck_bit("T4 the framer says so", f_started, 1'b0);
         ck_int("T4 tSU;STO before the edge", meas_su, NSUO);
         ck_bit("T4 both lines released", f_scl_low | f_sda_low, 1'b0);

         // ----------------------------------------------------------------
         // T5. tBUF. The bus is not free the instant the STOP happens.
         // ----------------------------------------------------------------
         // One step before reading the measurement. SystemVerilog does not need it -- the
         // measuring block uses blocking assignments, which are visible within the same time
         // step -- but the VHDL twin does, because a VHDL signal read in the delta it is
         // assigned still holds its old value. The step is kept in all three benches so the
         // three files stay line-for-line comparable and finish at the same time, which is
         // the evidence that they are running the same test.
         step;
         $display("T5  tBUF elapses after the STOP before the bus is free again");
         ck_int("T5 the bus-free delay is N_BUF cycles", meas_buf, NBUF);
         ck_bit("T5 and then it is free", f_bus_free, 1'b1);

         // ----------------------------------------------------------------
         // T6. A START requested before tBUF has elapsed is REFUSED, not queued and not
         //     issued early. Issuing it early is how two masters collide without ever
         //     arbitrating, because neither saw the other's START.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start;  wait_done(200);
         // THE CROSS-CHECK. With a transfer open the bus is not free, and the framer is
         // back in S_IDLE able to sample a command -- the only reachable state in which
         // `bus_free` is 0 and a command can be accepted, so the only place the guard
         // can be tested. Asking after tBUF has elapsed tests nothing, because
         // `bus_free` is 1 by then.
         //
         // But refusing is not observable on the WIRE here: mid-transfer the framer is
         // holding SCL low, so a wrongly-accepted START pulls SDA low while SCL is low,
         // which is an ordinary data change and produces no START for the monitor to
         // see. The defect is therefore invisible to any bus-level check.
         //
         // What catches it is the invariant between the two counters: every START the
         // framer BELIEVES it issued must have been visible on the bus. A framer that
         // accepts a command it should have refused increments its own counter without
         // the monitor seeing anything, and the two diverge.
         ck_bit("T6 with a transfer open the bus is not free", f_bus_free, 1'b0);
         k = m_nsta;
         j = n_sta;
         @(negedge clk); do_start = 1'b1;
         n = 0;
         while (n < 30) begin step; n = n + 1; end
         @(negedge clk); do_start = 1'b0;
         ck_int("T6 a START with the bus not free is refused", m_nsta, k);
         ck_int("T6 and the framer did not count one either", n_sta, j);
         ck_int("T6 every START counted was seen on the wire", n_sta, m_nsta);
         pulse_stop;
         // Ask for a START while the STOP sequence is still running out tBUF.
         wait_state(S_IDLE, 400);
         k = m_nsta;
         @(negedge clk); do_start = 1'b1;
         // Hold the request through the whole bus-free period.
         n = 0;
         while (!f_bus_free && n < 200) begin step; n = n + 1; end
         $display("T6  a START asked for before tBUF has elapsed is refused, not rushed");
         ck_int("T6 no extra START was issued during tBUF", m_nsta, k);
         // and once the bus IS free, the held request is honoured
         wait_done(200);
         @(negedge clk); do_start = 1'b0;
         ck_int("T6 and it is honoured once the bus is free", m_nsta, k + 1);

         // ----------------------------------------------------------------
         // T7. A REPEATED START from mid-transfer. SDA must be lifted first: a line that
         //     is already low cannot make a HIGH-to-LOW transition, so a repeated START
         //     after a zero data bit needs SDA released before anything else.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start; wait_done(200);
         // Simulate a data bit having left SDA low: the bench holds it down, then lets go
         // when the framer takes over.
         k = m_nsta;
         pulse_restart;
         wait_done(400);
         $display("T7  a repeated START from mid-transfer, with SDA lifted first");
         ck_int("T7 a second START was seen on the wire", m_nsta, k + 1);
         ck_int("T7 and no STOP", m_nsto, 0);
         ck_bit("T7 the transfer is still open", m_intr, 1'b1);
         ck_int("T7 counted as a repeated START", n_rs, 1);
         ck_int("T7 and not as an initial one", n_sta, 1);
         ck_bit("T7 SCL is low afterwards", scl, 1'b0);

         // ----------------------------------------------------------------
         // T8. THE STRETCH, on a repeated START. The framer releases SCL and must wait
         //     for the LINE before timing tSU;STA. A framer that counted from its own
         //     release would drop SDA while SCL was still low -- which is not a START at
         //     all but an ordinary data-bit change.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start; wait_done(200);
         @(negedge clk); tgt_scl_low = 1'b1;    // the target stretches
         k = m_nsta;
         pulse_restart;
         wait_state(S_RS_SCLHI, 200);
         $display("T8  a repeated START waits for SCL to really rise before tSU;STA");
         ck_bit("T8 the framer has released SCL", f_scl_low, 1'b0);
         ck_bit("T8 the line is still low", scl, 1'b0);
         step;
         ck_bit("T8 and it reports waiting", f_stretch, 1'b1);
         for (n = 0; n < 40; n = n + 1) begin
            step;
            ck_int("T8 no START was emitted while SCL was held", m_nsta, k);
         end
         @(negedge clk); tgt_scl_low = 1'b0;
         wait_done(400);
         ck_int("T8 and the START appears once SCL rises", m_nsta, k + 1);

         // ----------------------------------------------------------------
         // T9. THE STRETCH, on a STOP. Same requirement, same mechanism: releasing SDA
         //     while SCL is low is not a STOP.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start; wait_done(200);
         @(negedge clk); tgt_scl_low = 1'b1;
         k = m_nsto;
         pulse_stop;
         wait_state(S_STO_SCLHI, 200);
         $display("T9  a STOP waits for SCL to really rise before tSU;STO");
         ck_bit("T9 SCL released by the framer", f_scl_low, 1'b0);
         ck_bit("T9 the line is held low by the target", scl, 1'b0);
         for (n = 0; n < 40; n = n + 1) begin
            step;
            ck_int("T9 no STOP was emitted while SCL was held", m_nsto, k);
         end
         @(negedge clk); tgt_scl_low = 1'b0;
         wait_done(400);
         ck_int("T9 and the STOP appears once SCL rises", m_nsto, k + 1);
         ck_bit("T9 the transfer is closed", m_intr, 1'b0);

         // ----------------------------------------------------------------
         // T10. The framer produces no OTHER edges. Over a full START, repeated START and
         //      STOP sequence the monitor must see exactly three framing events and no
         //      no bytes, because nothing was clocked.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start;   wait_done(400);
         pulse_restart; wait_done(400);
         pulse_stop;    wait_done(400);
         $display("T10 three sequences produce exactly three framing events");
         ck_int("T10 two STARTs", m_nsta, 2);
         ck_int("T10 one STOP", m_nsto, 1);
         ck_int("T10 no bytes, because nothing was clocked", m_nbyte, 0);
         // No mid-byte reports. Each of the repeated START and the STOP does produce one
         // clock pulse of its own -- releasing SCL is a rising edge, and a receiver samples
         // there -- but one pulse is what every conforming framing sequence produces, so
         // the monitor's threshold is two. See i2c_proto_mon's header.
         ck_int("T10 no mid-byte framing reports", m_nmid, 0);

         // ----------------------------------------------------------------
         // T11. Counters distinguish an initial START from a repeated one, which the
         //      WIRE cannot: both are the same edge. Only the framer knows which it
         //      issued, and a design that conflated them could not report a protocol
         //      error that depends on the difference.
         // ----------------------------------------------------------------
         $display("T11 the framer distinguishes an initial START from a repeated one");
         ck_int("T11 one initial START", n_sta, 1);
         ck_int("T11 one repeated START", n_rs, 1);
         ck_int("T11 one STOP", n_sto, 1);
         ck_int("T11 and the wire saw two STARTs, indistinguishably", m_nsta, 2);

         // ----------------------------------------------------------------
         // T12. Idle means silent. With no command, the framer drives nothing for as long
         //      as you care to wait.
         // ----------------------------------------------------------------
         do_reset;
         $display("T12 an idle framer drives nothing at all");
         for (n = 0; n < 60; n = n + 1) begin
            step;
            ck_bit("T12 SCL untouched", f_scl_low, 1'b0);
            ck_bit("T12 SDA untouched", f_sda_req, 1'b0);
         end
         ck_int("T12 and nothing was seen on the wire", m_nsta + m_nsto, 0);
         ck_int("T12 still idle", fstate, S_IDLE);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_framer_tb.v — the same tests in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_framer_tb.sv
   // Independent oracle for i2c_framer.
   //
   // The framer drives a real wired-AND bus, and the checks are made by i2c_proto_mon --
   // which sees only the two wires and applies §3.1.1 as written. So a framer that reaches
   // the right states by the wrong edges fails here, which is the only interesting way for
   // a framer to be wrong.
   //
   // The bench is the second device on the bus and can stretch SCL, so the two sequences
   // that release SCL mid-way -- the repeated START and the STOP -- are tested against a
   // target that does not let go immediately.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_framer_tb;

      localparam integer NHD  = 5;   // tHD;STA
      localparam integer NSUA = 6;   // tSU;STA
      localparam integer NSUO = 5;   // tSU;STO
      localparam integer NBUF = 6;   // tBUF
      localparam integer NSUD = 3;   // tSU;DAT

      localparam [3:0] S_IDLE = 4'd0, S_RS_SCLHI = 4'd6, S_STO_SCLHI = 4'd11;

      reg clk = 1'b0, rst_n = 1'b0;
      reg do_start = 1'b0, do_restart = 1'b0, do_stop = 1'b0;

      // the bench as a second device
      reg tgt_scl_low = 1'b0, tgt_sda_low = 1'b0;

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

      // The framer owns SDA alone in this bench, so the inversion is applied here the way
      // i2c_sda_ctrl applies it: a transmitted one releases, a zero pulls down.
      wire f_sda_low = f_sda_req ? ~f_sda_bit : 1'b0;

      wire scl, sda;
      wire [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
      wire [7:0] scl_h, sda_h;

      i2c_line_model #(.N_DEV(2)) bus (
         .scl_drive_low({tgt_scl_low, f_scl_low}),
         .sda_drive_low({tgt_sda_low, f_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_framer #(.N_HD_STA(NHD), .N_SU_STA(NSUA), .N_SU_STO(NSUO),
                   .N_BUF(NBUF), .N_SU_DAT(NSUD), .CNT_W(16)) dut (
         .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(1'b0),
         .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_stretch),
         .starts(n_sta), .restarts(n_rs), .stops(n_sto), .state(fstate));

      // The oracle: it sees only the wires.
      wire m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv, m_intr, m_midbyte;
      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_midbyte), .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, j;

      // Measure the hold between the START edge on the wire and SCL going low.
      integer hd_run, meas_hd;  reg arm_hd;
      // Measure tSU;STO: how long SCL was high with SDA low before the STOP edge.
      integer su_run, meas_su;
      // Measure tBUF: cycles from the STOP edge until bus_free.
      integer buf_run, meas_buf; reg arm_buf;

      // The bench detects the framing edges ITSELF, from the lines, rather than using the
      // monitor's outputs. The monitor is registered, so its start_seen arrives a cycle
      // after the edge and an arming cycle after that -- which would understate every
      // measured interval by two cycles. A measurement of a Table 10 parameter cannot
      // afford that, so the edge detection is local and combinational in intent.
      reg scl_l, sda_l;
      always @(negedge clk) begin
         if (rst_n) begin
            // tHD;STA: from the START edge (SDA falls while SCL high) until SCL falls.
            if (sda_l && !sda && scl) begin arm_hd = 1'b1; hd_run = 1; end
            else if (arm_hd) begin
               if (scl) hd_run = hd_run + 1;
               else begin meas_hd = hd_run; arm_hd = 1'b0; end
            end
            // tSU;STO: cycles with SCL high and SDA low, ending at the STOP edge.
            if (scl && !sda) su_run = su_run + 1;
            else if (!scl)   su_run = 0;
            if (!sda_l && sda && scl) meas_su = su_run;
            // tBUF: from the STOP edge until bus_free is asserted.
            if (!sda_l && sda && scl) begin arm_buf = 1'b1; buf_run = 0; end
            else if (arm_buf) begin
               buf_run = buf_run + 1;
               if (f_bus_free) begin meas_buf = buf_run; arm_buf = 1'b0; end
            end
            scl_l = scl; sda_l = sda;
         end
      end

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

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; do_start = 1'b0; do_restart = 1'b0; do_stop = 1'b0;
            tgt_scl_low = 1'b0; tgt_sda_low = 1'b0;
            hd_run = 0; meas_hd = 0; arm_hd = 1'b0;
            su_run = 0; meas_su = 0;
            buf_run = 0; meas_buf = 0; arm_buf = 1'b0;
            scl_l = 1'b1; sda_l = 1'b1;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task pulse_start;   begin @(negedge clk); do_start   = 1'b1; @(posedge clk); @(negedge clk); do_start   = 1'b0; end endtask
      task pulse_restart; begin @(negedge clk); do_restart = 1'b1; @(posedge clk); @(negedge clk); do_restart = 1'b0; end endtask
      task pulse_stop;    begin @(negedge clk); do_stop    = 1'b1; @(posedge clk); @(negedge clk); do_stop    = 1'b0; end endtask

      task wait_done (input integer max_cycles);
         begin
            n = 0;
            while (!f_done && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_done: no completion after %0d cycles (state %0d)",
                        max_cycles, fstate);
               errors = errors + 1;
            end
         end
      endtask

      task wait_state (input [3:0] want, input integer max_cycles);
         begin
            n = 0;
            while (fstate != want && n < max_cycles) begin step; n = n + 1; end
            if (n >= max_cycles) begin
               $display("  FAIL wait_state: state %0d not reached (now %0d)", want, fstate);
               errors = errors + 1;
            end
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d expected %0d", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      task ck_bit (input [200*8:1] what, input g, input e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0b expected %0b", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      initial begin
         $display("=== i2c_framer: the edges a data bit is forbidden to make ===");

         // ----------------------------------------------------------------
         // T1. Reset releases both lines, and the bus is assumed free.
         // ----------------------------------------------------------------
         do_reset;
         $display("T1  a reset framer releases both lines");
         ck_bit("T1 SCL released", f_scl_low, 1'b0);
         ck_bit("T1 SDA released", f_sda_low, 1'b0);
         ck_bit("T1 both lines high", scl & sda, 1'b1);
         ck_bit("T1 the bus is free", f_bus_free, 1'b1);
         ck_bit("T1 not in a transfer", m_intr, 1'b0);
         ck_int("T1 no framing seen", m_nsta + m_nsto, 0);

         // ----------------------------------------------------------------
         // T2. A START. The monitor -- which sees only the wires -- must report one, and
         //     the transfer must be open afterwards.
         // ----------------------------------------------------------------
         pulse_start;
         wait_done(200);
         $display("T2  the monitor sees a START on the wire, having seen only the wires");
         ck_int("T2 exactly one START", m_nsta, 1);
         ck_int("T2 no STOP", m_nsto, 0);
         ck_bit("T2 the transfer is open", m_intr, 1'b1);
         ck_bit("T2 the framer says it started", f_started, 1'b1);
         ck_bit("T2 SCL is now low, ready for the first bit", scl, 1'b0);
         ck_bit("T2 and SDA has been handed back", f_sda_req, 1'b0);

         // ----------------------------------------------------------------
         // T3. tHD;STA. The START edge is followed by the hold time BEFORE SCL falls.
         //     A framer that pulled SCL low immediately would produce an edge the
         //     receiving devices are not obliged to recognise.
         // ----------------------------------------------------------------
         $display("T3  tHD;STA elapses between the START edge and SCL falling");
         ck_int("T3 the hold is N_HD_STA cycles", meas_hd, NHD);

         // ----------------------------------------------------------------
         // T4. A STOP. SDA rises while SCL is high, and tSU;STO precedes it.
         // ----------------------------------------------------------------
         pulse_stop;
         wait_done(200);
         $display("T4  a STOP: SDA rises while SCL is high, after tSU;STO");
         ck_int("T4 exactly one STOP", m_nsto, 1);
         ck_bit("T4 the transfer is closed", m_intr, 1'b0);
         ck_bit("T4 the framer says so", f_started, 1'b0);
         ck_int("T4 tSU;STO before the edge", meas_su, NSUO);
         ck_bit("T4 both lines released", f_scl_low | f_sda_low, 1'b0);

         // ----------------------------------------------------------------
         // T5. tBUF. The bus is not free the instant the STOP happens.
         // ----------------------------------------------------------------
         // One step before reading the measurement. SystemVerilog does not need it -- the
         // measuring block uses blocking assignments, which are visible within the same time
         // step -- but the VHDL twin does, because a VHDL signal read in the delta it is
         // assigned still holds its old value. The step is kept in all three benches so the
         // three files stay line-for-line comparable and finish at the same time, which is
         // the evidence that they are running the same test.
         step;
         $display("T5  tBUF elapses after the STOP before the bus is free again");
         ck_int("T5 the bus-free delay is N_BUF cycles", meas_buf, NBUF);
         ck_bit("T5 and then it is free", f_bus_free, 1'b1);

         // ----------------------------------------------------------------
         // T6. A START requested before tBUF has elapsed is REFUSED, not queued and not
         //     issued early. Issuing it early is how two masters collide without ever
         //     arbitrating, because neither saw the other's START.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start;  wait_done(200);
         // THE CROSS-CHECK. With a transfer open the bus is not free, and the framer is
         // back in S_IDLE able to sample a command -- the only reachable state in which
         // `bus_free` is 0 and a command can be accepted, so the only place the guard
         // can be tested. Asking after tBUF has elapsed tests nothing.
         //
         // Refusing is not observable on the WIRE here: mid-transfer the framer holds SCL
         // low, so a wrongly-accepted START pulls SDA low while SCL is low -- an ordinary
         // data change, producing no START for the monitor to see. What catches it is the
         // invariant between the two counters: every START the framer BELIEVES it issued
         // must have been visible on the bus.
         ck_bit("T6 with a transfer open the bus is not free", f_bus_free, 1'b0);
         k = m_nsta;
         j = n_sta;
         @(negedge clk); do_start = 1'b1;
         n = 0;
         while (n < 30) begin step; n = n + 1; end
         @(negedge clk); do_start = 1'b0;
         ck_int("T6 a START with the bus not free is refused", m_nsta, k);
         ck_int("T6 and the framer did not count one either", n_sta, j);
         ck_int("T6 every START counted was seen on the wire", n_sta, m_nsta);
         pulse_stop;
         // Ask for a START while the STOP sequence is still running out tBUF.
         wait_state(S_IDLE, 400);
         k = m_nsta;
         @(negedge clk); do_start = 1'b1;
         // Hold the request through the whole bus-free period.
         n = 0;
         while (!f_bus_free && n < 200) begin step; n = n + 1; end
         $display("T6  a START asked for before tBUF has elapsed is refused, not rushed");
         ck_int("T6 no extra START was issued during tBUF", m_nsta, k);
         // and once the bus IS free, the held request is honoured
         wait_done(200);
         @(negedge clk); do_start = 1'b0;
         ck_int("T6 and it is honoured once the bus is free", m_nsta, k + 1);

         // ----------------------------------------------------------------
         // T7. A REPEATED START from mid-transfer. SDA must be lifted first: a line that
         //     is already low cannot make a HIGH-to-LOW transition, so a repeated START
         //     after a zero data bit needs SDA released before anything else.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start; wait_done(200);
         // Simulate a data bit having left SDA low: the bench holds it down, then lets go
         // when the framer takes over.
         k = m_nsta;
         pulse_restart;
         wait_done(400);
         $display("T7  a repeated START from mid-transfer, with SDA lifted first");
         ck_int("T7 a second START was seen on the wire", m_nsta, k + 1);
         ck_int("T7 and no STOP", m_nsto, 0);
         ck_bit("T7 the transfer is still open", m_intr, 1'b1);
         ck_int("T7 counted as a repeated START", n_rs, 1);
         ck_int("T7 and not as an initial one", n_sta, 1);
         ck_bit("T7 SCL is low afterwards", scl, 1'b0);

         // ----------------------------------------------------------------
         // T8. THE STRETCH, on a repeated START. The framer releases SCL and must wait
         //     for the LINE before timing tSU;STA. A framer that counted from its own
         //     release would drop SDA while SCL was still low -- which is not a START at
         //     all but an ordinary data-bit change.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start; wait_done(200);
         @(negedge clk); tgt_scl_low = 1'b1;    // the target stretches
         k = m_nsta;
         pulse_restart;
         wait_state(S_RS_SCLHI, 200);
         $display("T8  a repeated START waits for SCL to really rise before tSU;STA");
         ck_bit("T8 the framer has released SCL", f_scl_low, 1'b0);
         ck_bit("T8 the line is still low", scl, 1'b0);
         step;
         ck_bit("T8 and it reports waiting", f_stretch, 1'b1);
         for (n = 0; n < 40; n = n + 1) begin
            step;
            ck_int("T8 no START was emitted while SCL was held", m_nsta, k);
         end
         @(negedge clk); tgt_scl_low = 1'b0;
         wait_done(400);
         ck_int("T8 and the START appears once SCL rises", m_nsta, k + 1);

         // ----------------------------------------------------------------
         // T9. THE STRETCH, on a STOP. Same requirement, same mechanism: releasing SDA
         //     while SCL is low is not a STOP.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start; wait_done(200);
         @(negedge clk); tgt_scl_low = 1'b1;
         k = m_nsto;
         pulse_stop;
         wait_state(S_STO_SCLHI, 200);
         $display("T9  a STOP waits for SCL to really rise before tSU;STO");
         ck_bit("T9 SCL released by the framer", f_scl_low, 1'b0);
         ck_bit("T9 the line is held low by the target", scl, 1'b0);
         for (n = 0; n < 40; n = n + 1) begin
            step;
            ck_int("T9 no STOP was emitted while SCL was held", m_nsto, k);
         end
         @(negedge clk); tgt_scl_low = 1'b0;
         wait_done(400);
         ck_int("T9 and the STOP appears once SCL rises", m_nsto, k + 1);
         ck_bit("T9 the transfer is closed", m_intr, 1'b0);

         // ----------------------------------------------------------------
         // T10. The framer produces no OTHER edges. Over a full START, repeated START and
         //      STOP sequence the monitor must see exactly three framing events and no
         //      no bytes, because nothing was clocked.
         // ----------------------------------------------------------------
         do_reset;
         pulse_start;   wait_done(400);
         pulse_restart; wait_done(400);
         pulse_stop;    wait_done(400);
         $display("T10 three sequences produce exactly three framing events");
         ck_int("T10 two STARTs", m_nsta, 2);
         ck_int("T10 one STOP", m_nsto, 1);
         ck_int("T10 no bytes, because nothing was clocked", m_nbyte, 0);
         // No mid-byte reports. Each of the repeated START and the STOP does produce one
         // clock pulse of its own -- releasing SCL is a rising edge, and a receiver samples
         // there -- but one pulse is what every conforming framing sequence produces, so
         // the monitor's threshold is two. See i2c_proto_mon's header.
         ck_int("T10 no mid-byte framing reports", m_nmid, 0);

         // ----------------------------------------------------------------
         // T11. Counters distinguish an initial START from a repeated one, which the
         //      WIRE cannot: both are the same edge. Only the framer knows which it
         //      issued, and a design that conflated them could not report a protocol
         //      error that depends on the difference.
         // ----------------------------------------------------------------
         $display("T11 the framer distinguishes an initial START from a repeated one");
         ck_int("T11 one initial START", n_sta, 1);
         ck_int("T11 one repeated START", n_rs, 1);
         ck_int("T11 one STOP", n_sto, 1);
         ck_int("T11 and the wire saw two STARTs, indistinguishably", m_nsta, 2);

         // ----------------------------------------------------------------
         // T12. Idle means silent. With no command, the framer drives nothing for as long
         //      as you care to wait.
         // ----------------------------------------------------------------
         do_reset;
         $display("T12 an idle framer drives nothing at all");
         for (n = 0; n < 60; n = n + 1) begin
            step;
            ck_bit("T12 SCL untouched", f_scl_low, 1'b0);
            ck_bit("T12 SDA untouched", f_sda_req, 1'b0);
         end
         ck_int("T12 and nothing was seen on the wire", m_nsta + m_nsto, 0);
         ck_int("T12 still idle", fstate, S_IDLE);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_framer_tb.vhd — the same tests in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_framer_tb.vhd
   -- Independent oracle for i2c_framer. Behavioural twin of the SV and Verilog benches.
   --
   -- The framer drives a real wired-AND bus, and the checks are made by i2c_proto_mon, which
   -- sees only the two wires and applies §3.1.1 as written. So a framer that reaches the right
   -- states by the wrong edges fails here, which is the only interesting way for a framer to be
   -- wrong.
   --
   -- The bench is the second device on the bus and can stretch SCL, so the two sequences that
   -- release SCL mid-way -- the repeated START and the STOP -- are tested against a target that
   -- does not let go immediately.
   --
   -- Note that the intervals are measured LOCALLY from the lines rather than from the monitor's
   -- outputs. The monitor is registered, so its start_seen arrives a cycle after the edge and an
   -- arming cycle after that, which would understate every measured interval by two cycles. A
   -- measurement of a Table 10 parameter cannot afford that.
   -- ---------------------------------------------------------------------------

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

   entity i2c_framer_tb is
   end entity i2c_framer_tb;

   architecture sim of i2c_framer_tb is

      constant TCLK : time := 10 ns;
      constant NHD  : integer := 5;
      constant NSUA : integer := 6;
      constant NSUO : integer := 5;
      constant NBUF : integer := 6;
      constant NSUD : integer := 3;

      constant S_IDLE      : integer := 0;
      constant S_RS_SCLHI  : integer := 6;
      constant S_STO_SCLHI : integer := 11;

      signal clk, rst_n : std_logic := '0';
      signal do_start, do_restart, do_stop : std_logic := '0';
      signal tgt_scl_low, tgt_sda_low : std_logic := '0';

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

      -- The framer owns SDA alone here, so the inversion is applied the way i2c_sda_ctrl does.
      signal f_sda_low : std_logic;

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

      signal m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv : std_logic;
      signal m_intr, m_midbyte : std_logic;
      signal m_byteval : std_logic_vector(7 downto 0);
      signal m_bidx : unsigned(3 downto 0);
      signal m_nsta, m_nsto, m_nbyte, m_nmid : unsigned(15 downto 0);

      signal meas_hd, meas_su, meas_buf : integer := 0;
      signal halt : boolean := false;

   begin

      f_sda_low <= (not f_sda_bit) when f_sda_req = '1' else '0';
      scl_drv   <= tgt_scl_low & f_scl_low;
      sda_drv   <= tgt_sda_low & f_sda_low;

      bus_m : entity work.i2c_line_model
         generic map (N_DEV => 2)
         port map (scl_drive_low => scl_drv, sda_drive_low => sda_drv,
            scl => scl, sda => sda, scl_in => scl_in, sda_in => sda_in,
            scl_released_but_low => scl_rbl, sda_released_but_low => sda_rbl,
            scl_holders => scl_h, sda_holders => sda_h);

      dut : entity work.i2c_framer
         generic map (N_HD_STA => NHD, N_SU_STA => NSUA, N_SU_STO => NSUO,
                      N_BUF => NBUF, N_SU_DAT => NSUD, 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 => '0',
            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_stretch, starts => n_sta, restarts => n_rs, stops => n_sto,
            state => fstate);

      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 => 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_midbyte, bit_index => m_bidx,
            n_starts => m_nsta, n_stops => m_nsto, n_bytes => m_nbyte,
            n_midbyte => m_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;

      -- Local edge detection and interval measurement, with running counters as VARIABLES so
      -- they behave like the SystemVerilog bench's blocking assignments.
      meas : process (clk, rst_n)
         variable hd_run, su_run, buf_run : integer := 0;
         variable arm_hd, arm_buf         : std_logic := '0';
         variable scl_l, sda_l            : std_logic := '1';
      begin
         if rst_n = '0' then
            hd_run := 0; su_run := 0; buf_run := 0;
            arm_hd := '0'; arm_buf := '0'; scl_l := '1'; sda_l := '1';
            meas_hd <= 0; meas_su <= 0; meas_buf <= 0;
         elsif falling_edge(clk) then
            -- tHD;STA: from the START edge (SDA falls while SCL high) until SCL falls.
            if sda_l = '1' and sda = '0' and scl = '1' then
               arm_hd := '1'; hd_run := 1;
            elsif arm_hd = '1' then
               if scl = '1' then hd_run := hd_run + 1;
               else              meas_hd <= hd_run; arm_hd := '0';
               end if;
            end if;
            -- tSU;STO: cycles with SCL high and SDA low, ending at the STOP edge.
            if scl = '1' and sda = '0' then su_run := su_run + 1;
            elsif scl = '0'               then su_run := 0;
            end if;
            if sda_l = '0' and sda = '1' and scl = '1' then meas_su <= su_run; end if;
            -- tBUF: from the STOP edge until bus_free is asserted.
            if sda_l = '0' and sda = '1' and scl = '1' then
               arm_buf := '1'; buf_run := 0;
            elsif arm_buf = '1' then
               buf_run := buf_run + 1;
               if f_bus_free = '1' then meas_buf <= buf_run; arm_buf := '0'; end if;
            end if;
            scl_l := scl; sda_l := sda;
         end if;
      end process;

      stim : process
         variable err : integer := 0;
         variable n, k, j : 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'; do_start <= '0'; do_restart <= '0'; do_stop <= '0';
            tgt_scl_low <= '0'; tgt_sda_low <= '0';
            for i in 0 to 2 loop wait until rising_edge(clk); end loop;
            wait until falling_edge(clk); rst_n <= '1';
            step;
         end procedure;

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

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

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

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

         procedure wait_state (want : integer; max_cycles : integer) is
         begin
            n := 0;
            while to_integer(fstate) /= want and n < max_cycles loop step; n := n + 1; end loop;
            if n >= max_cycles then
               report "  FAIL wait_state: state " & integer'image(want) & " not reached"
                      severity note;
               err := err + 1;
            end if;
         end procedure;

      begin
         report "=== i2c_framer: the edges a data bit is forbidden to make ===" severity note;

         -- T1. Reset releases both lines, and the bus is assumed free.
         do_reset;
         report "T1  a reset framer releases both lines" severity note;
         ck_bit("T1 SCL released", f_scl_low, '0');
         ck_bit("T1 SDA released", f_sda_low, '0');
         ck_bit("T1 both lines high", scl and sda, '1');
         ck_bit("T1 the bus is free", f_bus_free, '1');
         ck_bit("T1 not in a transfer", m_intr, '0');
         ck_int("T1 no framing seen", to_integer(m_nsta) + to_integer(m_nsto), 0);

         -- T2. A START, reported by a monitor that has seen only the wires.
         pulse_start;
         wait_done(200);
         report "T2  the monitor sees a START on the wire, having seen only the wires"
                severity note;
         ck_int("T2 exactly one START", to_integer(m_nsta), 1);
         ck_int("T2 no STOP", to_integer(m_nsto), 0);
         ck_bit("T2 the transfer is open", m_intr, '1');
         ck_bit("T2 the framer says it started", f_started, '1');
         ck_bit("T2 SCL is now low, ready for the first bit", scl, '0');
         ck_bit("T2 and SDA has been handed back", f_sda_req, '0');

         -- T3. tHD;STA elapses between the START edge and SCL falling.
         report "T3  tHD;STA elapses between the START edge and SCL falling" severity note;
         ck_int("T3 the hold is N_HD_STA cycles", meas_hd, NHD);

         -- T4. A STOP: SDA rises while SCL is high, after tSU;STO.
         pulse_stop;
         wait_done(200);
         report "T4  a STOP: SDA rises while SCL is high, after tSU;STO" severity note;
         ck_int("T4 exactly one STOP", to_integer(m_nsto), 1);
         ck_bit("T4 the transfer is closed", m_intr, '0');
         ck_bit("T4 the framer says so", f_started, '0');
         ck_int("T4 tSU;STO before the edge", meas_su, NSUO);
         ck_bit("T4 both lines released", f_scl_low or f_sda_low, '0');

         -- T5. tBUF elapses after the STOP before the bus is free.
         --
         -- One step first: `meas_buf` is assigned by the measuring process at the very falling
         -- edge on which bus_free is asserted -- which is the edge wait_done returned on -- and
         -- a VHDL signal read in the same delta it is assigned still holds its old value.
         step;
         report "T5  tBUF elapses after the STOP before the bus is free again" severity note;
         ck_int("T5 the bus-free delay is N_BUF cycles", meas_buf, NBUF);
         ck_bit("T5 and then it is free", f_bus_free, '1');

         -- T6. A START requested before tBUF has elapsed is REFUSED, not issued early.
         --     Issuing it early is how two masters collide without ever arbitrating.
         do_reset;
         pulse_start; wait_done(200);
         -- THE CROSS-CHECK. With a transfer open the bus is not free and the framer is
         -- back in S_IDLE able to sample a command -- the only reachable state in which
         -- bus_free is '0' and a command can be accepted, so the only place the guard can
         -- be tested. Refusing is not observable on the WIRE: mid-transfer the framer
         -- holds SCL low, so a wrongly-accepted START pulls SDA low while SCL is low, an
         -- ordinary data change that produces no START. The invariant between the two
         -- counters is what catches it.
         ck_bit("T6 with a transfer open the bus is not free", f_bus_free, '0');
         k := to_integer(m_nsta);
         j := to_integer(n_sta);
         wait until falling_edge(clk); do_start <= '1';
         n := 0;
         while n < 30 loop step; n := n + 1; end loop;
         wait until falling_edge(clk); do_start <= '0';
         ck_int("T6 a START with the bus not free is refused", to_integer(m_nsta), k);
         ck_int("T6 and the framer did not count one either", to_integer(n_sta), j);
         ck_int("T6 every START counted was seen on the wire",
                to_integer(n_sta), to_integer(m_nsta));
         pulse_stop;
         wait_state(S_IDLE, 400);
         k := to_integer(m_nsta);
         wait until falling_edge(clk); do_start <= '1';
         n := 0;
         while f_bus_free = '0' and n < 200 loop step; n := n + 1; end loop;
         report "T6  a START asked for before tBUF has elapsed is refused, not rushed"
                severity note;
         ck_int("T6 no extra START was issued during tBUF", to_integer(m_nsta), k);
         wait_done(200);
         wait until falling_edge(clk); do_start <= '0';
         ck_int("T6 and it is honoured once the bus is free", to_integer(m_nsta), k + 1);

         -- T7. A REPEATED START from mid-transfer, with SDA lifted first.
         do_reset;
         pulse_start; wait_done(200);
         k := to_integer(m_nsta);
         pulse_restart;
         wait_done(400);
         report "T7  a repeated START from mid-transfer, with SDA lifted first" severity note;
         ck_int("T7 a second START was seen on the wire", to_integer(m_nsta), k + 1);
         ck_int("T7 and no STOP", to_integer(m_nsto), 0);
         ck_bit("T7 the transfer is still open", m_intr, '1');
         ck_int("T7 counted as a repeated START", to_integer(n_rs), 1);
         ck_int("T7 and not as an initial one", to_integer(n_sta), 1);
         ck_bit("T7 SCL is low afterwards", scl, '0');

         -- T8. THE STRETCH, on a repeated START. A framer that counted from its own release
         --     would drop SDA while SCL was still low -- an ordinary data-bit change, not a
         --     START.
         do_reset;
         pulse_start; wait_done(200);
         wait until falling_edge(clk); tgt_scl_low <= '1';
         k := to_integer(m_nsta);
         pulse_restart;
         wait_state(S_RS_SCLHI, 200);
         report "T8  a repeated START waits for SCL to really rise before tSU;STA"
                severity note;
         ck_bit("T8 the framer has released SCL", f_scl_low, '0');
         ck_bit("T8 the line is still low", scl, '0');
         step;
         ck_bit("T8 and it reports waiting", f_stretch, '1');
         for i in 0 to 39 loop
            step;
            ck_int("T8 no START was emitted while SCL was held", to_integer(m_nsta), k);
         end loop;
         wait until falling_edge(clk); tgt_scl_low <= '0';
         wait_done(400);
         ck_int("T8 and the START appears once SCL rises", to_integer(m_nsta), k + 1);

         -- T9. THE STRETCH, on a STOP. Same requirement: releasing SDA while SCL is low is
         --     not a STOP.
         do_reset;
         pulse_start; wait_done(200);
         wait until falling_edge(clk); tgt_scl_low <= '1';
         k := to_integer(m_nsto);
         pulse_stop;
         wait_state(S_STO_SCLHI, 200);
         report "T9  a STOP waits for SCL to really rise before tSU;STO" severity note;
         ck_bit("T9 SCL released by the framer", f_scl_low, '0');
         ck_bit("T9 the line is held low by the target", scl, '0');
         for i in 0 to 39 loop
            step;
            ck_int("T9 no STOP was emitted while SCL was held", to_integer(m_nsto), k);
         end loop;
         wait until falling_edge(clk); tgt_scl_low <= '0';
         wait_done(400);
         ck_int("T9 and the STOP appears once SCL rises", to_integer(m_nsto), k + 1);
         ck_bit("T9 the transfer is closed", m_intr, '0');

         -- T10. Three sequences produce exactly three framing events and nothing else. Each of
         --      the repeated START and the STOP does produce one clock pulse of its own, but
         --      one pulse is what every conforming framing sequence produces, so the monitor's
         --      mid-byte threshold is two.
         do_reset;
         pulse_start;   wait_done(400);
         pulse_restart; wait_done(400);
         pulse_stop;    wait_done(400);
         report "T10 three sequences produce exactly three framing events" severity note;
         ck_int("T10 two STARTs", to_integer(m_nsta), 2);
         ck_int("T10 one STOP", to_integer(m_nsto), 1);
         ck_int("T10 no bytes, because nothing was clocked", to_integer(m_nbyte), 0);
         ck_int("T10 no mid-byte framing reports", to_integer(m_nmid), 0);

         -- T11. Counters distinguish an initial START from a repeated one, which the WIRE
         --      cannot: both are the same edge.
         report "T11 the framer distinguishes an initial START from a repeated one"
                severity note;
         ck_int("T11 one initial START", to_integer(n_sta), 1);
         ck_int("T11 one repeated START", to_integer(n_rs), 1);
         ck_int("T11 one STOP", to_integer(n_sto), 1);
         ck_int("T11 and the wire saw two STARTs, indistinguishably",
                to_integer(m_nsta), 2);

         -- T12. Idle means silent.
         do_reset;
         report "T12 an idle framer drives nothing at all" severity note;
         for i in 0 to 59 loop
            step;
            ck_bit("T12 SCL untouched", f_scl_low, '0');
            ck_bit("T12 SDA untouched", f_sda_req, '0');
         end loop;
         ck_int("T12 and nothing was seen on the wire",
                to_integer(m_nsta) + to_integer(m_nsto), 0);
         ck_int("T12 still idle", to_integer(fstate), S_IDLE);

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

   end architecture sim;

5b. Execution

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_framerPASS 12/12PASS 12/12PASS 12/123820 ns, all three

6. Mutation Testing — and the Defect No Bus Check Could See

Nine defects, one per claim in this chapter.

#Injected defectExpected detectionResult
M1STOP released while SCL is still lowT4, T9KILLED (4)
M2tHD;STA one cycle shortT3KILLED (2)
M3tSU;STO one cycle shortT4KILLED (2)
M4repeated START ignores the stretchT8KILLED (4)
M5tBUF dropped entirelyT5, T6KILLED (2)
M6START edge polarity reversedT2, T10KILLED (8)
M7the STOP edge drives SDA low instead of releasingT4KILLED (3)
M8a START accepted when the bus is not freeT6 after strengtheningKILLED (4)
M9the repeated-START edge never fallsT7, T11KILLED (6)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
killed: 9   survived: 0   score: 9/9
restored: PASS

M8 survived first, and the reason is worth the space

The mutation removes the bus_free guard so a START command is accepted whenever it arrives. It survived the original suite, and diagnosing why produced two findings.

First, the test was asking at the wrong moment. T6 originally did wait_state(S_IDLE, …) before asserting the request — but the framer enters S_IDLE from S_BUF in the same cycle it sets bus_free, so by the time the test asked, the bus was already free and the guard was irrelevant. The test's intent was right and its synchronisation defeated it.

The only reachable state in which bus_free is 0 and the framer can sample a command is S_IDLE mid-transfer, after a START, while the byte engine works. So that is the only place the guard can be tested at all.

Second — and this is the interesting part — refusing is not observable on the wire. Mid-transfer the framer holds SCL low. A wrongly accepted START therefore pulls SDA low while SCL is low, which is an ordinary data change. The monitor sees nothing. No bus-level check, however thorough, can detect this defect.

What catches it is an invariant between two counters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
every START the framer BELIEVES it issued must have been visible on the bus

  ck_int("T6 every START counted was seen on the wire", n_sta, m_nsta);

A framer that accepts a command it should have refused increments its own counter while the monitor sees nothing, and the two diverge. With that check added — ported to all three languages, which moved the finish time from 3500 ns to 3820 ns in all three — M8 dies with four failure lines.

7. Verification Connection — A Monitor That Distinguishes Framing From Data

Azvya Education Pvt. Ltd.VLSI Mentor
framing_monitor.sv — the one decision a framing monitor makes
   // A framing monitor's entire job is a single classification, made on every SDA
   // transition, and the rule is the contradiction of section 1 read backwards:
   //
   //     SDA changes while SCL is LOW   ->  ordinary data preparation.  Ignore.
   //     SDA falls   while SCL is HIGH  ->  START or repeated START.
   //     SDA rises   while SCL is HIGH  ->  STOP.
   //
   // WHICH of START and repeated START it is cannot be read from the edge: the two
   // are the same transition with the same shape. It depends on whether a transfer
   // is already open, which is MONITOR STATE, not a bus observation:
   //
   //     no transfer open  ->  START
   //     transfer open     ->  repeated START
   //
   // So even this minimal monitor is a state machine, and Chapter 5.4 made the same
   // point at the protocol level. A monitor written as a pure sampler cannot report
   // the difference, and the difference is what a scoreboard needs to know whether
   // the bus was released between two transfers.
   //
   // WHAT THIS MONITOR MUST NOT DO, and it is tempting: count framing events and
   // compare them against the DUT's own counters as its only check. Section 6 shows
   // why that comparison is necessary -- but it is a CROSS-check, an additional
   // observation, not the monitor's primary duty. A monitor that reports "the DUT's
   // count matches mine" has verified agreement, not correctness: both could be
   // wrong in the same direction if the monitor took its timing from the DUT.
   //
   // COVERAGE the framer makes reachable, and which a transfer-level bench misses:
   //
   //   cover: a repeated START whose SCL release was stretched   (T8)
   //   cover: a STOP whose SCL release was stretched             (T9)
   //   cover: a START requested during tBUF                     (T6)
   //
   // All three require another device to interfere at a precise moment. None occurs
   // in a regression built only from clean transfers.

8. FPGA and ASIC Implications

On an FPGA, the framer is the block most exposed to readback latency. It makes three decisions that depend on observing SCL high — the repeated START's setup, the STOP's setup, and leaving a stretch — and each sees the line two cycles late through the synchroniser. The direction is safe for the same reason as in 17.3: tSU;STA and tSU;STO have only minimums, so a late start to the interval makes it longer. But the hold parameter is different in character — tHD;STA is measured from an edge the framer produces itself, so no readback is involved and no latency applies. Knowing which intervals are anchored to observations and which to the master's own outputs tells you exactly which parameters synchroniser latency can and cannot affect.

On an ASIC, the same analysis applies with the pad's input filter added in series, and tSP — the 50 ns spike the filter must suppress — has a specific consequence here: a stretch shorter than the filter delay is invisible, so the framer will not wait for it. That is harmless, because a stretch shorter than tSP is also shorter than the rise time and the line was never meaningfully low. The framer's counters (starts, restarts, stops, stretch_wait) are cheap and belong in a status register: they are the only way firmware can distinguish "the bus is slow" from "the bus is stuck", and Chapter 17.11 uses them.

9. Debugging — The Repeated START That Was an Ordinary Data Bit

Symptom

A master implements combined transactions to read a register-map sensor: write the pointer, repeated START, then read. It works against two sensors and fails against a third, which returns the wrong register every time -- consistently the register the pointer had been set to on the PREVIOUS transaction. All three sensors are from different vendors and all three datasheets describe the same combined-transfer sequence.

Root Cause

The framer measured a setup interval from its own drive intent instead of from the observed line, so on a board with a slower SCL rise the repeated START was emitted while SCL was still low. A transition on SDA while SCL is low is not framing -- it is ordinary data preparation, and every conforming device reads it as such. The master and the sensor then held incompatible beliefs about what phase the transfer was in, and because the sensor kept acknowledging the bytes it was receiving, nothing in the master's view of the bus looked wrong. The two working boards were not evidence of correctness; their rise times simply happened to beat the master's fixed delay.

Fix
Gate the setup interval on the readback: enter the tSU;STA state only when scl_in actually reads high, which is mutation M4 in reverse. Then note what class of bug this was -- not a wrong number but a wrong ANCHOR, an interval measured from intent rather than observation, and the same defect shape as the SCL generator's in Chapter 17.3. For the regression: test T8, which holds SCL low across the framer's release and asserts that the repeated START edge does not appear until the line has risen. A bench whose bus model has zero rise time cannot fail the original design, which is why the environment needs a target that can hold the line rather than merely a wire that settles instantly.

Three generalisations.

A wrong anchor, not a wrong number. The interval length was correct; it was measured from the wrong event. This is the third appearance of that shape in the module — 17.3's generator, 17.4's arbitration gate, and now the framer — and in every case the fix is to anchor on the observation rather than the intent.

Acknowledges throughout are not evidence of agreement. The sensor acknowledged every byte, including the one the master intended as an address. Both devices were behaving correctly for the phase each believed it was in. An acknowledge confirms a byte was received, never that it was understood as the same kind of byte.

Two working boards were a coincidence of rise time. The defect was present on all three and latent on two. That is the same structure as 17.3 §10's 100 MHz qualification: a configuration in which the correct and incorrect designs are indistinguishable is not a passing test.

10. Common Misconceptions

"Framing is just another kind of bit." It is the one transition a data bit may never make. The bit engine guarantees SDA stability during SCL-high; framing requires the opposite, so they cannot be one block. §1.

"A START is an SDA falling edge." It is an SDA falling edge while SCL is high. With SCL low the identical edge is ordinary data preparation, and every device reads it that way. §1 and §9.

"A repeated START can just pull SDA low." Only if SDA is high first. Mid-transfer it may be a data zero, and a line already low cannot produce a falling edge — so the sequence must lift SDA before anything else. §2.

"tBUF applies to a transfer." It applies to the gap between two of them, and it is the only Table 10 parameter that constrains a master while it is doing nothing. §2.

"A setup interval can be counted from releasing the line." Not if a target may be stretching. tSU;STA is a setup time against the SDA edge, and it is meaningless if SCL never rose. §3 and §9.

"A short setup time is a marginal START." A START with SCL low is not a marginal START; it is not a START. The failure mode is categorical, not marginal. §3.

"If the master's counters say it issued a START, it issued one." Its counters record intent. Only a wire-level observer records the bus, and M8 is precisely the case where the two disagree. §6.

"A wire-level monitor catches everything." It cannot catch a defect whose only symptom is disagreement between intent and observation — mid-transfer, a wrongly accepted START produces no bus edge at all. §6.

"Acknowledges throughout mean both sides agree." Both sides were correct for the phase each believed it was in. An ACK confirms receipt, not shared interpretation. §9.

"It works on two boards, so the timing is right." Two boards whose rise time beat a fixed delay. The defect was present on all three. §9.

11. Reason It Through

Why is the framing sequencer's separateness a proof rather than a preference?

Because the bit engine's postcondition is "SDA stable while SCL high" and framing's precondition is "SDA moves while SCL high". A single block would have to guarantee and violate the same property on the same wire in the same phase. No coding style avoids that. §1.

A master releases SCL, waits a fixed 200 ns, then pulls SDA low. On which boards does this produce a repeated START?

Only those where SCL's rise completes inside 200 ns. Anywhere slower, SDA falls while SCL is still low and the bus sees ordinary data preparation. The design is correct for a subset of boards and wrong in general. §3 and §9.

Why must a repeated START lift SDA before doing anything else?

Because mid-transfer SDA may be low from a data zero, and a line that is already low cannot produce the HIGH-to-LOW transition a START is defined as. §2.

Which of tHD;STA, tSU;STA and tSU;STO are affected by synchroniser latency, and why not the others?

tSU;STA and tSU;STO are, because both are anchored on observing SCL high. tHD;STA is not, because it is measured from an edge the framer produces itself — no readback is involved. §8.

M8 removed the bus-free guard and no bus-level check could see it. Why, and what did?

Because mid-transfer the framer holds SCL low, so a wrongly accepted START pulls SDA low while SCL is low — an ordinary data change producing no framing edge. What caught it was comparing the framer's own START count against the monitor's: intent diverged from observation. §6.

A test intended to check that a START is refused before tBUF passed against a design with no such guard. What was wrong with the test?

It waited for the framer to reach S_IDLE first, and S_IDLE is entered from S_BUF in the same cycle bus_free is set. By the time the request was made the bus was already free, so the guard was never exercised. §6.

Why can a bench whose bus model has zero rise time never fail the §9 design?

Because the defect only appears when the line is still low after the master released it. A model in which release means instantly high makes drive intent and observation identical, which is exactly the distinction the defect violates. §9.

12. Understanding Check

13. Summary

The framing sequencer cannot be a mode of the bit engine, and that is a proof rather than a preference. The bit engine must keep SDA stable while SCL is high; framing requires SDA to move while SCL is high. Contradictory postconditions on one wire in one phase.

That contradiction is deliberate — framing is recognisable precisely because it is the one thing a data bit may never do.

Four sequences, each built from its Table 10 interval: START with tHD;STA, repeated START with tSU;STA then tHD;STA, STOP with tSU;STO, and tBUF between a STOP and the next START.

A repeated START must lift SDA first, because a line already low cannot produce a falling edge, and mid-transfer SDA may be a data zero.

tBUF is the only parameter that constrains an idle master. It governs the gap between transfers, not a transfer.

Every SCL release inside a framing sequence waits for the readback. A framer that counts a setup interval from its own release emits SDA falling while SCL is low — which is not a marginal START but an ordinary data change, read as such by every device.

Nine mutants, nine killed — but M8 survived the original suite twice over, and both reasons were findings.

A test can have the right intent and the wrong synchronisation. T6 waited for S_IDLE, which is entered in the same cycle bus_free is set, so the guard it meant to test was never exercised.

And a wire-level monitor has one blind spot. Mid-transfer, a wrongly accepted START produces no bus edge at all, so no bus-level check can see it. Only comparing the framer's own count against the monitor's — intent against observation — reveals the divergence.

The recurring defect shape in this module is a wrong anchor, not a wrong number. Three chapters, three intervals measured from intent instead of observation, and the same fix each time.

14. What Comes Next

The framer produces the edges that open and close a transfer. Between them, bytes have to move.

Chapter 17.6 builds exactly one bit — no more — using the two strobes 17.3 exposed and the ownership interface 17.4 defined. Its single obligation is the one the framer exists to violate: SDA stable through the whole of SCL-high.

It is also where transmitting a one stops being an abstraction. The bit engine releases the line and must not assume the line went high, because between its release and the pull-up there is a rise time, another device, and possibly a competitor.

Continue learning