Skip to content
VLSI Mentor

I²C · Module 17

Decomposing an I²C Master — From Requirements to Architecture

An I²C master is not one state machine, and the reason is structural rather than stylistic: the protocol imposes four independent time bases that change on four unrelated events. Derives the block structure from the normative obligations, establishes the wired-AND bus model every later chapter is written against, and shows why the framing generator cannot live inside the bit engine.

Sixteen modules have described what the I²C bus does. This one builds the thing that drives it.

The temptation at this point is to open an editor and write always_ff @(posedge clk) followed by a state enumeration, because that is what "designing a master" feels like from the outside. Almost every first I²C master is written that way, and almost every one of them is rewritten — not because the author made a careless mistake, but because the protocol will not fit in one state machine, and the reason it will not fit is worth an entire chapter.

This chapter writes no master logic at all. It derives the shape the master has to have, from obligations the specification already states, and it builds exactly one piece of hardware: a model of the wire itself. That ordering is deliberate. The architecture of a master is a consequence of the behaviour of the conductor it drives, and that behaviour has to be written down before anything can be designed against it.

1. What the Specification Fixes, and What It Hands to You

It is worth being precise about this before deriving anything, because the boundary is not where most people assume.

Unlike the register maps of Module 16, where the specification delegated nearly everything, almost all of a master's external behaviour is normative. UM10204 fixes the edges, the phases, the ownership handoffs and the feedback obligations. There is very little room for interpretation on the wire.

What it does not fix is the internal structure:

So the architecture is unconstrained and the behaviour is not. That combination is what makes this module possible: we can derive a structure, and then check it against obligations that are already fixed.

2. The Four Time Bases

Here is the argument this whole module rests on.

A single state machine clocked by the system clock would have to hold, simultaneously, four different kinds of state:

1. A TIME state — where in tLOW or tHIGH the bus currently is. The phases of Table 10 are analogue durations, and in RTL they are counts of system-clock cycles. Something has to be counting.

2. A BIT state — which of the nine pulses of a byte is in progress. Nine, not eight, because §3.1.4's acknowledge pulse is structurally different from the eight that precede it: SDA changes hands in it.

3. A BYTE state — which byte this is and which direction it travels, because SDA ownership depends on it. An address byte is always master-driven; a data byte may be either.

4. A TRANSACTION state — which phase of a combined transfer is running, because a repeated START must not reset the byte pointer. Module 10 established that the pointer surviving the turnaround is the entire reason the combined format works.

Now the part that matters. Those four change on four unrelated events:

StateAdvances onEvent source
TIMEa divider tickthe internal system clock
BITan SCL edge read back from the busthe bus, not this master
BYTEa byte boundary — the ninth pulse completingthe bit engine
TRANSACTIONa host commandsoftware, asynchronously

A state machine whose state must change on four unrelated events gets one of two things: four sets of transitions in every state, or counters bolted onto the side. And the counters are the other three machines, admitted late and without their own reset or test.

3. The Collision That Proves It

The four-time-base argument is abstract. There is one concrete collision that settles it on its own, and it is the cleanest proof in this module that the decomposition is forced.

Recall the two rules from Module 5:

The data rule. SDA must be stable while SCL is HIGH. It may change only while SCL is LOW.

The framing rule. A START is a HIGH-to-LOW transition on SDA while SCL is HIGH. A STOP is a LOW-to-HIGH transition on SDA while SCL is HIGH.

These are exact opposites, and deliberately so — that contradiction is what makes framing detectable at all, and Chapter 5.2 is built on it.

Now consider what that means for a bit engine. The bit engine's entire job — its one obligation, the thing it exists to guarantee — is that SDA is stable while SCL is HIGH. Framing requires SDA to move while SCL is HIGH.

So the block that generates framing cannot be the block that transmits bits. Not "should not for clarity" — cannot, because the two have contradictory postconditions on the same wire in the same phase. A separate framing sequencer is structurally necessary.

That is one block derived with certainty, before any code exists. Chapter 17.5 builds it.

4. The Wire Comes First

Everything above concerns the master's insides. Before any of it can be designed, the conductor has to be modelled, because two of the four time bases are driven by what the bus reads back rather than by what this master intended.

The same connection carries SDA. So a line is not a signal a device sets. It is the AND of what every device is doing, and the only way a device learns the line's state is to read it back.

Three consequences, each of which becomes a block later in this module:

A device drives LOW or RELEASES. There is no drive-high. Therefore the bit a device transmits is the inverse of its drive enable, and drive_low is the only output a conforming I²C pin has. This is the single most common sign error in a first master.

"Released and still low" is the feedback primitive. A device that releases a line and reads it low has learned something — and which something depends only on which line it was. On SCL it means another device is stretching or synchronising (§3.1.6, §3.1.7). On SDA it means another device is transmitting a zero where this one sent a one, which is arbitration loss (§3.1.8). One comparator, two protocol features. Chapter 17.10 is built entirely on this observation.

A single stuck device takes the whole bus down and no other device can lift it, because the line is low if any device pulls it low. That is why Chapter 17.11's recovery procedure exists — and why it cannot work on SCL.

5. The Architecture, Derived

Putting §2, §3 and §4 together gives the block structure — and every block on it is present because a specific obligation cannot be discharged anywhere else.

A block diagram of an I2C master in three rows. The top row is the forward path: a host command interface feeds a transaction controller, which feeds a byte engine, which feeds a bit engine. The middle row holds the two line-facing blocks: an SCL timing generator and an SDA open-drain control, both fed by the bit engine, and both driving the two-wire bus at the right. A framing sequencer sits beside them, also driving the bus, fed from the transaction controller directly rather than through the bit engine. The bottom row is the feedback path: the bus feeds a bus feedback block, which returns clock-stretch information to the SCL generator and arbitration-loss information to the transaction controller, and also feeds an error manager that reports back to the host command interface.Host interface17.2 — registersTransaction ctrl17.8 — TRANSACTIONByte engine17.7 — BYTEBit engine17.6 — BITFraming seq17.5 — S, Sr, PSCL generator17.3 — TIMESDA control17.4 — ownershipBus feedback17.10 — read backError manager17.11 — recovery12
Figure 1 — the master, decomposed. Left to right is the direction of authority: software asks, the transaction controller sequences, the byte and bit engines serialise, and the two line blocks drive copper. The feedback path along the bottom is the one that makes the whole thing an I²C master rather than a shift register with a clock — it carries what the bus actually did back to the blocks that must obey it.

Read the middle row as the only part that touches copper, and the bottom row as the part that makes this a bus master rather than a transmitter. A shift register with a clock generator would occupy the top row alone and would be wrong on a real bus in three separate ways — it would ignore stretching, miss arbitration, and never recover from a stuck line.

6. The Bus, in Three Languages

Here is the wire. It is the first executable hardware in this module and it is not a master at all — it is the model every subsequent chapter's testbench instantiates to stand in for the bus.

It is worth being explicit about what kind of artifact this is. It is a model, not a design intended for synthesis: scl_holders counts how many devices are pulling a line down, which no real bus can observe. That output exists so a testbench can distinguish "one device is holding SDA" from "two are" — a distinction the resolved line cannot show, and one that matters enormously when verifying arbitration.

6a. The bus model

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_line_model.sv — the wired-AND bus, one entry per device
   // -----------------------------------------------------------------------------
   // i2c_line_model.sv
   // The wired-AND bus, as a synthesizable-style model with one entry per device.
   //
   // This is the first block in Module 17 and not a master at all, because the
   // architecture of a master is DERIVED from the behaviour of the wire it drives, and
   // that behaviour has to be written down before anything can be designed against it.
   //
   // UM10204 §3.1.7: "Clock synchronization is performed using the wired-AND connection
   // of I2C interfaces to the SCL line." The same connection carries SDA. So the line is
   // not a signal a device sets; it is the AND of what every device is doing, and the
   // only way a device learns the line's state is to read it BACK.
   //
   // Three facts this model makes explicit, each of which becomes a block later:
   //
   //   1. A device drives LOW or RELEASES. There is no drive-high. So the bit a device
   //      transmits is the INVERSE of its drive enable, and `drive_low` is the only
   //      output a conforming I²C pin has.
   //
   //   2. `released_but_low` is the feedback primitive. A device that releases a line
   //      and reads it low has learned something -- and WHICH something depends only on
   //      which line it was: SCL means another device is stretching or synchronizing
   //      (§3.1.6, §3.1.7), SDA means another device is transmitting a zero where this
   //      one sent a one, which is arbitration loss (§3.1.8). One comparator, two
   //      protocol features. Chapter 17.10 is built on this.
   //
   //   3. The line is LOW if ANY device pulls it low, so a single stuck device takes the
   //      whole bus down and no other device can lift it. That is why Chapter 17.11's
   //      recovery exists and why it cannot work on SCL.
   //
   // The pull-up is modelled as "released by everyone => HIGH". A real bus takes tr to
   // get there, which is Table 10's rise time and the reason Chapter 17.3's period
   // budget has to allocate for it; this model is logical, not analogue, and says so.
   // -----------------------------------------------------------------------------

   module i2c_line_model #(
      parameter int N_DEV = 2          // how many devices hang off the bus
   ) (
      // Each device contributes one bit per line. Bit i is device i.
      input  logic [N_DEV-1:0]  scl_drive_low,
      input  logic [N_DEV-1:0]  sda_drive_low,

      // The resolved lines: the wired-AND of every device's contribution.
      output logic             scl,
      output logic             sda,

      // What each device reads back. Identical to the resolved line -- every device on
      // the bus sees the same thing, which is the whole point of a shared wire and the
      // reason a broadcast acknowledge cannot be attributed to a sender.
      output logic [N_DEV-1:0]  scl_in,
      output logic [N_DEV-1:0]  sda_in,

      // The feedback primitive, per device and per line: this device released the line
      // and the line is nonetheless low, so somebody else is holding it.
      output logic [N_DEV-1:0]  scl_released_but_low,
      output logic [N_DEV-1:0]  sda_released_but_low,

      // How many devices are pulling each line down. Not physical -- a real bus cannot
      // tell -- and exposed so a testbench can assert the difference between "one
      // device is holding SDA" and "two are", which the resolved line cannot show.
      output logic [7:0]        scl_holders,
      output logic [7:0]        sda_holders
   );

      // The wired-AND, which in drive-low terms is an OR of the drive enables: the line
      // is low if any device pulls it low, and high only if every device has released.
      assign scl = ~(|scl_drive_low);
      assign sda = ~(|sda_drive_low);

      // Every device reads the same line. Replicating it is the honest model: there is
      // no per-device version of a shared wire.
      assign scl_in = {N_DEV{scl}};
      assign sda_in = {N_DEV{sda}};

      // Released and still low. Note this is exactly `~drive_low & ~line`, which is why
      // a master needs no dedicated arbitration or stretch detector -- only a readback
      // path and one AND gate per line.
      assign scl_released_but_low = (~scl_drive_low) & {N_DEV{~scl}};
      assign sda_released_but_low = (~sda_drive_low) & {N_DEV{~sda}};

      // Population counts, for the bench only.
      //
      // Written as a function called from a continuous assignment rather than as an
      // `always @(*)` block, and the difference is not stylistic. An `always @(*)` is
      // triggered by a CHANGE on something it reads, so if every device starts released
      // and the first thing the bench does is release them all again, nothing changes,
      // the block never runs, and the counts sit at X for the whole of the first test --
      // reported as a mismatch against 0 in a model that is otherwise correct.
      //
      // A continuous assignment is evaluated at time zero regardless, so the counts are
      // right before anything has happened. Which is exactly when a truth-table bench
      // looks at them.
      function [7:0] popcount (input [N_DEV-1:0] v);
         integer i;
         begin
            popcount = 8'd0;
            for (i = 0; i < N_DEV; i = i + 1) if (v[i]) popcount = popcount + 8'd1;
         end
      endfunction

      assign scl_holders = popcount(scl_drive_low);
      assign sda_holders = popcount(sda_drive_low);

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_line_model.v — the same model in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_line_model.sv
   // The wired-AND bus, as a synthesizable-style model with one entry per device.
   //
   // This is the first block in Module 17 and not a master at all, because the
   // architecture of a master is DERIVED from the behaviour of the wire it drives, and
   // that behaviour has to be written down before anything can be designed against it.
   //
   // UM10204 §3.1.7: "Clock synchronization is performed using the wired-AND connection
   // of I2C interfaces to the SCL line." The same connection carries SDA. So the line is
   // not a signal a device sets; it is the AND of what every device is doing, and the
   // only way a device learns the line's state is to read it BACK.
   //
   // Three facts this model makes explicit, each of which becomes a block later:
   //
   //   1. A device drives LOW or RELEASES. There is no drive-high. So the bit a device
   //      transmits is the INVERSE of its drive enable, and `drive_low` is the only
   //      output a conforming I²C pin has.
   //
   //   2. `released_but_low` is the feedback primitive. A device that releases a line
   //      and reads it low has learned something -- and WHICH something depends only on
   //      which line it was: SCL means another device is stretching or synchronizing
   //      (§3.1.6, §3.1.7), SDA means another device is transmitting a zero where this
   //      one sent a one, which is arbitration loss (§3.1.8). One comparator, two
   //      protocol features. Chapter 17.10 is built on this.
   //
   //   3. The line is LOW if ANY device pulls it low, so a single stuck device takes the
   //      whole bus down and no other device can lift it. That is why Chapter 17.11's
   //      recovery exists and why it cannot work on SCL.
   //
   // The pull-up is modelled as "released by everyone => HIGH". A real bus takes tr to
   // get there, which is Table 10's rise time and the reason Chapter 17.3's period
   // budget has to allocate for it; this model is logical, not analogue, and says so.
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_line_model #(
      parameter N_DEV = 2          // how many devices hang off the bus
   ) (
      // Each device contributes one bit per line. Bit i is device i.
      input  wire [N_DEV-1:0]  scl_drive_low,
      input  wire [N_DEV-1:0]  sda_drive_low,

      // The resolved lines: the wired-AND of every device's contribution.
      output wire              scl,
      output wire              sda,

      // What each device reads back. Identical to the resolved line -- every device on
      // the bus sees the same thing, which is the whole point of a shared wire and the
      // reason a broadcast acknowledge cannot be attributed to a sender.
      output wire [N_DEV-1:0]  scl_in,
      output wire [N_DEV-1:0]  sda_in,

      // The feedback primitive, per device and per line: this device released the line
      // and the line is nonetheless low, so somebody else is holding it.
      output wire [N_DEV-1:0]  scl_released_but_low,
      output wire [N_DEV-1:0]  sda_released_but_low,

      // How many devices are pulling each line down. Not physical -- a real bus cannot
      // tell -- and exposed so a testbench can assert the difference between "one
      // device is holding SDA" and "two are", which the resolved line cannot show.
      output wire [7:0]        scl_holders,
      output wire [7:0]        sda_holders
   );

      // The wired-AND, which in drive-low terms is an OR of the drive enables: the line
      // is low if any device pulls it low, and high only if every device has released.
      assign scl = ~(|scl_drive_low);
      assign sda = ~(|sda_drive_low);

      // Every device reads the same line. Replicating it is the honest model: there is
      // no per-device version of a shared wire.
      assign scl_in = {N_DEV{scl}};
      assign sda_in = {N_DEV{sda}};

      // Released and still low. Note this is exactly `~drive_low & ~line`, which is why
      // a master needs no dedicated arbitration or stretch detector -- only a readback
      // path and one AND gate per line.
      assign scl_released_but_low = (~scl_drive_low) & {N_DEV{~scl}};
      assign sda_released_but_low = (~sda_drive_low) & {N_DEV{~sda}};

      // Population counts, for the bench only.
      //
      // Written as a function called from a continuous assignment rather than as an
      // `always @(*)` block, and the difference is not stylistic. An `always @(*)` is
      // triggered by a CHANGE on something it reads, so if every device starts released
      // and the first thing the bench does is release them all again, nothing changes,
      // the block never runs, and the counts sit at X for the whole of the first test --
      // reported as a mismatch against 0 in a model that is otherwise correct.
      //
      // A continuous assignment is evaluated at time zero regardless, so the counts are
      // right before anything has happened. Which is exactly when a truth-table bench
      // looks at them.
      function [7:0] popcount (input [N_DEV-1:0] v);
         integer i;
         begin
            popcount = 8'd0;
            for (i = 0; i < N_DEV; i = i + 1) if (v[i]) popcount = popcount + 8'd1;
         end
      endfunction

      assign scl_holders = popcount(scl_drive_low);
      assign sda_holders = popcount(sda_drive_low);

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_line_model.vhd — the same model in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_line_model.vhd
   -- The wired-AND bus, as a model with one entry per device.
   -- Behavioural twin of i2c_line_model.sv / .v.
   --
   -- This is the first block in Module 17 and not a master at all, because the architecture
   -- of a master is DERIVED from the behaviour of the wire it drives, and that behaviour has
   -- to be written down before anything can be designed against it.
   --
   -- UM10204 §3.1.7: "Clock synchronization is performed using the wired-AND connection of
   -- I2C interfaces to the SCL line." The same connection carries SDA. So the line is not a
   -- signal a device sets; it is the AND of what every device is doing, and the only way a
   -- device learns the line's state is to read it BACK.
   --
   -- A NOTE ON THE VHDL. `std_logic` is a RESOLVED type, so two drivers on one signal give
   -- 'X' rather than an error -- silently, unlike `integer`, where a second driver is fatal
   -- at elaboration. That makes VHDL a good place to model a wired-AND bus and a dangerous
   -- place to model one carelessly: this entity therefore computes the resolved line with an
   -- explicit reduction rather than by connecting several drivers to one signal, so the
   -- model says what it means instead of relying on resolution.
   -- ---------------------------------------------------------------------------

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

   entity i2c_line_model is
      generic (
         N_DEV : integer := 2
      );
      port (
         scl_drive_low : in  std_logic_vector(N_DEV-1 downto 0);
         sda_drive_low : in  std_logic_vector(N_DEV-1 downto 0);

         scl : out std_logic;
         sda : out std_logic;

         scl_in : out std_logic_vector(N_DEV-1 downto 0);
         sda_in : out std_logic_vector(N_DEV-1 downto 0);

         scl_released_but_low : out std_logic_vector(N_DEV-1 downto 0);
         sda_released_but_low : out std_logic_vector(N_DEV-1 downto 0);

         scl_holders : out unsigned(7 downto 0);
         sda_holders : out unsigned(7 downto 0)
      );
   end entity i2c_line_model;

   architecture rtl of i2c_line_model is

      -- The wired-AND, which in drive-low terms is an OR of the drive enables: the line is
      -- low if any device pulls it low, and high only if every device has released.
      function any_low (v : std_logic_vector) return std_logic is
         variable r : std_logic := '0';
      begin
         for i in v'range loop
            if v(i) = '1' then r := '1'; end if;
         end loop;
         return r;
      end function;

      -- Population counts, for the bench only. Written as a function rather than as a
      -- process, so that the value is right at time zero: a process sensitive to the inputs
      -- does not run until something CHANGES, and a truth-table bench looks at the counts
      -- before anything has happened.
      function popcount (v : std_logic_vector) return unsigned is
         variable r : unsigned(7 downto 0) := (others => '0');
      begin
         for i in v'range loop
            if v(i) = '1' then r := r + 1; end if;
         end loop;
         return r;
      end function;

      signal scl_i : std_logic;
      signal sda_i : std_logic;

   begin

      scl_i <= not any_low(scl_drive_low);
      sda_i <= not any_low(sda_drive_low);

      scl <= scl_i;
      sda <= sda_i;

      -- Every device reads the same line. Replicating it is the honest model: there is no
      -- per-device version of a shared wire.
      scl_in <= (others => '0') when scl_i = '0' else (others => '1');
      sda_in <= (others => '0') when sda_i = '0' else (others => '1');

      -- Released and still low. This is exactly `not drive_low and not line`, which is why a
      -- master needs no dedicated arbitration or stretch detector -- only a readback path and
      -- one gate per line.
      gen_fb : for i in 0 to N_DEV-1 generate
         scl_released_but_low(i) <= (not scl_drive_low(i)) and (not scl_i);
         sda_released_but_low(i) <= (not sda_drive_low(i)) and (not sda_i);
      end generate;

      scl_holders <= popcount(scl_drive_low);
      sda_holders <= popcount(sda_drive_low);

   end architecture rtl;

6b. The testbenches

The bench is a truth-table bench: it has no event waits at all, because the model is purely combinational. That is worth stating plainly, since this module's standard otherwise requires a watchdog on every wait — there are none here to bound, and the bench terminates at 51 ns by exhausting its stimulus rather than by waiting for anything.

Twelve tests, and the interesting ones are not the obvious ones:

#TestWhy it exists
T1nobody driving → the pull-up winsthe only way a line goes high
T2one device is enoughany single device takes the line down
T3exhaustive — all sixteen combinationsagainst an independently computed population count
T4every device reads the same linethere is no per-device copy of a shared wire
T5a device reads its own lowthe holder is not exempt from the wired-AND
T6the feedback primitivereleased and still low reports exactly once
T7a released line that is high reports nothingthe ordinary case must stay silent
T8two holders look exactly like onethe line cannot count its holders
T9the last holder decidesreleasing all but one leaves the line low
T10the two lines are independentnothing couples SCL and SDA in the model
T11there is no drive-highthe model has no way to express one
T12the counts never exceed the device countthe sanity check on the count itself

T8 is the one worth dwelling on. It asserts that the resolved line is identical whether one device or two are holding it down — that is, it asserts an inability. A bus cannot tell you how many devices are pulling on it, and a verification environment that quietly assumes it can will write arbitration checks that no real hardware could implement.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_line_model_tb.sv — twelve tests, including the exhaustive sixteen
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_line_model_tb.sv
   // Independent oracle for i2c_line_model.
   //
   // The model is combinational, so the bench is an exhaustive truth table rather than a
   // sequence: with four devices there are 16 combinations per line, and every one is
   // checked. That is worth doing precisely because the model is the foundation the other
   // twelve chapters stand on -- a wrong AND here would make every later block appear
   // broken in a different way.
   // -----------------------------------------------------------------------------
   module i2c_line_model_tb;

      localparam integer ND = 4;

      logic [ND-1:0] scl_low = {ND{1'b0}};
      logic [ND-1:0] sda_low = {ND{1'b0}};

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

      i2c_line_model #(.N_DEV(ND)) dut (
         .scl_drive_low(scl_low), .sda_drive_low(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));

      integer errors = 0;
      integer i, j, k, pc;

      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_line_model: the wire the whole module is designed against ===");

         // ----------------------------------------------------------------
         // T1. Nobody driving: the pull-up wins. This is the ONLY way a line goes high.
         // ----------------------------------------------------------------
         scl_low = 4'b0000; sda_low = 4'b0000; #1;
         $display("T1  released by everyone, the pull-up wins");
         ck_bit("T1 SCL high", scl, 1'b1);
         ck_bit("T1 SDA high", sda, 1'b1);
         ck_int("T1 nobody holds SCL", scl_h, 0);
         ck_int("T1 nobody holds SDA", sda_h, 0);

         // ----------------------------------------------------------------
         // T2. ONE DEVICE IS ENOUGH. Any single device takes the line down, and no other
         //     device can lift it. That asymmetry is why Chapter 17.11's recovery exists
         //     and why it cannot work on SCL.
         // ----------------------------------------------------------------
         $display("T2  one device is enough to take a line down, and nobody can lift it");
         for (i = 0; i < ND; i = i + 1) begin
            scl_low = 4'b0000; scl_low[i] = 1'b1;
            sda_low = 4'b0000; sda_low[i] = 1'b1;
            #1;
            ck_bit("T2 SCL is low", scl, 1'b0);
            ck_bit("T2 SDA is low", sda, 1'b0);
            ck_int("T2 exactly one holder on SCL", scl_h, 1);
            ck_int("T2 exactly one holder on SDA", sda_h, 1);
         end

         // ----------------------------------------------------------------
         // T3. EXHAUSTIVE. All sixteen combinations, against a population count computed
         //     independently by the bench.
         // ----------------------------------------------------------------
         $display("T3  all sixteen drive combinations, against an independent count");
         for (i = 0; i < 16; i = i + 1) begin
            scl_low = i[3:0];
            sda_low = ~i[3:0];
            #1;
            pc = 0;
            for (j = 0; j < ND; j = j + 1) if (i[j]) pc = pc + 1;
            ck_int("T3 the SCL holder count", scl_h, pc);
            ck_int("T3 the SDA holder count", sda_h, ND - pc);
            ck_bit("T3 SCL is low exactly when somebody holds it", scl, (pc == 0));
            ck_bit("T3 SDA is low exactly when somebody holds it", sda, (ND - pc == 0));
         end

         // ----------------------------------------------------------------
         // T4. EVERY DEVICE READS THE SAME LINE. There is no per-device version of a
         //     shared wire, which is why a broadcast acknowledge cannot be attributed to
         //     any particular device.
         // ----------------------------------------------------------------
         scl_low = 4'b0010; sda_low = 4'b1000; #1;
         $display("T4  every device reads the same line, including the one driving it");
         for (i = 0; i < ND; i = i + 1) begin
            ck_bit("T4 this device reads the same SCL", scl_in[i], scl);
            ck_bit("T4 this device reads the same SDA", sda_in[i], sda);
         end

         // ----------------------------------------------------------------
         // T5. A DEVICE READS ITS OWN LOW. The device pulling the line down sees it low,
         //     which is why "released and low" rather than "low" is the useful primitive.
         // ----------------------------------------------------------------
         $display("T5  the device pulling a line down reads it low, like everyone else");
         ck_bit("T5 device 1 holds SCL and reads it low", scl_in[1], 1'b0);
         ck_bit("T5 and is not reporting released-but-low", scl_rbl[1], 1'b0);
         ck_bit("T5 device 3 holds SDA and reads it low", sda_in[3], 1'b0);
         ck_bit("T5 and is not reporting released-but-low", sda_rbl[3], 1'b0);

         // ----------------------------------------------------------------
         // T6. THE FEEDBACK PRIMITIVE. A device that released the line and reads it low
         //     has learned something, and which something depends only on the line: SCL
         //     means stretching or synchronization (§3.1.6, §3.1.7), SDA means arbitration
         //     loss (§3.1.8). One AND gate, two protocol features.
         // ----------------------------------------------------------------
         $display("T6  released and still low: the primitive both feedback features use");
         ck_bit("T6 device 0 released SCL and reads it low", scl_rbl[0], 1'b1);
         ck_bit("T6 device 2 released SCL and reads it low", scl_rbl[2], 1'b1);
         ck_bit("T6 device 0 released SDA and reads it low", sda_rbl[0], 1'b1);
         ck_bit("T6 but device 3 is the one holding SDA",    sda_rbl[3], 1'b0);

         // ----------------------------------------------------------------
         // T7. A released line that is HIGH reports nothing, which is the ordinary case
         //     and must not be mistaken for feedback.
         // ----------------------------------------------------------------
         scl_low = 4'b0000; sda_low = 4'b0000; #1;
         $display("T7  a released line that is high reports nothing");
         ck_int("T7 no SCL feedback", scl_rbl, 0);
         ck_int("T7 no SDA feedback", sda_rbl, 0);

         // ----------------------------------------------------------------
         // T8. TWO HOLDERS LOOK EXACTLY LIKE ONE, on the line. The holder count is not
         //     physical -- it exists only so this bench can assert the difference -- and
         //     the fact that the line cannot show it is why a master can never tell how
         //     many devices are stretching.
         // ----------------------------------------------------------------
         scl_low = 4'b0001; #1;
         k = scl;
         scl_low = 4'b1111; #1;
         $display("T8  two holders look exactly like one, on the line");
         ck_bit("T8 the line reads the same with one holder and with four", scl, k[0]);
         ck_int("T8 though the bench can see four", scl_h, 4);
         scl_low = 4'b0001; #1;
         ck_int("T8 and one", scl_h, 1);

         // ----------------------------------------------------------------
         // T9. THE LAST HOLDER DECIDES. Releasing all but one leaves the line low;
         //     releasing the last lets it go. There is no majority and no arbitration in
         //     the wire itself -- §3.1.8's arbitration is a consequence of this, not a
         //     mechanism in it.
         // ----------------------------------------------------------------
         $display("T9  the last holder decides, and there is no majority rule in a wire");
         scl_low = 4'b1111; #1; ck_bit("T9 four holders: low", scl, 1'b0);
         scl_low = 4'b0111; #1; ck_bit("T9 three holders: low", scl, 1'b0);
         scl_low = 4'b0011; #1; ck_bit("T9 two holders: low", scl, 1'b0);
         scl_low = 4'b0001; #1; ck_bit("T9 one holder: low", scl, 1'b0);
         scl_low = 4'b0000; #1; ck_bit("T9 none: high", scl, 1'b1);

         // ----------------------------------------------------------------
         // T10. THE TWO LINES ARE INDEPENDENT. Nothing couples them in the model, because
         //      nothing couples them on a board -- which is why a stuck SDA and a stuck
         //      SCL are different faults with different remedies (§3.1.16).
         // ----------------------------------------------------------------
         scl_low = 4'b1111; sda_low = 4'b0000; #1;
         $display("T10 the two lines are independent, which is why the two faults differ");
         ck_bit("T10 SCL low", scl, 1'b0);
         ck_bit("T10 SDA high", sda, 1'b1);
         scl_low = 4'b0000; sda_low = 4'b1111; #1;
         ck_bit("T10 SCL high", scl, 1'b1);
         ck_bit("T10 SDA low", sda, 1'b0);

         // ----------------------------------------------------------------
         // T11. THERE IS NO DRIVE-HIGH, and the model has no way to express one. The only
         //      input is a drive-LOW enable, so the bit a device transmits is the inverse
         //      of its drive enable -- the sign error Chapter 17.4 exists to contain.
         // ----------------------------------------------------------------
         $display("T11 the only input is a drive-LOW, so a transmitted one is a release");
         scl_low = 4'b0000; sda_low = 4'b0001; #1;
         ck_bit("T11 device 0 transmitting a zero drives low", sda, 1'b0);
         sda_low = 4'b0000; #1;
         ck_bit("T11 device 0 transmitting a one releases", sda, 1'b1);

         // ----------------------------------------------------------------
         // T12. AND THE COUNTS NEVER EXCEED THE DEVICE COUNT, which is the sanity check
         //      that catches a width mismatch between the parameter and the port.
         // ----------------------------------------------------------------
         $display("T12 the holder counts are bounded by the number of devices");
         for (i = 0; i < 16; i = i + 1) begin
            scl_low = i[3:0]; sda_low = i[3:0]; #1;
            if (scl_h > ND || sda_h > ND) begin
               $display("  FAIL T12 a holder count exceeded %0d", ND);
               errors = errors + 1;
            end
         end
         ck_int("T12 with everyone driving, the count is the device count", scl_h, ND);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_line_model_tb.v — the same twelve tests in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_line_model_tb.sv
   // Independent oracle for i2c_line_model.
   //
   // The model is combinational, so the bench is an exhaustive truth table rather than a
   // sequence: with four devices there are 16 combinations per line, and every one is
   // checked. That is worth doing precisely because the model is the foundation the other
   // twelve chapters stand on -- a wrong AND here would make every later block appear
   // broken in a different way.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_line_model_tb;

      localparam integer ND = 4;

      reg [ND-1:0] scl_low = {ND{1'b0}};
      reg [ND-1:0] sda_low = {ND{1'b0}};

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

      i2c_line_model #(.N_DEV(ND)) dut (
         .scl_drive_low(scl_low), .sda_drive_low(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));

      integer errors = 0;
      integer i, j, k, pc;

      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_line_model: the wire the whole module is designed against ===");

         // ----------------------------------------------------------------
         // T1. Nobody driving: the pull-up wins. This is the ONLY way a line goes high.
         // ----------------------------------------------------------------
         scl_low = 4'b0000; sda_low = 4'b0000; #1;
         $display("T1  released by everyone, the pull-up wins");
         ck_bit("T1 SCL high", scl, 1'b1);
         ck_bit("T1 SDA high", sda, 1'b1);
         ck_int("T1 nobody holds SCL", scl_h, 0);
         ck_int("T1 nobody holds SDA", sda_h, 0);

         // ----------------------------------------------------------------
         // T2. ONE DEVICE IS ENOUGH. Any single device takes the line down, and no other
         //     device can lift it. That asymmetry is why Chapter 17.11's recovery exists
         //     and why it cannot work on SCL.
         // ----------------------------------------------------------------
         $display("T2  one device is enough to take a line down, and nobody can lift it");
         for (i = 0; i < ND; i = i + 1) begin
            scl_low = 4'b0000; scl_low[i] = 1'b1;
            sda_low = 4'b0000; sda_low[i] = 1'b1;
            #1;
            ck_bit("T2 SCL is low", scl, 1'b0);
            ck_bit("T2 SDA is low", sda, 1'b0);
            ck_int("T2 exactly one holder on SCL", scl_h, 1);
            ck_int("T2 exactly one holder on SDA", sda_h, 1);
         end

         // ----------------------------------------------------------------
         // T3. EXHAUSTIVE. All sixteen combinations, against a population count computed
         //     independently by the bench.
         // ----------------------------------------------------------------
         $display("T3  all sixteen drive combinations, against an independent count");
         for (i = 0; i < 16; i = i + 1) begin
            scl_low = i[3:0];
            sda_low = ~i[3:0];
            #1;
            pc = 0;
            for (j = 0; j < ND; j = j + 1) if (i[j]) pc = pc + 1;
            ck_int("T3 the SCL holder count", scl_h, pc);
            ck_int("T3 the SDA holder count", sda_h, ND - pc);
            ck_bit("T3 SCL is low exactly when somebody holds it", scl, (pc == 0));
            ck_bit("T3 SDA is low exactly when somebody holds it", sda, (ND - pc == 0));
         end

         // ----------------------------------------------------------------
         // T4. EVERY DEVICE READS THE SAME LINE. There is no per-device version of a
         //     shared wire, which is why a broadcast acknowledge cannot be attributed to
         //     any particular device.
         // ----------------------------------------------------------------
         scl_low = 4'b0010; sda_low = 4'b1000; #1;
         $display("T4  every device reads the same line, including the one driving it");
         for (i = 0; i < ND; i = i + 1) begin
            ck_bit("T4 this device reads the same SCL", scl_in[i], scl);
            ck_bit("T4 this device reads the same SDA", sda_in[i], sda);
         end

         // ----------------------------------------------------------------
         // T5. A DEVICE READS ITS OWN LOW. The device pulling the line down sees it low,
         //     which is why "released and low" rather than "low" is the useful primitive.
         // ----------------------------------------------------------------
         $display("T5  the device pulling a line down reads it low, like everyone else");
         ck_bit("T5 device 1 holds SCL and reads it low", scl_in[1], 1'b0);
         ck_bit("T5 and is not reporting released-but-low", scl_rbl[1], 1'b0);
         ck_bit("T5 device 3 holds SDA and reads it low", sda_in[3], 1'b0);
         ck_bit("T5 and is not reporting released-but-low", sda_rbl[3], 1'b0);

         // ----------------------------------------------------------------
         // T6. THE FEEDBACK PRIMITIVE. A device that released the line and reads it low
         //     has learned something, and which something depends only on the line: SCL
         //     means stretching or synchronization (§3.1.6, §3.1.7), SDA means arbitration
         //     loss (§3.1.8). One AND gate, two protocol features.
         // ----------------------------------------------------------------
         $display("T6  released and still low: the primitive both feedback features use");
         ck_bit("T6 device 0 released SCL and reads it low", scl_rbl[0], 1'b1);
         ck_bit("T6 device 2 released SCL and reads it low", scl_rbl[2], 1'b1);
         ck_bit("T6 device 0 released SDA and reads it low", sda_rbl[0], 1'b1);
         ck_bit("T6 but device 3 is the one holding SDA",    sda_rbl[3], 1'b0);

         // ----------------------------------------------------------------
         // T7. A released line that is HIGH reports nothing, which is the ordinary case
         //     and must not be mistaken for feedback.
         // ----------------------------------------------------------------
         scl_low = 4'b0000; sda_low = 4'b0000; #1;
         $display("T7  a released line that is high reports nothing");
         ck_int("T7 no SCL feedback", scl_rbl, 0);
         ck_int("T7 no SDA feedback", sda_rbl, 0);

         // ----------------------------------------------------------------
         // T8. TWO HOLDERS LOOK EXACTLY LIKE ONE, on the line. The holder count is not
         //     physical -- it exists only so this bench can assert the difference -- and
         //     the fact that the line cannot show it is why a master can never tell how
         //     many devices are stretching.
         // ----------------------------------------------------------------
         scl_low = 4'b0001; #1;
         k = scl;
         scl_low = 4'b1111; #1;
         $display("T8  two holders look exactly like one, on the line");
         ck_bit("T8 the line reads the same with one holder and with four", scl, k[0]);
         ck_int("T8 though the bench can see four", scl_h, 4);
         scl_low = 4'b0001; #1;
         ck_int("T8 and one", scl_h, 1);

         // ----------------------------------------------------------------
         // T9. THE LAST HOLDER DECIDES. Releasing all but one leaves the line low;
         //     releasing the last lets it go. There is no majority and no arbitration in
         //     the wire itself -- §3.1.8's arbitration is a consequence of this, not a
         //     mechanism in it.
         // ----------------------------------------------------------------
         $display("T9  the last holder decides, and there is no majority rule in a wire");
         scl_low = 4'b1111; #1; ck_bit("T9 four holders: low", scl, 1'b0);
         scl_low = 4'b0111; #1; ck_bit("T9 three holders: low", scl, 1'b0);
         scl_low = 4'b0011; #1; ck_bit("T9 two holders: low", scl, 1'b0);
         scl_low = 4'b0001; #1; ck_bit("T9 one holder: low", scl, 1'b0);
         scl_low = 4'b0000; #1; ck_bit("T9 none: high", scl, 1'b1);

         // ----------------------------------------------------------------
         // T10. THE TWO LINES ARE INDEPENDENT. Nothing couples them in the model, because
         //      nothing couples them on a board -- which is why a stuck SDA and a stuck
         //      SCL are different faults with different remedies (§3.1.16).
         // ----------------------------------------------------------------
         scl_low = 4'b1111; sda_low = 4'b0000; #1;
         $display("T10 the two lines are independent, which is why the two faults differ");
         ck_bit("T10 SCL low", scl, 1'b0);
         ck_bit("T10 SDA high", sda, 1'b1);
         scl_low = 4'b0000; sda_low = 4'b1111; #1;
         ck_bit("T10 SCL high", scl, 1'b1);
         ck_bit("T10 SDA low", sda, 1'b0);

         // ----------------------------------------------------------------
         // T11. THERE IS NO DRIVE-HIGH, and the model has no way to express one. The only
         //      input is a drive-LOW enable, so the bit a device transmits is the inverse
         //      of its drive enable -- the sign error Chapter 17.4 exists to contain.
         // ----------------------------------------------------------------
         $display("T11 the only input is a drive-LOW, so a transmitted one is a release");
         scl_low = 4'b0000; sda_low = 4'b0001; #1;
         ck_bit("T11 device 0 transmitting a zero drives low", sda, 1'b0);
         sda_low = 4'b0000; #1;
         ck_bit("T11 device 0 transmitting a one releases", sda, 1'b1);

         // ----------------------------------------------------------------
         // T12. AND THE COUNTS NEVER EXCEED THE DEVICE COUNT, which is the sanity check
         //      that catches a width mismatch between the parameter and the port.
         // ----------------------------------------------------------------
         $display("T12 the holder counts are bounded by the number of devices");
         for (i = 0; i < 16; i = i + 1) begin
            scl_low = i[3:0]; sda_low = i[3:0]; #1;
            if (scl_h > ND || sda_h > ND) begin
               $display("  FAIL T12 a holder count exceeded %0d", ND);
               errors = errors + 1;
            end
         end
         ck_int("T12 with everyone driving, the count is the device count", scl_h, ND);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_line_model_tb.vhd — the same twelve tests in VHDL
   -- ---------------------------------------------------------------------------
   -- i2c_line_model_tb.vhd
   -- Independent oracle for i2c_line_model. Behavioural twin of the SV and Verilog benches.
   --
   -- The model is combinational, so the bench is an exhaustive truth table rather than a
   -- sequence: with four devices there are sixteen combinations per line, and every one is
   -- checked against a population count the bench computes itself.
   -- ---------------------------------------------------------------------------

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

   entity i2c_line_model_tb is
   end entity i2c_line_model_tb;

   architecture sim of i2c_line_model_tb is

      constant ND : integer := 4;

      signal scl_low : std_logic_vector(ND-1 downto 0) := (others => '0');
      signal sda_low : std_logic_vector(ND-1 downto 0) := (others => '0');

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

   begin

      dut : entity work.i2c_line_model
         generic map (N_DEV => ND)
         port map (scl_drive_low => scl_low, sda_drive_low => 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);

      stim : process
         variable err : integer := 0;
         variable pc  : integer;
         variable k   : std_logic;
         -- A variable, because an aggregate with a non-locally-static choice -- `(i => '1',
         -- others => '0')` with a loop index -- is illegal in VHDL. A variable is updated
         -- immediately rather than after a delta, so the whole vector is set in one go and
         -- the bench needs only one delay per iteration.
         variable onehot : std_logic_vector(ND-1 downto 0);

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

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

         function b2sl (b : boolean) return std_logic is
         begin
            if b then return '1'; else return '0'; end if;
         end function;

      begin
         report "=== i2c_line_model: the wire the whole module is designed against ==="
                severity note;

         -- T1. Nobody driving: the pull-up wins. This is the ONLY way a line goes high.
         scl_low <= (others => '0'); sda_low <= (others => '0'); wait for 1 ns;
         report "T1  released by everyone, the pull-up wins" severity note;
         ck_bit("T1 SCL high", scl, '1');
         ck_bit("T1 SDA high", sda, '1');
         ck_int("T1 nobody holds SCL", to_integer(scl_h), 0);
         ck_int("T1 nobody holds SDA", to_integer(sda_h), 0);

         -- T2. ONE DEVICE IS ENOUGH. Any single device takes the line down and no other can
         --     lift it. That asymmetry is why Chapter 17.11's recovery exists and why it
         --     cannot work on SCL.
         report "T2  one device is enough to take a line down, and nobody can lift it"
                severity note;
         for i in 0 to ND-1 loop
            onehot := (others => '0');
            onehot(i) := '1';
            scl_low <= onehot;
            sda_low <= onehot;
            wait for 1 ns;
            ck_bit("T2 SCL is low", scl, '0');
            ck_bit("T2 SDA is low", sda, '0');
            ck_int("T2 exactly one holder on SCL", to_integer(scl_h), 1);
            ck_int("T2 exactly one holder on SDA", to_integer(sda_h), 1);
         end loop;

         -- T3. EXHAUSTIVE. All sixteen combinations, against a count the bench computes.
         report "T3  all sixteen drive combinations, against an independent count"
                severity note;
         for i in 0 to 15 loop
            scl_low <= std_logic_vector(to_unsigned(i, ND));
            sda_low <= not std_logic_vector(to_unsigned(i, ND));
            wait for 1 ns;
            pc := 0;
            for j in 0 to ND-1 loop
               if ((i / (2**j)) mod 2) = 1 then pc := pc + 1; end if;
            end loop;
            ck_int("T3 the SCL holder count", to_integer(scl_h), pc);
            ck_int("T3 the SDA holder count", to_integer(sda_h), ND - pc);
            ck_bit("T3 SCL is low exactly when somebody holds it", scl, b2sl(pc = 0));
            ck_bit("T3 SDA is low exactly when somebody holds it", sda, b2sl(ND - pc = 0));
         end loop;

         -- T4. EVERY DEVICE READS THE SAME LINE. There is no per-device version of a shared
         --     wire, which is why a broadcast acknowledge cannot be attributed to a device.
         scl_low <= "0010"; sda_low <= "1000"; wait for 1 ns;
         report "T4  every device reads the same line, including the one driving it"
                severity note;
         for i in 0 to ND-1 loop
            ck_bit("T4 this device reads the same SCL", scl_in(i), scl);
            ck_bit("T4 this device reads the same SDA", sda_in(i), sda);
         end loop;

         -- T5. A DEVICE READS ITS OWN LOW. The device pulling the line down sees it low,
         --     which is why "released and low" rather than "low" is the useful primitive.
         report "T5  the device pulling a line down reads it low, like everyone else"
                severity note;
         ck_bit("T5 device 1 holds SCL and reads it low", scl_in(1), '0');
         ck_bit("T5 and is not reporting released-but-low", scl_rbl(1), '0');
         ck_bit("T5 device 3 holds SDA and reads it low", sda_in(3), '0');
         ck_bit("T5 and is not reporting released-but-low", sda_rbl(3), '0');

         -- T6. THE FEEDBACK PRIMITIVE. Released and low means somebody else is holding it,
         --     and which protocol feature that is depends only on the line: SCL means
         --     stretching or synchronization (§3.1.6, §3.1.7), SDA means arbitration loss
         --     (§3.1.8). One gate, two features.
         report "T6  released and still low: the primitive both feedback features use"
                severity note;
         ck_bit("T6 device 0 released SCL and reads it low", scl_rbl(0), '1');
         ck_bit("T6 device 2 released SCL and reads it low", scl_rbl(2), '1');
         ck_bit("T6 device 0 released SDA and reads it low", sda_rbl(0), '1');
         ck_bit("T6 but device 3 is the one holding SDA",    sda_rbl(3), '0');

         -- T7. A released line that is HIGH reports nothing, which is the ordinary case and
         --     must not be mistaken for feedback.
         scl_low <= (others => '0'); sda_low <= (others => '0'); wait for 1 ns;
         report "T7  a released line that is high reports nothing" severity note;
         ck_int("T7 no SCL feedback", to_integer(unsigned(scl_rbl)), 0);
         ck_int("T7 no SDA feedback", to_integer(unsigned(sda_rbl)), 0);

         -- T8. TWO HOLDERS LOOK EXACTLY LIKE ONE, on the line. The holder count is not
         --     physical -- a real bus cannot tell -- and the fact that the line cannot show
         --     it is why a master can never know how many devices are stretching.
         scl_low <= "0001"; wait for 1 ns;
         k := scl;
         scl_low <= "1111"; wait for 1 ns;
         report "T8  two holders look exactly like one, on the line" severity note;
         ck_bit("T8 the line reads the same with one holder and with four", scl, k);
         ck_int("T8 though the bench can see four", to_integer(scl_h), 4);
         scl_low <= "0001"; wait for 1 ns;
         ck_int("T8 and one", to_integer(scl_h), 1);

         -- T9. THE LAST HOLDER DECIDES. There is no majority rule in a wire -- §3.1.8's
         --     arbitration is a consequence of this, not a mechanism inside it.
         report "T9  the last holder decides, and there is no majority rule in a wire"
                severity note;
         scl_low <= "1111"; wait for 1 ns; ck_bit("T9 four holders: low", scl, '0');
         scl_low <= "0111"; wait for 1 ns; ck_bit("T9 three holders: low", scl, '0');
         scl_low <= "0011"; wait for 1 ns; ck_bit("T9 two holders: low", scl, '0');
         scl_low <= "0001"; wait for 1 ns; ck_bit("T9 one holder: low", scl, '0');
         scl_low <= "0000"; wait for 1 ns; ck_bit("T9 none: high", scl, '1');

         -- T10. THE TWO LINES ARE INDEPENDENT, because nothing couples them on a board --
         --      which is why a stuck SDA and a stuck SCL are different faults with different
         --      remedies (§3.1.16).
         scl_low <= "1111"; sda_low <= "0000"; wait for 1 ns;
         report "T10 the two lines are independent, which is why the two faults differ"
                severity note;
         ck_bit("T10 SCL low", scl, '0');
         ck_bit("T10 SDA high", sda, '1');
         scl_low <= "0000"; sda_low <= "1111"; wait for 1 ns;
         ck_bit("T10 SCL high", scl, '1');
         ck_bit("T10 SDA low", sda, '0');

         -- T11. THERE IS NO DRIVE-HIGH, and the model has no way to express one. The only
         --      input is a drive-LOW enable, so the bit a device transmits is the inverse of
         --      its drive enable -- the sign error Chapter 17.4 exists to contain.
         report "T11 the only input is a drive-LOW, so a transmitted one is a release"
                severity note;
         scl_low <= "0000"; sda_low <= "0001"; wait for 1 ns;
         ck_bit("T11 device 0 transmitting a zero drives low", sda, '0');
         sda_low <= "0000"; wait for 1 ns;
         ck_bit("T11 device 0 transmitting a one releases", sda, '1');

         -- T12. AND THE COUNTS NEVER EXCEED THE DEVICE COUNT, which catches a width mismatch
         --      between the parameter and the port.
         report "T12 the holder counts are bounded by the number of devices" severity note;
         for i in 0 to 15 loop
            scl_low <= std_logic_vector(to_unsigned(i, ND));
            sda_low <= std_logic_vector(to_unsigned(i, ND));
            wait for 1 ns;
            if to_integer(scl_h) > ND or to_integer(sda_h) > ND then
               report "  FAIL T12 a holder count exceeded " & integer'image(ND)
                      severity note;
               err := err + 1;
            end if;
         end loop;
         ck_int("T12 with everyone driving, the count is the device count",
                to_integer(scl_h), ND);

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

   end architecture sim;

6c. Execution

Every combination was compiled and run. The finish times are identical across all three languages, which is the parity check that matters: a VHDL port that agrees on every assertion but finishes at a different time has diverged somewhere a value comparison did not reach.

DesignSystemVerilogVerilog-2001VHDLFinish
i2c_line_modelPASS 12/12PASS 12/12PASS 12/1251 ns, all three

7. Mutation Testing

Twelve passing tests prove nothing on their own. Five defects were injected into the model to find out whether the bench can actually see them.

#Injected defectExpected detectionResult
M1wired-AND becomes wired-OR — the line reads low only if every device pulls it lowT2 and T3KILLED (26 failure lines)
M2inverted line polarity on SDA — driving low reads back highT1, T3, T5KILLED (29)
M3drop the still-low qualifier, so "released" alone reports as "released but low"T7KILLED (2)
M4every device gets its own copy of the line instead of the shared oneT4, T5KILLED (4)
M5population count stops one device shortT3, T12KILLED (21)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
baseline: PASS   (verified before injecting anything)
killed: 5   survived: 0   score: 5/5
restored: PASS

Two of these deserve comment.

M3 was killed by two lines, and M1 by twenty-six. That asymmetry is informative rather than a defect in the bench. M1 breaks the resolution rule itself, so every test that reads a line disagrees; M3 breaks only the feedback output in the one case where a released line is high. A mutation that produces two failure lines is still dead — but the narrow blast radius tells you T7 is the only test standing between the design and that bug, which is exactly the kind of thing worth knowing before someone "simplifies" the bench.

M4 is the one that would have shipped. Giving each device its own copy of the line is a plausible-looking simplification — it even reads as an optimisation — and it produces a model in which arbitration can never be detected, because a device would read back its own intent instead of the bus. Only T4 and T5 fail, and both are tests that look almost trivial when you write them.

8. Verification Connection — What a Monitor Can and Cannot See

The line model settles a question that shapes every I²C verification environment, and settling it here saves rediscovering it in Module 20.

Azvya Education Pvt. Ltd.VLSI Mentor
monitor_observability.sv — what the bus can and cannot tell a monitor
   // A passive I²C monitor has exactly ONE honest input per line: the resolved level.
   //
   //   virtual i2c_if.monitor vif;      // vif.scl, vif.sda -- and nothing else
   //
   // It does NOT have, and must never be written to expect:
   //
   //   vif.master_sda_drive_low         // whose intent produced this level
   //   vif.holder_count                 // how many devices are pulling
   //
   // Those exist in i2c_line_model for the BENCH's benefit and have no counterpart
   // on a real bus. A monitor that peeks at drive intent is not observing a
   // protocol; it is reading the DUT's mind, and it will pass against a DUT whose
   // intent is right and whose pad is broken.
   //
   // The consequence for transaction abstraction is sharp:
   //
   //   a monitor can report "the acknowledge slot read LOW"
   //   a monitor CANNOT report "the target acknowledged"
   //
   // because a LOW in the ACK slot is also what a stuck line looks like, and what a
   // second target on the same address looks like. Attribution requires an
   // assumption the wire does not supply. Chapter 20.x makes that assumption
   // explicit and configurable rather than implicit and wrong.

This is the observability boundary, and the model is what makes it concrete: sda_holders is in the model precisely because the bus cannot provide it, so the bench can check facts the DUT is not allowed to know.

9. FPGA and ASIC Implications

On an FPGA, the three-signal pin becomes a tri-state buffer instantiated at the top level, and drive_low drives its output enable with the data input tied to zero. The line that reads back is the buffer's input, not a copy of the output enable — and taking it from the wrong place produces a design that simulates perfectly and cannot arbitrate, which is mutation M4 realised in hardware. The readback path must also be synchronised: it is asynchronous to the system clock, and Module 2 established that the bus has no clock of its own. Two flops, and Chapter 17.6 is where the latency they add starts to matter.

On an ASIC, the same structure appears as a pad cell with an open-drain configuration, and the readback comes from the pad's input path with its own input filter — Table 10's tSP, the 50 ns spike the filter must suppress. That filter is part of the timing budget, not a detail: it delays the readback the feedback logic depends on, so Chapter 17.10's stretch detection sees a stretched clock slightly late. The pull-up itself is almost never on-die; it is a board component, and Module 2.4 sized it.

Neither realisation changes a line of the model. That is the argument for having written it.

10. Debugging — The Master That Worked Against a Model and Failed on a Board

Symptom

A newly written I²C master passes a full block-level regression against a simulation target model -- writes, reads, combined transfers, all byte counts. On its first real board it completes every transaction successfully against one device and hangs permanently on the first transaction to a second, slower device. The hang is not a timeout; the state machine is simply stopped. Power-cycling clears it until the next access to the slow device.

Root Cause

The master treats its own SCL output as the bus clock. Its phase counter advances on the internal divider tick regardless of what the line is doing, so the TIME state is driven by the system clock alone -- one of the four time bases wired to the wrong event. UM10204 section 3.1.6 is explicit that a stretched transaction 'cannot continue until the line is released HIGH again', and this master continues. The board did not expose a new bug; it supplied the first target that ever stretched. The regression could not have caught it, because the target model made the defect unobservable: a master that ignores feedback and a master that honours it are indistinguishable against a target that never withholds it.

Fix
Gate the phase counter on the line, not the count: the generator may leave its low phase only when scl_in actually reads high, which is the single comparator of section 4. That makes the TIME state advance on an SCL edge read back from the bus, as the protocol requires. Then fix the environment, which is the durable half -- give the target model a configurable stretch and turn it on in the default regression, not in one opt-in test. The test that matters is not 'does the master survive stretching' but 'does the master's bit timing stay correct across a stretch', which requires checking the drive and sample instants against the observed line rather than against the master's own count.

Three things generalise from this.

The defect was in which event advanced a state, not in any expression. The counter was right, the phase widths were right, the comparator was even present. One of the four time bases in §2 was clocked by the wrong source, and that is the characteristic failure of a master whose architecture was never explicitly decomposed — the tangle hides which event drives what.

A permissive model makes a real defect unobservable. The target that always acknowledged immediately was not a weak test of stretching; it was no test of stretching, and it silently certified the master. This is the same shape as the surviving-mutation problem: if the environment cannot withhold what the DUT is obliged to wait for, the obligation is untested.

The readback path being "wired" proved nothing. scl_in was connected and used — by the wrong block, for a different purpose. Connectivity is not compliance, and a design review that checks whether a signal exists will pass a design in which it is read by nobody who needs it.

11. Common Misconceptions

"An I²C master is a state machine." It is at least four, on four different time bases, advancing on four unrelated events. §2.

"The decomposition is a style choice." The framing generator and the bit engine have contradictory postconditions on SDA during the SCL-high phase. One block cannot hold both. §3.

"A master sets SDA." A master pulls SDA low or releases it. The transmitted bit is the inverse of the drive enable, and there is no drive-high anywhere in a conforming I²C interface. §4.

"A master knows what the bus is doing because it is driving it." It knows what it intends. What the bus is doing is the wired-AND of every device, and the only way to learn it is to read the line back. §4 and §10.

"Clock stretching and arbitration are two features needing two detectors." They are the same comparator — released and still low — on two different lines. §4, and 17.10 builds it once.

"A pin is an inout in RTL." In synthesizable RTL a pin is drive_low out and line_in in, with the tri-state at the top level. An inout in the middle of a design is neither synthesizable nor safely simulatable against a second driver. §4.

"The bus can tell you how many devices are holding a line." It cannot. T8 asserts exactly that inability, and a verification environment that assumes otherwise checks something no hardware can implement. §6b.

"If the testbench passes, the block is verified." Five injected defects were needed to establish that the bench can see anything at all, and one of them — M4 — is a plausible simplification that removes arbitration entirely. §7.

"A monitor can report that the target acknowledged." It can report that the ACK slot read low. A low there is also a stuck line and also a second device on the same address. Attribution needs an assumption the wire does not supply. §8.

"The FSM comes first." It comes last, in 17.12, derived from blocks that already exist and are already verified. Writing it first is what produces the master that gets rewritten.

12. Reason It Through

Why can the block that generates a START not also be the block that transmits data bits?

Because their obligations on SDA during the SCL-high phase are exact opposites. The bit engine must hold SDA stable while SCL is high; a START requires SDA to fall while SCL is high. A single block would have to guarantee stability and violate it in the same phase of the same wire. §3.

A master's SCL generator advances its phase counter on its internal divider tick. On which bus does this design work, and on which does it fail?

It works on any bus where no device ever stretches, which includes essentially every simulation with a permissive target model and some real boards. It fails the first time a target holds SCL low, because the master's notion of which phase the bus is in stops matching the bus. §10.

You are handed an I²C master and asked whether it can detect arbitration loss, without reading its state machine. What single question settles it?

Where does its SDA readback come from — the pad's input, or a copy of its own drive enable? If the latter, it reads back its own intent and can never detect a competitor. That is mutation M4, and it is invisible in any test where the master is the only device driving. §7 and §9.

Why does the line model expose a holder count that no real bus can provide?

So the bench can check a fact the design is not allowed to know. The model's job is to let a testbench assert that one holder and two holders are indistinguishable on the line — which is T8, and which requires the bench to know the truth the line conceals. §6b.

A master releases SCL and reads it back low. What has it learned, and what has it not?

That some other device is holding SCL down. It has not learned which device, or why — stretching by a target and clock synchronisation with a competing master produce the identical observation, and §3.1.6 and §3.1.7 are the same mechanism seen from two sides. It does not need to distinguish them: the required response, waiting, is the same. §4.

Why is "the readback signal is connected" not evidence that a master honours feedback?

Because connectivity says nothing about which block consumes it. In §10 the readback was wired and read by the error manager, while the generator that was obliged to wait on it ignored it entirely. §10.

If the four time bases are forced by the protocol, why do tangled single-FSM masters exist and sometimes work?

Because the bases can be conflated whenever the events happen to coincide — a bus with no stretching, no arbitration and no combined transfers lets the divider tick stand in for the SCL edge and the byte boundary. The design is then correct for that bus and wrong in general, and it fails when a slower device arrives. §2 and §10.

13. Understanding Check

14. Summary

Almost everything about a master's behaviour on the wire is normative, and nothing about its internal structure is. That combination is what lets this module derive an architecture and then check it against fixed obligations.

A master contains four time bases, not one state machine — TIME, BIT, BYTE and TRANSACTION — and they advance on four unrelated events: a divider tick, an SCL edge read back from the bus, a byte boundary, and a host command.

An FSM whose state must change on four unrelated events gets four sets of transitions per state, or counters bolted on. The counters are the other three machines, admitted late and untested. So the decomposition is forced by the protocol, not chosen for readability.

One collision proves it outright. 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 mean the framing sequencer cannot live inside the bit engine.

The wire comes before the master. A line is the wired-AND of every device on it, so a device learns its state only by reading it back — and two of the four time bases are driven by that readback rather than by intent.

There is no drive-high. The transmitted bit is the inverse of the drive enable, which is the most common sign error in a first master.

"Released and still low" is one comparator that implements two protocol features — clock stretching on SCL, arbitration loss on SDA. A master that builds them as two features has written the same logic twice.

A synthesizable pin is three signals, not an inout: drive low, read back, and a tri-state at the top level whose readback must come from the pad's input rather than a copy of the drive enable.

The bus cannot count its holders. One device holding a line and two are identical on the line, and the model exposes a holder count precisely so a bench can assert that inability.

Twelve tests and five injected defects establish the bench can see anything at all — and the most dangerous mutant, M4, is a plausible simplification that removes arbitration detection entirely while looking like a tidy-up.

A permissive environment certifies a non-compliant master. A target model that never stretches makes the difference between honouring and ignoring feedback unobservable, which is how a master that treats its own output as the bus clock passes a full regression and hangs on its first real board.

15. What Comes Next

The architecture is derived and the wire is modelled. Everything after this builds one block of Figure 1 and verifies it against that wire.

Chapter 17.2 starts at the left edge, with the part the specification says nothing about at all: what software actually asks for. It is the only block in the module with no normative content, and that turns out to make it harder rather than easier — a register map has to be chosen, and the choice determines whether a driver can express a combined transfer at all.

It also draws the distinction that trips up integration: a board-level bus and an on-chip register bus are not the same kind of thing, and a master's host interface faces the second while its pins face the first.

Continue learning