Skip to content
VLSI Mentor

I²C · Module 10

Repeated START in Practice — Why Not STOP Then START

Two sequences that look nearly identical in a driver's source are completely different on the wire. This chapter measures the difference, builds the passive monitor that tells them apart from two wires alone, and names exactly what a STOP costs on a shared bus.

Chapter 10.1 asserted that the junction between the two phases of a combined transaction must be a repeated START. This chapter earns that claim.

The two candidate sequences are, in a driver's source, about four characters apart:

Azvya Education Pvt. Ltd.VLSI Mentor
the two junctions, as a driver writes them
   i2c_write(dev, ptr);            i2c_write(dev, ptr);
   i2c_restart();                  i2c_stop();
   i2c_read(dev, buf, n);          i2c_start();
                                   i2c_read(dev, buf, n);

Both put identical bytes on the wire in identical order. Both work perfectly on a bench with one master. And one of them is correct while the other has a failure mode that is intermittent, bus-wide, and essentially impossible to reproduce deliberately.

So the useful thing is not the advice — everyone has heard "use a repeated START". It is knowing exactly what the difference is on the wire, exactly what it costs, and how to see it in a capture.

1. The Difference Is One Framing Event

Chapter 5.1 established the rule that makes this measurable: an SDA edge while SCL is high is reserved for framing. Everything follows from it.

eventwire signatureeffect on ownership
START / repeated STARTSDA falls while SCL is hightakes the bus, or keeps it
STOPSDA rises while SCL is highreleases the bus

A START and a repeated START are the same electrical event. There is no difference whatsoever in what the wires do — the distinction is entirely contextual:

If a transfer is already open, an SDA fall while SCL is high is a repeated START and the bus was never released. If no transfer is open, the same edge is a fresh START and the bus had been free.

That is why the monitor in §5 has to track state to classify one. It also means the two sequences differ by exactly one thing: whether a STOP appears between the phases. Not the bytes, not the addresses, not the acknowledges — one framing event.

Note 4 is the reason this works at all. Every device on the bus, on seeing an Sr, resets its bus logic and expects an address — so the repeated START unambiguously re-opens the addressing phase for everybody, with no device left believing the previous phase is still running. Chapter 9.2 §4 covered the one deliberate exception: the register pointer survives, because note 1 requires it to.

2. The Two Sequences, Side by Side

Repeated START versus STOP-then-START — the same bytes, one framing event apart

10 cycles
Ten intervals at byte resolution. The first row shows the correct sequence: START, address with write, pointer, repeated START, address with read, two data bytes, STOP. The second row shows the same bytes but with a STOP where the repeated START was, followed by an idle gap and a fresh START, which pushes the remaining bytes later. A third row marks whether the bus is owned or free in each interval for the second sequence, showing free intervals where the first sequence has none.identical in bothidentical in bothbus FREE: exposedbus FREE: exposedsecond transactionsecond transactionthe only difference: Sr or Pthe only difference: Sr orPthe exposure windowthe exposure windowthe bus has to be re-takenthe bus has to be re-takencorrectS0xA00x12Sr0xA1d0d1PPPbrokenS0xA00x12PidleS0xA1d0d1Pbus ownedYYYNNYYYYNt0t1t2t3t4t5t6t7t8t9
The same access twice. The upper rows are the correct combined transaction; the lower rows use a STOP and a fresh START. Byte for byte identical — the only difference is the framing event at interval 3, and the gap it opens.

The bus owned row is the whole argument. In the correct sequence it never reads N until the final STOP. In the broken one there are intervals in the middle where the answer is no — and during those intervals, any other master on the bus is not merely able to start a transfer, it is entitled to. The bus is free; that is what free means.

Three costs follow, and they are worth separating because only one of them is about correctness.

Time — real but minor. A STOP and a START have setup and hold requirements, and between them the mandatory bus-free interval tBUF must elapse (Chapter 5.5). Plus a whole extra address byte: nine pulses. At 100 kHz that is roughly 90 µs plus the framing. Measurable, rarely decisive.

Slave state — usually survivable, sometimes not. Note 4 says devices reset their bus logic on an S or Sr. Many devices also discard the register pointer on a STOP, because a STOP ends the transaction in every sense the protocol recognises. If yours does, the second phase reads from wherever the pointer defaults to, and the failure is deterministic and immediate — which, perversely, makes it the good outcome, because you find it on the first run.

The exposure window — the one that matters. Another master takes the free bus, addresses the same device, writes its own pointer, and completes. Your read phase then returns data from its location. Every byte is acknowledged, the framing is clean, the transaction reports success, and the data is plausible and wrong.

3. Reading It in a Capture

The diagnostic is structural and takes one pass. From Chapter 7.5 §2's method, on pass 1 you already scan for SDA edges while SCL is high. Classify them and count:

One logical access should produce ONE start and k repeated starts. If it produced two starts, the bus was released.

That is it. A write-then-read should show S, Sr, P — one fresh start, one repeated start, one stop. The broken version shows S, P, S, P — two fresh starts and no repeated start at all.

what you seewhat it was
S … Sr … Pone combined transaction, bus held throughout
S … P … S … Ptwo transactions; the bus was released in between
S … Sr … Sr … Pa three-phase transaction, still held throughout
S … P onlya single-phase transfer, or an abort (Chapter 10.1 §5)

Two refinements make this reliable in practice.

Count starts, not stops. A missing repeated START is easy to overlook — it is one edge in a dense capture. Two fresh starts where you expected one is much more visible, because it means the capture contains two frames where the source contains one function call.

Measure the gap. Between the STOP and the next START, the bus is idle. That interval is the exposure window, and its length is worth knowing: a driver that releases the bus for 2 µs and one that releases it for 2 ms have the same bug and wildly different probabilities of being bitten by it. The monitor in §5 measures it.

4. What the Monitor Can and Cannot Tell You

Before the code, a limit that is genuinely important and is the reason Chapter 10.3 exists.

A monitor reads the wire. The wire records events, not intentions.

On the wire, S … P … S … P is two transactions. Not "a combined transaction that was done wrongly" — two transactions, and the first one is over in every sense the protocol recognises: the bus was released, every device reset its bus logic, and any master was free to begin. There is no field, no flag, and no timing signature that distinguishes:

  • a driver that wrongly released the bus mid-access, from
  • two separate accesses that happened to be adjacent.

So the monitor below reports facts: how many fresh starts, how many repeated starts, how many stops, and how long the bus was free. It does not report "you have a bug", because it cannot know. Turning those facts into a verdict requires one extra input — what the master believed it was doing — and that is the next chapter.

This is not a weakness of the design; it is a property of the bus, and it is worth internalising because it recurs. Chapter 10.1 §5 noted that an abort and a mid-transaction release produce the same wire event too. Whenever intent matters, the wire is not enough.

5. The Monitor in Three Languages

The monitor detects framing from the two wires using only the rule in §1, classifies each START by whether a transfer was already open, and times the gaps.

outputwhat it reports
start_det, restart_det, stop_detframing events, classified
in_transferbetween an S/Sr and the next P
junction_kindhow the current addressing was reached: repeated START, or fresh
bus_was_freepulse: a fresh START re-took a bus that had been released
free_cyclesthe gap in progress
last_free_cyclesthe measured length of the last gap — the exposure window
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_phase_junction_monitor.sv — a passive monitor that classifies the junction from the two wires
   // A PASSIVE bus monitor that classifies the JUNCTION between two phases of a transfer
   // and measures what a release costs. It drives nothing.
   //
   // The two sequences this chapter compares look almost identical in a driver's source
   // and are completely different on the wire:
   //
   //   REPEATED START            S ... A  [Sr]  addr+R ...           bus NEVER released
   //   STOP then START           S ... A  [P]   ......  [S] addr+R   bus released
   //
   // And they are distinguishable from the two wires alone, using nothing but the framing
   // rule of Chapter 5.1 -- an SDA edge while SCL is HIGH is reserved for framing:
   //
   //   SDA falling while SCL high  ->  a START.  If a transfer is already open it is a
   //                                   REPEATED start, and the bus was never released.
   //   SDA rising  while SCL high  ->  a STOP.   The bus is now free, and every device
   //                                   on it is entitled to start a transfer.
   //
   // So "was the bus released between the phases" is not a matter of intent or of reading
   // the driver's source. It is a fact on the wire, and this block reads it.
   //
   // The measurement that matters is free_cycles: how long the bus sat idle between a STOP
   // and the next START. That interval is the EXPOSURE WINDOW -- the time during which any
   // other master on the bus could legitimately have begun its own transfer and, in the
   // register-pointer pattern of Chapter 10.1, changed the pointer this master had just
   // written. A repeated START makes that window exactly zero, which is the whole reason
   // the specification's combined format exists.
   //
   // ONE LIMIT IS WORTH STATING PLAINLY, because it is the bridge to Chapter 10.3.
   // This block reports FACTS about the wire and cannot report INTENT. A STOP followed by
   // a START is, on the wire, exactly two separate transactions -- and that is precisely
   // what it IS. There is no field, flag or timing that distinguishes "a driver that
   // wrongly released the bus mid-transaction" from "two transactions that happened to be
   // adjacent", because after the STOP the first transaction is over in every sense the
   // protocol recognises. So the diagnostic here is structural rather than semantic:
   //
   //     one logical access should produce ONE start and k RESTARTS.
   //     If it produced two STARTS, the bus was released, whatever the driver intended.
   //
   // Correlating that with what a master believed it was doing needs a second input, and
   // Chapter 10.3 supplies it.
   module i2c_phase_junction_monitor #(
       parameter int CNT_W = 16
   )(
       input  logic clk,
       input  logic rst_n,
       // The two wires, already synchronised to clk by the input stage of Module 4.
       input  logic sda_in,
       input  logic scl_in,

       // ---- framing, detected from the wire ----
       output logic start_det,        // pulse: a START (first of a transfer)
       output logic restart_det,      // pulse: a REPEATED START -- bus retained
       output logic stop_det,         // pulse: a STOP -- bus released
       output logic in_transfer,      // a transfer is open: between an S/Sr and the next P

       // ---- junction classification ----
       // How the most recent addressing was reached: by a repeated START, with the bus
       // retained, or by a fresh START after the bus had been free.
       output logic [1:0] junction_kind,   // 0 = none yet, 1 = repeated START, 2 = fresh
       output logic       bus_was_free,    // pulse: a fresh START re-took a released bus

       // ---- the cost of a release ----
       // Cycles the bus spent idle between a STOP and the next START. In a combined
       // transaction this is the window another master could have used.
       output logic [CNT_W-1:0] free_cycles,
       output logic [CNT_W-1:0] last_free_cycles,  // the measurement of the last gap
       output logic [CNT_W-1:0] n_starts,
       output logic [CNT_W-1:0] n_restarts,
       output logic [CNT_W-1:0] n_stops
   );
       localparam logic [1:0] JK_NONE    = 2'd0;
       localparam logic [1:0] JK_RESTART = 2'd1;
       localparam logic [1:0] JK_FRESH   = 2'd2;

       logic sda_q, scl_q;
       logic sda_fall, sda_rise, scl_stable_high;

       assign sda_fall = sda_q && !sda_in;
       assign sda_rise = !sda_q && sda_in;

       // SCL must be high ACROSS the SDA edge, not merely high after it. Requiring both
       // the previous and the current sample to be high is what stops a coincident SCL
       // rise from being misread as framing -- the defect Module 4's mutation suite found
       // and the reason that chapter's detector uses both terms.
       assign scl_stable_high = scl_q && scl_in;

       // A STOP has just released the bus, so the gap timer runs until the next START.
       // "Idle" means both lines released: an undriven bus is high on both wires.
       logic gap_running;
       logic bus_idle;
       assign bus_idle = sda_in && scl_in;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               sda_q            <= 1'b1;   // an idle bus is high on both wires
               scl_q            <= 1'b1;
               start_det        <= 1'b0;
               restart_det      <= 1'b0;
               stop_det         <= 1'b0;
               in_transfer      <= 1'b0;
               junction_kind    <= JK_NONE;
               bus_was_free     <= 1'b0;
               free_cycles      <= '0;
               last_free_cycles <= '0;
               n_starts         <= '0;
               n_restarts       <= '0;
               n_stops          <= '0;
               gap_running      <= 1'b0;
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               start_det   <= 1'b0;
               restart_det <= 1'b0;
               stop_det     <= 1'b0;
               bus_was_free <= 1'b0;

               if (sda_fall && scl_stable_high) begin
                   // A START. Whether it is a plain one or a REPEATED one depends entirely
                   // on whether a transfer was already open -- there is no difference in
                   // the electrical event itself, which is why a monitor must track state
                   // to classify it.
                   if (in_transfer) begin
                       restart_det   <= 1'b1;
                       n_restarts    <= n_restarts + 1'b1;
                       junction_kind <= JK_RESTART;
                   end else begin
                       start_det     <= 1'b1;
                       n_starts      <= n_starts + 1'b1;
                       junction_kind <= JK_FRESH;
                       // A fresh START that follows a STOP re-takes a bus that was
                       // genuinely free. The gap timer was running, so its value is the
                       // window during which any other master could have taken it.
                       if (gap_running) begin
                           bus_was_free     <= 1'b1;
                           last_free_cycles <= free_cycles;
                       end
                   end
                   in_transfer <= 1'b1;
                   gap_running <= 1'b0;
                   free_cycles <= '0;
               end else if (sda_rise && scl_stable_high) begin
                   // A STOP. The bus is released from this instant, and the gap timer
                   // starts -- it is measuring the window, not waiting to see if anyone
                   // uses it.
                   stop_det    <= 1'b1;
                   n_stops     <= n_stops + 1'b1;
                   in_transfer <= 1'b0;
                   gap_running <= 1'b1;
                   free_cycles <= '0;
               end else if (gap_running && bus_idle) begin
                   free_cycles <= free_cycles + 1'b1;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_phase_junction_monitor_tb.sv — both sequences built from identical primitives, plus the gap measurement
   `timescale 1ns/1ps
   // The testbench drives the two WIRES, not an abstract command interface, because the
   // claim under test is about what the wire shows. Both sequences are built from the same
   // primitives so the only difference between them is the junction.
   module i2c_phase_junction_monitor_tb;
       localparam int CNT_W = 16;

       logic clk = 1'b0;
       always #5 clk = ~clk;

       logic rst_n = 1'b0;
       logic sda_in = 1'b1, scl_in = 1'b1;    // an idle bus is high on both wires

       logic start_det, restart_det, stop_det, in_transfer, bus_was_free;
       logic [1:0] junction_kind;
       logic [CNT_W-1:0] free_cycles, last_free_cycles;
       logic [CNT_W-1:0] n_starts, n_restarts, n_stops;

       localparam logic [1:0] JK_NONE = 2'd0, JK_RESTART = 2'd1, JK_FRESH = 2'd2;

       int errors = 0;

       i2c_phase_junction_monitor #(.CNT_W(CNT_W)) dut (.*);

       initial begin #400000; $display("FAIL: watchdog expired"); $finish; end

       // ---- bus primitives, built strictly from the framing rules of Module 5 ----------
       // A START: SDA falls while SCL is HIGH.
       task automatic bus_start();
           sda_in = 1'b1; scl_in = 1'b1; @(negedge clk); @(negedge clk);
           sda_in = 1'b0;                @(negedge clk);   // the falling edge, SCL high
           scl_in = 1'b0;                @(negedge clk);   // SCL drops: the transfer begins
       endtask

       // A STOP: SDA rises while SCL is HIGH.
       task automatic bus_stop();
           sda_in = 1'b0; scl_in = 1'b0; @(negedge clk);
           scl_in = 1'b1;                @(negedge clk);   // SCL up with SDA still low
           sda_in = 1'b1;                @(negedge clk);   // the rising edge, SCL high
       endtask

       // A data bit: SDA changes only while SCL is LOW, then one SCL pulse.
       task automatic bus_bit(input logic v);
           scl_in = 1'b0; @(negedge clk);
           sda_in = v;    @(negedge clk);
           scl_in = 1'b1; @(negedge clk);
           scl_in = 1'b0; @(negedge clk);
       endtask

       // One byte and its acknowledge slot -- nine bits, exactly as Chapter 7.1 requires.
       task automatic bus_byte(input logic [7:0] v, input logic ninth);
           for (int i = 7; i >= 0; i--) bus_bit(v[i]);
           bus_bit(ninth);
       endtask

       // Let the bus sit IDLE for n cycles. Both wires released, which is what an
       // unowned bus looks like.
       task automatic bus_idle_for(input int n);
           sda_in = 1'b1; scl_in = 1'b1;
           repeat (n) @(negedge clk);
       endtask

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

           // ================================================================
           // SEQUENCE 1 — the CORRECT combined transaction: repeated START.
           //     S  addr+W  A  ptr  A  Sr  addr+R  A  data  N  P
           // ================================================================
           bus_start();
           bus_byte(8'hA0, 1'b0);          // addr+W, slave ACKs (SDA low in the 9th)
           bus_byte(8'h12, 1'b0);          // the pointer, ACKed
           bus_start();                    // THE JUNCTION -- a repeated START
           bus_byte(8'hA1, 1'b0);          // addr+R, ACKed
           bus_byte(8'h5A, 1'b1);          // data, master NACKs (SDA high in the 9th)
           bus_stop();
           bus_idle_for(10);

           // THE structural diagnostic. One logical access, ONE start: a repeated START
           // does not release the bus, so there is nothing to re-take.
           if (n_starts !== 16'd1) begin
               $display("FAIL: a held transaction produced %0d STARTs -- one access, one START",
                        n_starts); errors++; end
           if (n_restarts !== 16'd1) begin
               $display("FAIL: counted %0d repeated STARTs, expected 1", n_restarts); errors++; end
           if (n_stops !== 16'd1) begin
               $display("FAIL: counted %0d STOPs, expected 1", n_stops); errors++; end
           if (junction_kind !== JK_RESTART) begin
               $display("FAIL: junction_kind = %0d, expected %0d (repeated START)",
                        junction_kind, JK_RESTART); errors++; end
           // No gap to measure, and therefore no window in which another master could
           // have interposed.
           if (bus_was_free !== 1'b0) begin
               $display("FAIL: a repeated START reported the bus as having been free");
               errors++; end
           if (in_transfer !== 1'b0) begin
               $display("FAIL: still in a transfer after the STOP"); errors++; end

           // ================================================================
           // SEQUENCE 2 — the SAME ACCESS written as STOP then START.
           //     S  addr+W  A  ptr  A  P   [bus free]   S  addr+R  A  data  N  P
           // Byte for byte identical. Only the junction differs.
           // ================================================================
           begin
               logic [CNT_W-1:0] sr_before, s_before;
               sr_before = n_restarts; s_before = n_starts;

               bus_start();
               bus_byte(8'hA0, 1'b0);
               bus_byte(8'h12, 1'b0);
               bus_stop();                 // THE JUNCTION -- the bus is RELEASED here
               bus_idle_for(24);           // the mandatory bus-free interval, and then some
               bus_start();                // ... and re-taken
               bus_byte(8'hA1, 1'b0);
               bus_byte(8'h5A, 1'b1);
               bus_stop();
               bus_idle_for(10);

               // THE structural difference: no repeated START at all, and TWO fresh
               // STARTs where one logical access should have produced one.
               if (n_restarts !== sr_before) begin
                   $display("FAIL: a STOP-then-START junction produced a repeated START");
                   errors++; end
               if (n_starts !== s_before + 16'd2) begin
                   $display("FAIL: a released junction produced %0d fresh STARTs, expected 2",
                            n_starts - s_before); errors++; end
           end
           if (junction_kind !== JK_FRESH) begin
               $display("FAIL: junction_kind = %0d, expected %0d (fresh START)",
                        junction_kind, JK_FRESH); errors++; end
           // THE cost. The bus was measurably free, and every cycle of it is a window in
           // which another master could have taken the bus and changed the pointer.
           if (last_free_cycles === '0) begin
               $display("FAIL: a release junction measured ZERO free cycles"); errors++; end
           if (last_free_cycles < 16'd20) begin
               $display("FAIL: the gap measured %0d cycles, expected at least 20",
                        last_free_cycles); errors++; end

           // ================================================================
           // 3 — the gap is MEASURED, not assumed. A longer idle must read longer.
           // ================================================================
           begin
               logic [CNT_W-1:0] short_gap, long_gap;
               bus_start(); bus_byte(8'hA0, 1'b0); bus_stop();
               bus_idle_for(12);
               bus_start(); bus_byte(8'hA1, 1'b0); bus_stop();
               short_gap = last_free_cycles;
               bus_idle_for(60);
               bus_start(); bus_byte(8'hA1, 1'b0); bus_stop();
               long_gap = last_free_cycles;
               if (!(long_gap > short_gap)) begin
                   $display("FAIL: a 60-cycle gap measured %0d, a 12-cycle gap measured %0d",
                            long_gap, short_gap); errors++; end
               if (short_gap < 16'd8 || short_gap > 16'd20) begin
                   $display("FAIL: the 12-cycle gap measured %0d, expected roughly 12",
                            short_gap); errors++; end
           end

           // ================================================================
           // 3b — free_cycles is the RUNNING gap, and there is no gap during a transfer.
           //      A consumer that read it mid-transfer must see zero, not the previous
           //      gap's length left over.
           // ================================================================
           bus_start();
           if (free_cycles !== '0) begin
               $display("FAIL: free_cycles reads %0d inside a transfer -- a stale measurement",
                        free_cycles); errors++; end
           bus_byte(8'hA0, 1'b0);
           if (free_cycles !== '0) begin
               $display("FAIL: free_cycles reads %0d mid-byte, expected 0", free_cycles);
               errors++; end
           bus_stop();
           bus_idle_for(10);

           // ================================================================
           // 3c — the gap measures time the bus was ACTUALLY FREE. If somebody is holding
           //      SCL low after the STOP, the bus is not available, and those cycles must
           //      not be counted as though another master could have used them.
           // ================================================================
           begin
               logic [CNT_W-1:0] gap_with_hold;
               bus_start(); bus_byte(8'hA0, 1'b0); bus_stop();
               // SCL held LOW by somebody -- the bus is busy, not free
               sda_in = 1'b1; scl_in = 1'b0;
               repeat (40) @(negedge clk);
               // now genuinely released
               bus_idle_for(12);
               bus_start(); bus_byte(8'hA1, 1'b0); bus_stop();
               gap_with_hold = last_free_cycles;
               // 40 held cycles plus 12 free ones. Only the free ones are a window.
               if (gap_with_hold >= 16'd40) begin
                   $display("FAIL: the gap measured %0d cycles, counting the 40 in which SCL was HELD LOW",
                            gap_with_hold); errors++; end
               if (gap_with_hold === '0) begin
                   $display("FAIL: the genuinely free cycles were not counted at all"); errors++; end
           end

           // ================================================================
           // 4 — THREE phases joined by two repeated STARTs. The bus is held across the
           //     whole thing, so a three-phase transaction is no more exposed than a
           //     two-phase one -- the exposure is a property of the JUNCTIONS, not of
           //     the length.
           // ================================================================
           begin
               logic [CNT_W-1:0] s_before, sr_before;
               s_before = n_starts; sr_before = n_restarts;
               bus_start();
               bus_byte(8'hA0, 1'b0); bus_byte(8'h12, 1'b0);
               bus_start();                             // junction 1
               bus_byte(8'hA1, 1'b0); bus_byte(8'h5A, 1'b0);
               bus_start();                             // junction 2
               bus_byte(8'hA1, 1'b0); bus_byte(8'h6B, 1'b1);
               bus_stop();
               bus_idle_for(10);
               // Three phases, ONE start, TWO repeated STARTs. Exposure is a property of
               // the junctions, not of the transaction's length: a three-phase held
               // transaction is exactly as exposed as a two-phase one, namely not at all.
               if (n_starts !== s_before + 16'd1) begin
                   $display("FAIL: a three-phase held transaction produced %0d STARTs, expected 1",
                            n_starts - s_before); errors++; end
               if (n_restarts !== sr_before + 16'd2) begin
                   $display("FAIL: a three-phase transaction produced %0d repeated STARTs, expected 2",
                            n_restarts - sr_before); errors++; end
               if (junction_kind !== JK_RESTART) begin
                   $display("FAIL: the last junction of a held transaction was not an Sr");
                   errors++; end
           end

           // ================================================================
           // 5 — an SDA edge while SCL is LOW is DATA, never framing. This is the check
           //     that stops the monitor from inventing junctions inside a byte.
           // ================================================================
           begin
               logic [CNT_W-1:0] s_before, p_before;
               s_before = n_starts; p_before = n_stops;
               // a byte whose bits move SDA up and down many times, all with SCL low
               bus_start();
               bus_byte(8'hAA, 1'b0);       // 1010 1010 -- eight SDA transitions
               bus_byte(8'h55, 1'b0);       // 0101 0101 -- eight more
               bus_stop();
               bus_idle_for(10);
               // exactly one S and one P from the framing above, and nothing from the data
               if (n_starts !== s_before + 16'd1) begin
                   $display("FAIL: data transitions produced %0d spurious STARTs",
                            n_starts - s_before - 1); errors++; end
               if (n_stops !== p_before + 16'd1) begin
                   $display("FAIL: data transitions produced %0d spurious STOPs",
                            n_stops - p_before - 1); errors++; end
           end

           // ================================================================
           // 6 — a long IDLE with no framing at all must not open a transfer or count
           //     anything. The bus spends most of its life here.
           // ================================================================
           begin
               logic [CNT_W-1:0] s_before, sr_before, p_before;
               s_before = n_starts; sr_before = n_restarts; p_before = n_stops;
               bus_idle_for(80);
               if (n_starts !== s_before || n_restarts !== sr_before || n_stops !== p_before) begin
                   $display("FAIL: an idle bus produced framing events"); errors++; end
               if (in_transfer !== 1'b0) begin
                   $display("FAIL: an idle bus opened a transfer"); errors++; end
           end

           if (errors == 0)
               $display("PASS: Sr and STOP-then-START distinguished from the wire, the release window measured, data edges never mistaken for framing");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_phase_junction_monitor.v — the same monitor in Verilog-2001
   // A PASSIVE bus monitor  (Verilog-2001)
   // that classifies the JUNCTION between two phases of a transfer
   // and measures what a release costs. It drives nothing.
   //
   // The two sequences this chapter compares look almost identical in a driver's source
   // and are completely different on the wire:
   //
   //   REPEATED START            S ... A  [Sr]  addr+R ...           bus NEVER released
   //   STOP then START           S ... A  [P]   ......  [S] addr+R   bus released
   //
   // And they are distinguishable from the two wires alone, using nothing but the framing
   // rule of Chapter 5.1 -- an SDA edge while SCL is HIGH is reserved for framing:
   //
   //   SDA falling while SCL high  ->  a START.  If a transfer is already open it is a
   //                                   REPEATED start, and the bus was never released.
   //   SDA rising  while SCL high  ->  a STOP.   The bus is now free, and every device
   //                                   on it is entitled to start a transfer.
   //
   // So "was the bus released between the phases" is not a matter of intent or of reading
   // the driver's source. It is a fact on the wire, and this block reads it.
   //
   // The measurement that matters is free_cycles: how long the bus sat idle between a STOP
   // and the next START. That interval is the EXPOSURE WINDOW -- the time during which any
   // other master on the bus could legitimately have begun its own transfer and, in the
   // register-pointer pattern of Chapter 10.1, changed the pointer this master had just
   // written. A repeated START makes that window exactly zero, which is the whole reason
   // the specification's combined format exists.
   //
   // ONE LIMIT IS WORTH STATING PLAINLY, because it is the bridge to Chapter 10.3.
   // This block reports FACTS about the wire and cannot report INTENT. A STOP followed by
   // a START is, on the wire, exactly two separate transactions -- and that is precisely
   // what it IS. There is no field, flag or timing that distinguishes "a driver that
   // wrongly released the bus mid-transaction" from "two transactions that happened to be
   // adjacent", because after the STOP the first transaction is over in every sense the
   // protocol recognises. So the diagnostic here is structural rather than semantic:
   //
   //     one logical access should produce ONE start and k RESTARTS.
   //     If it produced two STARTS, the bus was released, whatever the driver intended.
   //
   // Correlating that with what a master believed it was doing needs a second input, and
   // Chapter 10.3 supplies it.
   module i2c_phase_junction_monitor #(
       parameter CNT_W = 16
   )(
       input  wire  clk,
       input  wire  rst_n,
       // The two wires, already synchronised to clk by the input stage of Module 4.
       input  wire  sda_in,
       input  wire  scl_in,

       // ---- framing, detected from the wire ----
       output reg   start_det,        // pulse: a START (first of a transfer)
       output reg   restart_det,      // pulse: a REPEATED START -- bus retained
       output reg   stop_det,         // pulse: a STOP -- bus released
       output reg   in_transfer,      // a transfer is open: between an S/Sr and the next P

       // ---- junction classification ----
       // How the most recent addressing was reached: by a repeated START, with the bus
       // retained, or by a fresh START after the bus had been free.
       output reg   [1:0] junction_kind,   // 0 = none yet, 1 = repeated START, 2 = fresh
       output reg         bus_was_free,    // pulse: a fresh START re-took a released bus

       // ---- the cost of a release ----
       // Cycles the bus spent idle between a STOP and the next START. In a combined
       // transaction this is the window another master could have used.
       output reg   [CNT_W-1:0] free_cycles,
       output reg   [CNT_W-1:0] last_free_cycles,  // the measurement of the last gap
       output reg   [CNT_W-1:0] n_starts,
       output reg   [CNT_W-1:0] n_restarts,
       output reg   [CNT_W-1:0] n_stops
   );
       localparam [1:0] JK_NONE    = 2'd0;
       localparam [1:0] JK_RESTART = 2'd1;
       localparam [1:0] JK_FRESH   = 2'd2;

       reg  sda_q, scl_q;
       wire sda_fall, sda_rise, scl_stable_high;

       assign sda_fall = sda_q && !sda_in;
       assign sda_rise = !sda_q && sda_in;

       // SCL must be high ACROSS the SDA edge, not merely high after it. Requiring both
       // the previous and the current sample to be high is what stops a coincident SCL
       // rise from being misread as framing -- the defect Module 4's mutation suite found
       // and the reason that chapter's detector uses both terms.
       assign scl_stable_high = scl_q && scl_in;

       // A STOP has just released the bus, so the gap timer runs until the next START.
       // "Idle" means both lines released: an undriven bus is high on both wires.
       reg  gap_running;
       wire bus_idle;
       assign bus_idle = sda_in && scl_in;

       always @(posedge clk) begin
           if (!rst_n) begin
               sda_q            <= 1'b1;   // an idle bus is high on both wires
               scl_q            <= 1'b1;
               start_det        <= 1'b0;
               restart_det      <= 1'b0;
               stop_det         <= 1'b0;
               in_transfer      <= 1'b0;
               junction_kind    <= JK_NONE;
               bus_was_free     <= 1'b0;
               free_cycles      <= {CNT_W{1'b0}};
               last_free_cycles <= {CNT_W{1'b0}};
               n_starts         <= {CNT_W{1'b0}};
               n_restarts       <= {CNT_W{1'b0}};
               n_stops          <= {CNT_W{1'b0}};
               gap_running      <= 1'b0;
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               start_det   <= 1'b0;
               restart_det <= 1'b0;
               stop_det     <= 1'b0;
               bus_was_free <= 1'b0;

               if (sda_fall && scl_stable_high) begin
                   // A START. Whether it is a plain one or a REPEATED one depends entirely
                   // on whether a transfer was already open -- there is no difference in
                   // the electrical event itself, which is why a monitor must track state
                   // to classify it.
                   if (in_transfer) begin
                       restart_det   <= 1'b1;
                       n_restarts    <= n_restarts + 1'b1;
                       junction_kind <= JK_RESTART;
                   end else begin
                       start_det     <= 1'b1;
                       n_starts      <= n_starts + 1'b1;
                       junction_kind <= JK_FRESH;
                       // A fresh START that follows a STOP re-takes a bus that was
                       // genuinely free. The gap timer was running, so its value is the
                       // window during which any other master could have taken it.
                       if (gap_running) begin
                           bus_was_free     <= 1'b1;
                           last_free_cycles <= free_cycles;
                       end
                   end
                   in_transfer <= 1'b1;
                   gap_running <= 1'b0;
                   free_cycles <= {CNT_W{1'b0}};
               end else if (sda_rise && scl_stable_high) begin
                   // A STOP. The bus is released from this instant, and the gap timer
                   // starts -- it is measuring the window, not waiting to see if anyone
                   // uses it.
                   stop_det    <= 1'b1;
                   n_stops     <= n_stops + 1'b1;
                   in_transfer <= 1'b0;
                   gap_running <= 1'b1;
                   free_cycles <= {CNT_W{1'b0}};
               end else if (gap_running && bus_idle) begin
                   free_cycles <= free_cycles + 1'b1;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_phase_junction_monitor_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // The testbench drives the two WIRES, not an abstract command interface, because the
   // claim under test is about what the wire shows. Both sequences are built from the same
   // primitives so the only difference between them is the junction.
   module i2c_phase_junction_monitor_tb;   // Verilog-2001
       localparam CNT_W = 16;

       reg clk = 1'b0;
       always #5 clk = ~clk;

       reg rst_n = 1'b0;
       reg sda_in = 1'b1, scl_in = 1'b1;    // an idle bus is high on both wires

       wire start_det, restart_det, stop_det, in_transfer, bus_was_free;
       wire [1:0] junction_kind;
       wire [CNT_W-1:0] free_cycles, last_free_cycles;
       wire [CNT_W-1:0] n_starts, n_restarts, n_stops;

       localparam [1:0] JK_NONE = 2'd0, JK_RESTART = 2'd1, JK_FRESH = 2'd2;

       integer errors = 0;
       integer i;
       reg [CNT_W-1:0] sr_before, s_before, p_before, short_gap, long_gap, gap_with_hold;

       i2c_phase_junction_monitor #(.CNT_W(CNT_W)) dut (
           .clk(clk), .rst_n(rst_n), .sda_in(sda_in), .scl_in(scl_in),
           .start_det(start_det), .restart_det(restart_det), .stop_det(stop_det),
           .in_transfer(in_transfer), .junction_kind(junction_kind),
           .bus_was_free(bus_was_free), .free_cycles(free_cycles),
           .last_free_cycles(last_free_cycles), .n_starts(n_starts),
           .n_restarts(n_restarts), .n_stops(n_stops));

       initial begin #400000; $display("FAIL: watchdog expired"); $finish; end

       // ---- bus primitives, built strictly from the framing rules of Module 5 ----------
       // A START: SDA falls while SCL is HIGH.
       task bus_start; begin
           sda_in = 1'b1; scl_in = 1'b1; @(negedge clk); @(negedge clk);
           sda_in = 1'b0;                @(negedge clk);   // the falling edge, SCL high
           scl_in = 1'b0;                @(negedge clk);   // SCL drops: the transfer begins
       end endtask

       // A STOP: SDA rises while SCL is HIGH.
       task bus_stop; begin
           sda_in = 1'b0; scl_in = 1'b0; @(negedge clk);
           scl_in = 1'b1;                @(negedge clk);   // SCL up with SDA still low
           sda_in = 1'b1;                @(negedge clk);   // the rising edge, SCL high
       end endtask

       // A data bit: SDA changes only while SCL is LOW, then one SCL pulse.
       task bus_bit;
           input v;
           begin
               scl_in = 1'b0; @(negedge clk);
               sda_in = v;    @(negedge clk);
               scl_in = 1'b1; @(negedge clk);
               scl_in = 1'b0; @(negedge clk);
           end
       endtask

       // One byte and its acknowledge slot -- nine bits, exactly as Chapter 7.1 requires.
       task bus_byte;
           input [7:0] v;
           input ninth;
           begin
               for (i = 7; i >= 0; i = i - 1) bus_bit(v[i]);
               bus_bit(ninth);
           end
       endtask

       // Let the bus sit IDLE for n cycles. Both wires released, which is what an
       // unowned bus looks like.
       task bus_idle_for;
           input integer n;
           begin
               sda_in = 1'b1; scl_in = 1'b1;
               repeat (n) @(negedge clk);
           end
       endtask

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

           // ================================================================
           // SEQUENCE 1 — the CORRECT combined transaction: repeated START.
           //     S  addr+W  A  ptr  A  Sr  addr+R  A  data  N  P
           // ================================================================
           bus_start;
           bus_byte(8'hA0, 1'b0);          // addr+W, slave ACKs (SDA low in the 9th)
           bus_byte(8'h12, 1'b0);          // the pointer, ACKed
           bus_start;                    // THE JUNCTION -- a repeated START
           bus_byte(8'hA1, 1'b0);          // addr+R, ACKed
           bus_byte(8'h5A, 1'b1);          // data, master NACKs (SDA high in the 9th)
           bus_stop;
           bus_idle_for(10);

           // THE structural diagnostic. One logical access, ONE start: a repeated START
           // does not release the bus, so there is nothing to re-take.
           if (n_starts !== 16'd1) begin
               $display("FAIL: a held transaction produced %0d STARTs -- one access, one START",
                        n_starts); errors = errors + 1; end
           if (n_restarts !== 16'd1) begin
               $display("FAIL: counted %0d repeated STARTs, expected 1", n_restarts); errors = errors + 1; end
           if (n_stops !== 16'd1) begin
               $display("FAIL: counted %0d STOPs, expected 1", n_stops); errors = errors + 1; end
           if (junction_kind !== JK_RESTART) begin
               $display("FAIL: junction_kind = %0d, expected %0d (repeated START)",
                        junction_kind, JK_RESTART); errors = errors + 1; end
           // No gap to measure, and therefore no window in which another master could
           // have interposed.
           if (bus_was_free !== 1'b0) begin
               $display("FAIL: a repeated START reported the bus as having been free");
               errors = errors + 1; end
           if (in_transfer !== 1'b0) begin
               $display("FAIL: still in a transfer after the STOP"); errors = errors + 1; end

           // ================================================================
           // SEQUENCE 2 — the SAME ACCESS written as STOP then START.
           //     S  addr+W  A  ptr  A  P   [bus free]   S  addr+R  A  data  N  P
           // Byte for byte identical. Only the junction differs.
           // ================================================================
           begin
               sr_before = n_restarts; s_before = n_starts;

               bus_start;
               bus_byte(8'hA0, 1'b0);
               bus_byte(8'h12, 1'b0);
               bus_stop;                 // THE JUNCTION -- the bus is RELEASED here
               bus_idle_for(24);           // the mandatory bus-free interval, and then some
               bus_start;                // ... and re-taken
               bus_byte(8'hA1, 1'b0);
               bus_byte(8'h5A, 1'b1);
               bus_stop;
               bus_idle_for(10);

               // THE structural difference: no repeated START at all, and TWO fresh
               // STARTs where one logical access should have produced one.
               if (n_restarts !== sr_before) begin
                   $display("FAIL: a STOP-then-START junction produced a repeated START");
                   errors = errors + 1; end
               if (n_starts !== s_before + 16'd2) begin
                   $display("FAIL: a released junction produced %0d fresh STARTs, expected 2",
                            n_starts - s_before); errors = errors + 1; end
           end
           if (junction_kind !== JK_FRESH) begin
               $display("FAIL: junction_kind = %0d, expected %0d (fresh START)",
                        junction_kind, JK_FRESH); errors = errors + 1; end
           // THE cost. The bus was measurably free, and every cycle of it is a window in
           // which another master could have taken the bus and changed the pointer.
           if (last_free_cycles === {CNT_W{1'b0}}) begin
               $display("FAIL: a release junction measured ZERO free cycles"); errors = errors + 1; end
           if (last_free_cycles < 16'd20) begin
               $display("FAIL: the gap measured %0d cycles, expected at least 20",
                        last_free_cycles); errors = errors + 1; end

           // ================================================================
           // 3 — the gap is MEASURED, not assumed. A longer idle must read longer.
           // ================================================================
           begin
               bus_start; bus_byte(8'hA0, 1'b0); bus_stop;
               bus_idle_for(12);
               bus_start; bus_byte(8'hA1, 1'b0); bus_stop;
               short_gap = last_free_cycles;
               bus_idle_for(60);
               bus_start; bus_byte(8'hA1, 1'b0); bus_stop;
               long_gap = last_free_cycles;
               if (!(long_gap > short_gap)) begin
                   $display("FAIL: a 60-cycle gap measured %0d, a 12-cycle gap measured %0d",
                            long_gap, short_gap); errors = errors + 1; end
               if (short_gap < 16'd8 || short_gap > 16'd20) begin
                   $display("FAIL: the 12-cycle gap measured %0d, expected roughly 12",
                            short_gap); errors = errors + 1; end
           end

           // ================================================================
           // 3b — free_cycles is the RUNNING gap, and there is no gap during a transfer.
           //      A consumer that read it mid-transfer must see zero, not the previous
           //      gap's length left over.
           // ================================================================
           bus_start;
           if (free_cycles !== {CNT_W{1'b0}}) begin
               $display("FAIL: free_cycles reads %0d inside a transfer -- a stale measurement",
                        free_cycles); errors = errors + 1; end
           bus_byte(8'hA0, 1'b0);
           if (free_cycles !== {CNT_W{1'b0}}) begin
               $display("FAIL: free_cycles reads %0d mid-byte, expected 0", free_cycles);
               errors = errors + 1; end
           bus_stop;
           bus_idle_for(10);

           // ================================================================
           // 3c — the gap measures time the bus was ACTUALLY FREE. If somebody is holding
           //      SCL low after the STOP, the bus is not available, and those cycles must
           //      not be counted as though another master could have used them.
           // ================================================================
           begin
               bus_start; bus_byte(8'hA0, 1'b0); bus_stop;
               // SCL held LOW by somebody -- the bus is busy, not free
               sda_in = 1'b1; scl_in = 1'b0;
               repeat (40) @(negedge clk);
               // now genuinely released
               bus_idle_for(12);
               bus_start; bus_byte(8'hA1, 1'b0); bus_stop;
               gap_with_hold = last_free_cycles;
               // 40 held cycles plus 12 free ones. Only the free ones are a window.
               if (gap_with_hold >= 16'd40) begin
                   $display("FAIL: the gap measured %0d cycles, counting the 40 in which SCL was HELD LOW",
                            gap_with_hold); errors = errors + 1; end
               if (gap_with_hold === {CNT_W{1'b0}}) begin
                   $display("FAIL: the genuinely free cycles were not counted at all");
                   errors = errors + 1; end
           end

           // ================================================================
           // 4 — THREE phases joined by two repeated STARTs. The bus is held across the
           //     whole thing, so a three-phase transaction is no more exposed than a
           //     two-phase one -- the exposure is a property of the JUNCTIONS, not of
           //     the length.
           // ================================================================
           begin
               s_before = n_starts; sr_before = n_restarts;
               bus_start;
               bus_byte(8'hA0, 1'b0); bus_byte(8'h12, 1'b0);
               bus_start;                             // junction 1
               bus_byte(8'hA1, 1'b0); bus_byte(8'h5A, 1'b0);
               bus_start;                             // junction 2
               bus_byte(8'hA1, 1'b0); bus_byte(8'h6B, 1'b1);
               bus_stop;
               bus_idle_for(10);
               // Three phases, ONE start, TWO repeated STARTs. Exposure is a property of
               // the junctions, not of the transaction's length: a three-phase held
               // transaction is exactly as exposed as a two-phase one, namely not at all.
               if (n_starts !== s_before + 16'd1) begin
                   $display("FAIL: a three-phase held transaction produced %0d STARTs, expected 1",
                            n_starts - s_before); errors = errors + 1; end
               if (n_restarts !== sr_before + 16'd2) begin
                   $display("FAIL: a three-phase transaction produced %0d repeated STARTs, expected 2",
                            n_restarts - sr_before); errors = errors + 1; end
               if (junction_kind !== JK_RESTART) begin
                   $display("FAIL: the last junction of a held transaction was not an Sr");
                   errors = errors + 1; end
           end

           // ================================================================
           // 5 — an SDA edge while SCL is LOW is DATA, never framing. This is the check
           //     that stops the monitor from inventing junctions inside a byte.
           // ================================================================
           begin
               s_before = n_starts; p_before = n_stops;
               // a byte whose bits move SDA up and down many times, all with SCL low
               bus_start;
               bus_byte(8'hAA, 1'b0);       // 1010 1010 -- eight SDA transitions
               bus_byte(8'h55, 1'b0);       // 0101 0101 -- eight more
               bus_stop;
               bus_idle_for(10);
               // exactly one S and one P from the framing above, and nothing from the data
               if (n_starts !== s_before + 16'd1) begin
                   $display("FAIL: data transitions produced %0d spurious STARTs",
                            n_starts - s_before - 1); errors = errors + 1; end
               if (n_stops !== p_before + 16'd1) begin
                   $display("FAIL: data transitions produced %0d spurious STOPs",
                            n_stops - p_before - 1); errors = errors + 1; end
           end

           // ================================================================
           // 6 — a long IDLE with no framing at all must not open a transfer or count
           //     anything. The bus spends most of its life here.
           // ================================================================
           begin
               s_before = n_starts; sr_before = n_restarts; p_before = n_stops;
               bus_idle_for(80);
               if (n_starts !== s_before || n_restarts !== sr_before || n_stops !== p_before) begin
                   $display("FAIL: an idle bus produced framing events"); errors = errors + 1; end
               if (in_transfer !== 1'b0) begin
                   $display("FAIL: an idle bus opened a transfer"); errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: Sr and STOP-then-START distinguished from the wire, the release window measured, data edges never mistaken for framing");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_phase_junction_monitor.vhd — the same monitor in VHDL
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- A PASSIVE bus monitor that classifies the JUNCTION between two phases of a transfer
   -- and measures what a release costs. It drives nothing.
   --
   -- The two sequences this chapter compares look almost identical in a driver's source
   -- and are completely different on the wire:
   --
   --   REPEATED START            S ... A  [Sr]  addr+R ...           bus NEVER released
   --   STOP then START           S ... A  [P]   ......  [S] addr+R   bus released
   --
   -- And they are distinguishable from the two wires alone, using nothing but the framing
   -- rule of Chapter 5.1 -- an SDA edge while SCL is HIGH is reserved for framing:
   --
   --   SDA falling while SCL high  ->  a START.  If a transfer is already open it is a
   --                                   REPEATED start, and the bus was never released.
   --   SDA rising  while SCL high  ->  a STOP.   The bus is now free, and every device
   --                                   on it is entitled to start a transfer.
   --
   -- The measurement that matters is free_cycles: how long the bus sat idle between a STOP
   -- and the next START. That interval is the EXPOSURE WINDOW -- the time during which any
   -- other master could legitimately have begun its own transfer and, in the
   -- register-pointer pattern of Chapter 10.1, changed the pointer this master had just
   -- written. A repeated START makes that window exactly zero.
   --
   -- ONE LIMIT IS WORTH STATING PLAINLY, because it is the bridge to Chapter 10.3.
   -- This block reports FACTS about the wire and cannot report INTENT. A STOP followed by
   -- a START is, on the wire, exactly two separate transactions -- and that is precisely
   -- what it IS. So the diagnostic here is structural rather than semantic:
   --
   --     one logical access should produce ONE start and k RESTARTS.
   --     If it produced two STARTS, the bus was released, whatever the driver intended.
   entity i2c_phase_junction_monitor is
       generic (
           CNT_W : positive := 16
       );
       port (
           clk   : in std_logic;
           rst_n : in std_logic;
           -- The two wires, already synchronised to clk by the input stage of Module 4.
           sda_in : in std_logic;
           scl_in : in std_logic;

           -- framing, detected from the wire
           start_det   : out std_logic;   -- pulse: a START (first of a transfer)
           restart_det : out std_logic;   -- pulse: a REPEATED START -- bus retained
           stop_det    : out std_logic;   -- pulse: a STOP -- bus released
           in_transfer : out std_logic;   -- open: between an S/Sr and the next P

           -- How the most recent addressing was reached: by a repeated START, with the bus
           -- retained, or by a fresh START after the bus had been free.
           junction_kind : out unsigned(1 downto 0);  -- 0 none, 1 repeated START, 2 fresh
           bus_was_free  : out std_logic;             -- pulse: a fresh START re-took a free bus

           -- the cost of a release
           free_cycles      : out unsigned(CNT_W - 1 downto 0);
           last_free_cycles : out unsigned(CNT_W - 1 downto 0);
           n_starts         : out unsigned(CNT_W - 1 downto 0);
           n_restarts       : out unsigned(CNT_W - 1 downto 0);
           n_stops          : out unsigned(CNT_W - 1 downto 0)
       );
   end entity;

   architecture rtl of i2c_phase_junction_monitor is
       constant JK_NONE    : unsigned(1 downto 0) := to_unsigned(0, 2);
       constant JK_RESTART : unsigned(1 downto 0) := to_unsigned(1, 2);
       constant JK_FRESH   : unsigned(1 downto 0) := to_unsigned(2, 2);

       signal sda_q, scl_q : std_logic := '1';   -- an idle bus is high on both wires
       signal sda_fall, sda_rise, scl_stable_high : std_logic;

       signal gap_running : std_logic := '0';
       signal bus_idle    : std_logic;

       signal gap  : unsigned(CNT_W - 1 downto 0) := (others => '0');
       signal open_xfer : std_logic := '0';
   begin
       sda_fall <= sda_q and (not sda_in);
       sda_rise <= (not sda_q) and sda_in;

       -- SCL must be high ACROSS the SDA edge, not merely high after it. Requiring both
       -- the previous and the current sample to be high is what stops a coincident SCL
       -- rise from being misread as framing -- the defect Module 4's mutation suite found.
       scl_stable_high <= scl_q and scl_in;

       -- "Idle" means both lines released: an undriven bus is high on both wires.
       bus_idle <= sda_in and scl_in;

       free_cycles <= gap;
       in_transfer <= open_xfer;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   sda_q            <= '1';
                   scl_q            <= '1';
                   start_det        <= '0';
                   restart_det      <= '0';
                   stop_det         <= '0';
                   open_xfer        <= '0';
                   junction_kind    <= JK_NONE;
                   bus_was_free     <= '0';
                   gap              <= (others => '0');
                   last_free_cycles <= (others => '0');
                   n_starts         <= (others => '0');
                   n_restarts       <= (others => '0');
                   n_stops          <= (others => '0');
                   gap_running      <= '0';
               else
                   sda_q <= sda_in;
                   scl_q <= scl_in;

                   start_det    <= '0';
                   restart_det  <= '0';
                   stop_det     <= '0';
                   bus_was_free <= '0';

                   if sda_fall = '1' and scl_stable_high = '1' then
                       -- A START. Whether it is a plain one or a REPEATED one depends
                       -- entirely on whether a transfer was already open -- there is no
                       -- difference in the electrical event itself, which is why a monitor
                       -- must track state to classify it.
                       if open_xfer = '1' then
                           restart_det   <= '1';
                           n_restarts    <= n_restarts + 1;
                           junction_kind <= JK_RESTART;
                       else
                           start_det     <= '1';
                           n_starts      <= n_starts + 1;
                           junction_kind <= JK_FRESH;
                           -- A fresh START that follows a STOP re-takes a bus that was
                           -- genuinely free. The gap timer was running, so its value is the
                           -- window during which any other master could have taken it.
                           if gap_running = '1' then
                               bus_was_free     <= '1';
                               last_free_cycles <= gap;
                           end if;
                       end if;
                       open_xfer   <= '1';
                       gap_running <= '0';
                       gap         <= (others => '0');
                   elsif sda_rise = '1' and scl_stable_high = '1' then
                       -- A STOP. The bus is released from this instant, and the gap timer
                       -- starts -- it is measuring the window, not waiting to see if
                       -- anyone uses it.
                       stop_det    <= '1';
                       n_stops     <= n_stops + 1;
                       open_xfer   <= '0';
                       gap_running <= '1';
                       gap         <= (others => '0');
                   elsif gap_running = '1' and bus_idle = '1' then
                       gap <= gap + 1;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_phase_junction_monitor_tb.vhd — the VHDL testbench, driving the wires directly
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- The testbench drives the two WIRES, not an abstract command interface, because the
   -- claim under test is about what the wire shows. Both sequences are built from the same
   -- primitives so the only difference between them is the junction.
   entity i2c_phase_junction_monitor_tb is
   end entity;

   architecture sim of i2c_phase_junction_monitor_tb is
       constant CNT_W : positive := 16;

       constant JK_NONE    : unsigned(1 downto 0) := to_unsigned(0, 2);
       constant JK_RESTART : unsigned(1 downto 0) := to_unsigned(1, 2);
       constant JK_FRESH   : unsigned(1 downto 0) := to_unsigned(2, 2);

       signal clk   : std_logic := '0';
       signal rst_n : std_logic := '0';
       signal sda_in, scl_in : std_logic := '1';   -- an idle bus is high on both wires

       signal start_det, restart_det, stop_det, in_transfer, bus_was_free : std_logic;
       signal junction_kind : unsigned(1 downto 0);
       signal free_cycles, last_free_cycles : unsigned(CNT_W - 1 downto 0);
       signal n_starts, n_restarts, n_stops : unsigned(CNT_W - 1 downto 0);

       signal test_done : std_logic := '0';
   begin
       dut : entity work.i2c_phase_junction_monitor
           generic map (CNT_W => CNT_W)
           port map (clk => clk, rst_n => rst_n, sda_in => sda_in, scl_in => scl_in,
                     start_det => start_det, restart_det => restart_det,
                     stop_det => stop_det, in_transfer => in_transfer,
                     junction_kind => junction_kind, bus_was_free => bus_was_free,
                     free_cycles => free_cycles, last_free_cycles => last_free_cycles,
                     n_starts => n_starts, n_restarts => n_restarts, n_stops => n_stops);

       clk <= not clk after 5 ns;

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

       stim : process
           variable errs : natural := 0;
           variable sr_before, s_before, p_before : unsigned(CNT_W - 1 downto 0);
           variable short_gap, long_gap, gap_with_hold : unsigned(CNT_W - 1 downto 0);

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

           -- A START: SDA falls while SCL is HIGH.
           procedure bus_start is
           begin
               sda_in <= '1'; scl_in <= '1'; waitn(2);
               sda_in <= '0';               waitn(1);   -- the falling edge, SCL high
               scl_in <= '0';               waitn(1);   -- SCL drops: the transfer begins
           end procedure;

           -- A STOP: SDA rises while SCL is HIGH.
           procedure bus_stop is
           begin
               sda_in <= '0'; scl_in <= '0'; waitn(1);
               scl_in <= '1';                waitn(1);  -- SCL up with SDA still low
               sda_in <= '1';                waitn(1);  -- the rising edge, SCL high
           end procedure;

           -- A data bit: SDA changes only while SCL is LOW, then one SCL pulse.
           procedure bus_bit (v : in std_logic) is
           begin
               scl_in <= '0'; waitn(1);
               sda_in <= v;   waitn(1);
               scl_in <= '1'; waitn(1);
               scl_in <= '0'; waitn(1);
           end procedure;

           -- One byte and its acknowledge slot -- nine bits, as Chapter 7.1 requires.
           procedure bus_byte (v : in std_logic_vector(7 downto 0); ninth : in std_logic) is
           begin
               for i in 7 downto 0 loop bus_bit(v(i)); end loop;
               bus_bit(ninth);
           end procedure;

           -- Let the bus sit IDLE for n cycles: both wires released, which is what an
           -- unowned bus looks like.
           procedure bus_idle_for (n : in positive) is
           begin
               sda_in <= '1'; scl_in <= '1';
               waitn(n);
           end procedure;
       begin
           waitn(3);
           if in_transfer /= '0' then
               report "in_transfer out of reset" severity error; errs := errs + 1; end if;
           if junction_kind /= JK_NONE then
               report "junction_kind out of reset" severity error; errs := errs + 1; end if;
           rst_n <= '1'; waitn(1);

           -- ================================================================
           -- SEQUENCE 1 -- the CORRECT combined transaction: repeated START.
           --     S  addr+W  A  ptr  A  Sr  addr+R  A  data  N  P
           -- ================================================================
           bus_start;
           bus_byte(x"A0", '0');          -- addr+W, slave ACKs (SDA low in the 9th)
           bus_byte(x"12", '0');          -- the pointer, ACKed
           bus_start;                     -- THE JUNCTION -- a repeated START
           bus_byte(x"A1", '0');          -- addr+R, ACKed
           bus_byte(x"5A", '1');          -- data, master NACKs (SDA high in the 9th)
           bus_stop;
           bus_idle_for(10);

           -- THE structural diagnostic. One logical access, ONE start: a repeated START
           -- does not release the bus, so there is nothing to re-take.
           if n_starts /= to_unsigned(1, CNT_W) then
               report "a held transaction produced more than one START" severity error;
               errs := errs + 1; end if;
           if n_restarts /= to_unsigned(1, CNT_W) then
               report "wrong number of repeated STARTs, expected 1" severity error;
               errs := errs + 1; end if;
           if n_stops /= to_unsigned(1, CNT_W) then
               report "wrong number of STOPs, expected 1" severity error; errs := errs + 1; end if;
           if junction_kind /= JK_RESTART then
               report "junction_kind should be repeated START" severity error;
               errs := errs + 1; end if;
           -- No gap to measure, and therefore no window in which another master could
           -- have interposed.
           if bus_was_free /= '0' then
               report "a repeated START reported the bus as having been free" severity error;
               errs := errs + 1; end if;
           if in_transfer /= '0' then
               report "still in a transfer after the STOP" severity error; errs := errs + 1; end if;

           -- ================================================================
           -- SEQUENCE 2 -- the SAME ACCESS written as STOP then START.
           --     S  addr+W  A  ptr  A  P   [bus free]   S  addr+R  A  data  N  P
           -- Byte for byte identical. Only the junction differs.
           -- ================================================================
           sr_before := n_restarts; s_before := n_starts;
           bus_start;
           bus_byte(x"A0", '0');
           bus_byte(x"12", '0');
           bus_stop;                      -- THE JUNCTION -- the bus is RELEASED here
           bus_idle_for(24);              -- the mandatory bus-free interval, and then some
           bus_start;                     -- ... and re-taken
           bus_byte(x"A1", '0');
           bus_byte(x"5A", '1');
           bus_stop;
           bus_idle_for(10);

           -- THE structural difference: no repeated START at all, and TWO fresh STARTs
           -- where one logical access should have produced one.
           if n_restarts /= sr_before then
               report "a STOP-then-START junction produced a repeated START" severity error;
               errs := errs + 1; end if;
           if n_starts /= s_before + 2 then
               report "a released junction did not produce two fresh STARTs" severity error;
               errs := errs + 1; end if;
           if junction_kind /= JK_FRESH then
               report "junction_kind should be a fresh START" severity error;
               errs := errs + 1; end if;
           -- THE cost. The bus was measurably free, and every cycle of it is a window in
           -- which another master could have taken the bus and changed the pointer.
           if last_free_cycles = to_unsigned(0, CNT_W) then
               report "a release junction measured ZERO free cycles" severity error;
               errs := errs + 1; end if;
           if last_free_cycles < to_unsigned(20, CNT_W) then
               report "the measured gap was shorter than the idle that produced it"
                   severity error; errs := errs + 1; end if;

           -- ================================================================
           -- 3 -- the gap is MEASURED, not assumed. A longer idle must read longer.
           -- ================================================================
           bus_start; bus_byte(x"A0", '0'); bus_stop;
           bus_idle_for(12);
           bus_start; bus_byte(x"A1", '0'); bus_stop;
           short_gap := last_free_cycles;
           bus_idle_for(60);
           bus_start; bus_byte(x"A1", '0'); bus_stop;
           long_gap := last_free_cycles;
           if not (long_gap > short_gap) then
               report "a 60-cycle gap did not measure longer than a 12-cycle gap"
                   severity error; errs := errs + 1; end if;
           if short_gap < to_unsigned(8, CNT_W) or short_gap > to_unsigned(20, CNT_W) then
               report "the 12-cycle gap measured outside the expected range" severity error;
               errs := errs + 1; end if;

           -- ================================================================
           -- 3b -- free_cycles is the RUNNING gap, and there is no gap during a transfer.
           --       A consumer that read it mid-transfer must see zero, not the previous
           --       gap's length left over.
           -- ================================================================
           bus_start;
           if free_cycles /= to_unsigned(0, CNT_W) then
               report "free_cycles is nonzero inside a transfer -- a stale measurement"
                   severity error; errs := errs + 1; end if;
           bus_byte(x"A0", '0');
           if free_cycles /= to_unsigned(0, CNT_W) then
               report "free_cycles is nonzero mid-byte, expected 0" severity error;
               errs := errs + 1; end if;
           bus_stop;
           bus_idle_for(10);

           -- ================================================================
           -- 3c -- the gap measures time the bus was ACTUALLY FREE. If somebody is holding
           --       SCL low after the STOP, the bus is not available, and those cycles must
           --       not be counted as though another master could have used them.
           -- ================================================================
           bus_start; bus_byte(x"A0", '0'); bus_stop;
           -- SCL held LOW by somebody -- the bus is busy, not free
           sda_in <= '1'; scl_in <= '0';
           waitn(40);
           -- now genuinely released
           bus_idle_for(12);
           bus_start; bus_byte(x"A1", '0'); bus_stop;
           gap_with_hold := last_free_cycles;
           -- 40 held cycles plus 12 free ones. Only the free ones are a window.
           if gap_with_hold >= to_unsigned(40, CNT_W) then
               report "the gap counted the cycles in which SCL was HELD LOW" severity error;
               errs := errs + 1; end if;
           if gap_with_hold = to_unsigned(0, CNT_W) then
               report "the genuinely free cycles were not counted at all" severity error;
               errs := errs + 1; end if;

           -- ================================================================
           -- 4 -- THREE phases joined by two repeated STARTs. The bus is held across the
           --      whole thing, so a three-phase transaction is no more exposed than a
           --      two-phase one -- the exposure is a property of the JUNCTIONS, not of
           --      the length.
           -- ================================================================
           s_before := n_starts; sr_before := n_restarts;
           bus_start;
           bus_byte(x"A0", '0'); bus_byte(x"12", '0');
           bus_start;                             -- junction 1
           bus_byte(x"A1", '0'); bus_byte(x"5A", '0');
           bus_start;                             -- junction 2
           bus_byte(x"A1", '0'); bus_byte(x"6B", '1');
           bus_stop;
           bus_idle_for(10);
           if n_starts /= s_before + 1 then
               report "a three-phase held transaction produced more than one START"
                   severity error; errs := errs + 1; end if;
           if n_restarts /= sr_before + 2 then
               report "a three-phase transaction did not produce two repeated STARTs"
                   severity error; errs := errs + 1; end if;
           if junction_kind /= JK_RESTART then
               report "the last junction of a held transaction was not an Sr" severity error;
               errs := errs + 1; end if;

           -- ================================================================
           -- 5 -- an SDA edge while SCL is LOW is DATA, never framing. This is the check
           --      that stops the monitor from inventing junctions inside a byte.
           -- ================================================================
           s_before := n_starts; p_before := n_stops;
           bus_start;
           bus_byte(x"AA", '0');       -- 1010 1010 -- eight SDA transitions
           bus_byte(x"55", '0');       -- 0101 0101 -- eight more
           bus_stop;
           bus_idle_for(10);
           if n_starts /= s_before + 1 then
               report "data transitions produced spurious STARTs" severity error;
               errs := errs + 1; end if;
           if n_stops /= p_before + 1 then
               report "data transitions produced spurious STOPs" severity error;
               errs := errs + 1; end if;

           -- ================================================================
           -- 6 -- a long IDLE with no framing at all must not open a transfer or count
           --      anything. The bus spends most of its life here.
           -- ================================================================
           s_before := n_starts; sr_before := n_restarts; p_before := n_stops;
           bus_idle_for(80);
           if n_starts /= s_before or n_restarts /= sr_before or n_stops /= p_before then
               report "an idle bus produced framing events" severity error;
               errs := errs + 1; end if;
           if in_transfer /= '0' then
               report "an idle bus opened a transfer" severity error; errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_phase_junction_monitor self-check complete: Sr and STOP-then-START "
                    & "distinguished from the wire, the release window measured, data edges "
                    & "never mistaken for framing" severity note;
           else
               report "i2c_phase_junction_monitor self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

5a. Five Decisions Worth Defending

scl_stable_high requires SCL high on both samples, not just the current one. A framing event is an SDA edge while SCL is high, which means SCL must be high across the edge. Testing only the current sample admits a case where SCL and SDA moved together — and a coincident SCL rise would then be read as framing. Module 4's mutation suite found exactly this defect, and mutation B3 re-injects it here: the result is a held transaction reporting two STARTs, because a data-phase transition gets misclassified.

A repeated START is detected by state, not by the wire. if (in_transfer) is the entire classifier, because §1 established the two events are electrically identical. This is worth noticing as a design shape: when two protocol events share a physical signature, the classifier is necessarily stateful, and the state it needs is exactly the thing the protocol uses to disambiguate them. Mutation B1 removes the test and every repeated START is counted as a fresh one.

The gap timer requires the bus to be genuinely idle, not merely "after a STOP". gap_running && bus_idle, where idle means both wires released. If somebody is holding SCL low after the STOP — a stuck slave, another master's aborted attempt — the bus is not available, and counting those cycles would overstate the window. The measurement is meant to answer "how long could another master have used this bus", and a held-low SCL is time nobody could have used. Mutation B6 drops the bus_idle term and is killed by a test that holds SCL low for forty cycles in the middle of a gap.

free_cycles and last_free_cycles are separate outputs. One is the gap in progress, the other the completed measurement, and a consumer needs different things from each. Keeping them separate also means free_cycles reads zero during a transfer rather than holding a stale value — which is a real requirement and the one mutation B5 violates. Nothing in the design consumes either; both exist to be read.

It is genuinely passive. No output goes near SDA or SCL. That is what makes it safe to leave enabled in production silicon, and a debug block that could perturb the bus would be switched off in precisely the situations where it was wanted.

5b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o b0 i2c_phase_junction_monitor.sv i2c_phase_junction_monitor_tb.sv && ./b0
   PASS: Sr and STOP-then-START distinguished from the wire, the release window measured,
   data edges never mistaken for framing
   i2c_phase_junction_monitor_tb.sv:272: $finish called at 11630 (1ps)

   $ iverilog -g2005 -o b1 i2c_phase_junction_monitor.v i2c_phase_junction_monitor_tb.v && ./b1
   PASS: Sr and STOP-then-START distinguished from the wire, the release window measured,
   data edges never mistaken for framing
   i2c_phase_junction_monitor_tb.v:285: $finish called at 11630 (1ps)

   $ nvc -a i2c_phase_junction_monitor.vhd i2c_phase_junction_monitor_tb.vhd
   $ nvc -e i2c_phase_junction_monitor_tb && nvc -r i2c_phase_junction_monitor_tb --stop-time=900us
   ** Note: 11630ns+0: i2c_phase_junction_monitor self-check complete: Sr and
      STOP-then-START distinguished from the wire, the release window measured, data
      edges never mistaken for framing

All three at 11630 ns.

6. What the Testbench Proves

This testbench drives the two wires, not a command interface, because the claim under test is about what the wire shows. Its primitives are built strictly from the framing rules: bus_start moves SDA down while SCL is high, bus_stop moves it up while SCL is high, bus_bit changes SDA only while SCL is low.

That matters more than it sounds. Building the stimulus from the rules rather than from a recording means the two sequences in §2 are assembled from the identical primitives in the identical order, with one substitution — so the test genuinely isolates the junction rather than comparing two hand-written waveforms that might differ in other ways too.

#stimuluswhat it establishes
1the correct sequence, with an Srone fresh START, one repeated START, one STOP; no gap
2the same bytes, with a STOP-then-STARTtwo fresh STARTs, no repeated START, and a measured gap
3a 12-cycle gap and a 60-cycle gapthe gap is measured, not assumed — longer reads longer
3bmid-transferfree_cycles reads zero, not the previous gap's length
3cSCL held low for 40 cycles inside a gaponly the genuinely free cycles are counted
4three phases, two repeated STARTsone START, two Sr's — exposure is about junctions, not length
5bytes 0xAA and 0x55sixteen SDA transitions with SCL low produce no framing
680 idle cyclesnothing counted, no transfer opened

Test 5 is the one that keeps the monitor honest. 0xAA is 1010 1010 and 0x55 is 0101 0101, so between them they move SDA sixteen times — every one with SCL low, every one legal data. A monitor that got the SCL condition wrong would invent eight starts and eight stops inside two bytes, and the failure would look like a wildly broken capture rather than like a detector bug.

Test 3c was added because a mutation survived without it, and the scenario it covers is real: after a STOP, SCL held low means somebody is driving the bus, so those cycles are not a window anyone could have used. Measuring them would overstate the exposure — and the whole value of the measurement is that it is honest about size.

7. Mutation Testing

Eight defects injected into the SystemVerilog monitor.

#injected defectoutcome
B1a repeated START is counted as a fresh STARTkilled — a held transaction reported 2 STARTs
B2framing detected on an SDA edge while SCL is lowkilled — reported 11 STARTs in one transaction
B3only the current SCL sample is required, not bothkilled — a held transaction reported 2 STARTs
B4START and STOP swappedkilled — 0 repeated STARTs, expected 1
B5the gap timer is not reset by a STARTkilled — free_cycles stale inside a transfer
B6the gap counts even while the bus is not idlekilled — counted the 40 held-low cycles
B7the gap measurement is never capturedkilled — a release measured zero free cycles
B8in_transfer is not cleared by a STOPkilled — still in a transfer after the STOP

Eight injected, eight killed — with B5 and B6 requiring two new tests, both recorded here rather than smoothed over.

B5 and B6 survived the first version of the testbench and their common shape is instructive. Both concern the gap timer in situations the original stimulus never created: B5 needs somebody to read free_cycles during a transfer (the original only read it after a gap), and B6 needs a gap in which the bus is not idle (the original made every gap perfectly idle). Neither is exotic — a stale measurement and an inflated one are both ordinary instrument defects — and neither was reachable.

The generalisation is one this course has hit before, in Chapter 8.3 §9: a suite that only exercises the clean case cannot test the guards that define it. The gap timer has two guards, gap_running and bus_idle, and testing it only during clean idle gaps exercises neither.

B2 and B3 are the same mistake at two severities, which is why both are worth injecting. B2 inverts the SCL condition entirely and produces eleven STARTs in a two-byte transaction — unmistakable. B3 merely weakens it, requiring SCL high only on the current sample, and produces two STARTs where one belongs. The second is the realistic bug: it is a plausible simplification, it works for most edges, and it fails only when SCL and SDA move in the same cycle. Injecting the loud version alone would have left the quiet one untested.

8. Verification Connection — Assert the Junction, Cover the Gap

The junction rule is a property: it holds at every moment of every combined transaction, including ones written later by somebody who has not read this chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_junction_props.sv — the junction rule as continuous properties
   // A direction change requires a re-addressing, and a re-addressing inside a
   // transaction is a REPEATED START. So a transaction whose direction changes must
   // show an Sr, never a STOP followed by a START.
   //
   // Note what this does NOT say: it says nothing about two adjacent transactions,
   // because on the wire those are legal and indistinguishable (section 4). The
   // property is only assertable because `txn_multi_phase` comes from the MASTER.
   property p_junction_is_restart;
      @(posedge clk) disable iff (!rst_n)
      (txn_multi_phase && phase_complete && !txn_last_phase) |=> restart_det;
   endproperty
   assert property (p_junction_is_restart)
      else $error("a phase junction used a STOP -- the bus was released mid-transaction");

   // A repeated START is only meaningful inside a transfer. One detected while no
   // transfer is open means the monitor's state and the bus disagree, which is worth
   // an error in its own right: every count after it will be wrong.
   property p_restart_only_inside_a_transfer;
      @(posedge clk) disable iff (!rst_n)
      restart_det |-> $past(in_transfer);
   endproperty
   assert property (p_restart_only_inside_a_transfer)
      else $error("a repeated START was detected with no transfer open");

   // Framing NEVER happens while SCL is low. This is the rule the whole detector rests
   // on, asserted directly so a detector bug is caught at the source rather than as a
   // wrong byte count several layers up.
   property p_framing_requires_scl_high;
      @(posedge clk) disable iff (!rst_n)
      (start_det || restart_det || stop_det) |-> $past(scl_in) && scl_in;
   endproperty
   assert property (p_framing_requires_scl_high)
      else $error("a framing event was detected while SCL was low");

And the coverage, which for this chapter is about gap sizes — because the bug's probability is a function of the window.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_gap_cov.sv — the exposure windows worth distinguishing
   covergroup i2c_gap_cg with function sample(int free_cycles, int n_starts_in_access,
                                              bit multi_phase);
      // THE coverpoint. A combined access that produced more than one fresh START
      // released the bus. One is correct; two is the bug; more than two means several
      // releases in one logical access.
      starts_per_access: coverpoint n_starts_in_access {
         bins held      = {1};          // correct: one START, junctions are Sr
         bins released  = {2};          // the bug of this chapter
         bins repeated  = {[3:16]};     // released more than once
      }

      // Gap size buckets, chosen because the FAILURE PROBABILITY scales with them, not
      // because they are evenly spaced. A sub-microsecond release is a latent bug that
      // will bite rarely; a millisecond release will bite in the field this week.
      gap: coverpoint free_cycles {
         bins none       = {0};              // a repeated START: no window at all
         bins tiny       = {[1:20]};
         bins short      = {[21:200]};
         bins long       = {[201:2000]};
         bins very_long  = {[2001:$]};
      }

      multi: coverpoint multi_phase;

      // The cross is the point: a single-phase transfer legitimately has one START and
      // a gap before it, while a MULTI-phase access with a gap in the middle is the
      // bug. Covering gap size and phase count separately cannot tell them apart.
      gap_x_multi: cross gap, multi;
      starts_x_multi: cross starts_per_access, multi;
   endgroup

9. FPGA and ASIC Implications

The monitor is four counters and an edge detector. Two synchroniser flops for the wires, three event counters, a gap counter and a captured measurement, plus three state bits. At CNT_W = 16 that is roughly 90 flops — small enough to leave permanently enabled on any FPGA and negligible on an ASIC.

Size the gap counter for the longest gap you care to measure, not the longest possible. A gap counter that saturates reports a small number, which is the worst failure mode a measurement has (Chapter 9.3 §10 made the same point about pulse counters). At a 50 MHz system clock, sixteen bits is 1.3 ms — plenty for a junction gap and far too short for "how long has this bus been idle", so if you want the second measurement, use a second wider counter rather than stretching this one.

The two wires must be synchronised before they reach this block. It samples sda_in and scl_in on the system clock and compares consecutive samples, so both must already be clean single-clock-domain signals — the input stage Module 4 describes. Feeding raw pads in would let a metastable sample produce a phantom framing event, and a phantom START is exactly the kind of error that makes every subsequent count wrong.

bus_was_free is the output worth wiring to something. It pulses when a fresh START re-takes a bus that had been released, with the measured window on last_free_cycles beside it. On a single-master system that is normal and uninteresting. On a multi-master system, correlated with a master's own transaction state, it is the signal that catches the bug of this chapter — which is what Chapter 10.3 does with it.

This is a genuinely useful thing to ship. A permanently enabled monitor answers questions that are otherwise a logic-analyser session: is the bus being clocked, how many transactions per second, how long is it idle, and — the one specific to this chapter — did any access release the bus in the middle. None of that needs a probe on the board.

10. Debugging — The Sensor That Read Wrong Once a Week

Pitfall — a STOP-then-START junction on a bus that later gained a second master
Buggy Code
// A temperature sensor driver, written and validated two years earlier on a board
// with a single I2C master. The register read was implemented as two calls:
//
//     i2c_write(SENSOR, &reg, 1);          // phase 1: the pointer -- ends with a STOP
//     i2c_read(SENSOR, buf, 2);            // phase 2: a fresh START
//
// Both are ordinary library calls and each is individually correct. Together they
// put this on the wire:
//
//     S 0xA0 A 0x00 A P   <bus free, ~40 us>   S 0xA1 A d0 A d1 N P
//                       ^                    ^
//                       released here ... and re-taken here
//
// It worked flawlessly for two years, because there was only one master.
//
// Then a revision added a second controller on the same bus for a power-management
// IC -- which also, occasionally, reads that same sensor for a thermal limit.
Symptom

Roughly once a week, in the field, a unit logged a temperature about forty degrees off. One sample, never two in a row, then perfectly normal readings for days.

Every diagnostic said the bus was healthy. The driver reported success on every read. Retry counters were zero. The sensor passed every self-test. Swapping the sensor changed nothing, and neither did swapping the board.

It was not reproducible. A test rig ran the read in a tight loop for four days -- about thirty million reads -- with no bad sample at all, because the rig did not run the power-management firmware that was the other master.

The theory that held longest was electrical: a marginal pull-up, a noise event corrupting one byte. That theory is very hard to disprove from a log, and it predicts exactly what was observed -- a rare single-sample error.

What actually resolved it was a capture triggered on the sensor's address with a long buffer, looked at STRUCTURALLY rather than for signal quality. Most accesses showed S, P, S, P for what the source called one register read. And one capture showed, in the 40 us gap, the OTHER master's complete transaction: address, its own pointer write, a STOP. Then our read phase resumed, addressed the sensor, and read two bytes -- from the pointer the other master had just written.

Both transactions were legal. Nothing was corrupted. Our read returned a perfectly good value from the wrong register.

Root Cause

The junction between the two phases was a STOP, so the bus was released for about 40 microseconds. During that window the other master was entitled to take the bus, and occasionally did -- addressing the same device and overwriting the register pointer that phase 1 had just established.

The latent bug was two years old and harmless while the bus had one master. The hardware revision that added the second master did not introduce the bug; it made an existing one reachable. That is why nothing in the change log pointed at it.

11. Common Misconceptions

"A repeated START is a special kind of START." It is the same electrical event — SDA falling while SCL is high. What makes it "repeated" is that a transfer was already open. This is why a monitor must be stateful to classify one.

"STOP-then-START is just slower." It is also a release. On a single-master bus the difference is only time; on a shared one it opens a window in which another master may legitimately change the device's state, and both transactions remain perfectly legal.

"The bug would show up in testing." It needs two masters contending on the same device within tens of microseconds. That is rare enough to survive an entire development cycle and appear only in the field, with no error reported anywhere.

"Make the gap shorter and the race goes away." It becomes less likely, which is not the same thing. A repeated START makes the window exactly zero — a different kind of answer from reducing a probability.

"A monitor can detect a mid-transaction release from the wire." It cannot. S … P … S … P is two transactions on the wire, and nothing distinguishes that from two adjacent accesses. Detecting the fault needs the master's own intent, which is Chapter 10.3.

"All those SDA transitions inside a byte are near-misses for framing." They are legal data and are not near-misses at all, because they occur while SCL is low. The framing condition is an SDA edge while SCL is high, and §6's test 5 drives sixteen data transitions to prove the detector is not fooled.

"Any gap after a STOP is the exposure window." Only the genuinely idle part is. If SCL is being held low, nobody could have used the bus, so counting those cycles overstates the exposure — which matters because the measurement's value is that it is honest about size.

12. Reason It Through

A capture of what the source calls one register read shows two fresh STARTs and no repeated START. How confident can you be that this is a bug, and what would make you certain?

Structurally it is certain that the bus was released — two fresh STARTs means the first frame ended with a STOP. Whether that is a bug depends on intent: if the source performs one logical access, yes. The wire alone cannot settle it (§4), which is why the certain answer comes from comparing the capture against the driver's source, or from the tracker in Chapter 10.3 that takes the master's own transaction state as an input.

Why does the monitor need SCL high on two consecutive samples rather than one?

Because a framing event is an SDA edge while SCL is high, so SCL must be high across the edge. With only the current sample tested, a cycle in which SCL and SDA moved together reads as framing — and mutation B3 shows the consequence: a held transaction reporting two STARTs, because a data transition got promoted to a framing event. The quiet version of this bug is much more dangerous than the loud one.

After a STOP, SCL is held low for 200 µs, then the bus goes fully idle for 5 µs, then a START. What is the exposure window, and why?

5 µs. While SCL was held low the bus was being driven — somebody owned it — so no other master could have started a transfer. The window is the time the bus was genuinely available, which is why the gap timer requires both wires released rather than merely counting from the STOP.

A driver releases the bus for 2 µs between phases; another for 2 ms. Do they have the same bug?

Yes — identical defect, wildly different probability of being bitten. Both produce two fresh STARTs for one logical access. The 2 ms version will be found; the 2 µs version may sit latent for years and then surface as a "hardware regression" when a board revision adds a second master. This is why §8's coverage bins gap sizes by order of magnitude: the failure rate scales with the window, so the bins should too.

Why is free_cycles required to read zero during a transfer, when nothing in the design uses it?

Because it is an output, and its meaning is "the gap currently in progress". During a transfer there is no gap, so any nonzero value is a stale measurement that a consumer would misread as a live one. Mutation B5 leaves it stale and survives every check placed after a gap — the test that kills it has to read the output at a moment the original stimulus never examined.

13. Understanding Check

14. Summary

A START and a repeated START are the same electrical event. SDA falling while SCL is high. The difference is whether a transfer was already open, which is why classifying one requires state and why the two sequences in this chapter differ by exactly one framing event.

The difference is a STOP, and a STOP releases the bus. Three costs follow: a little time, possible loss of the device's register pointer, and — the one that matters — a window in which another master may legitimately take the bus and change the device's state, producing plausible wrong data with nothing reported.

The diagnostic is structural and costs one pass. One logical access should produce one fresh START and k repeated STARTs. Two fresh STARTs means the bus was released. Count STARTs, not STOPs, and measure the gap.

The wire records events, not intentions. S … P … S … P is two transactions, and no field, flag or timing distinguishes that from two adjacent accesses. A monitor can report facts and a measured window; turning those into a verdict needs the master's own state.

Measure only the time the bus was genuinely free. Cycles in which SCL is held low are time nobody could have used, and counting them overstates the exposure that the measurement exists to quantify.

A repeated START makes the window zero, not small. That is the difference between removing a race and making it rarer — and since framing costs no clock pulses, it is free.

15. What Comes Next

Chapter 10.3 closes the module by taking the input this chapter could not get from the wire. With the master's own transaction state alongside the framing events, "the bus was released mid-transaction" becomes a verdict rather than an observation — and the chapter defines atomicity precisely enough to enumerate everything that can break it.

Three things end bus ownership: a release, an arbitration loss (Module 13), and an abandonment where a master stops without a final STOP. One thing that looks as though it should does not: clock stretching, where a slave holding SCL low is the bus being owned rather than lost — the case most often got wrong, and the reason the tracker takes it as an explicit input and deliberately ignores it.

There is also a fourth condition the chapter can express that this one cannot: whether the register pointer written in phase 1 is still trustworthy. That turns out to depend not on whether ownership was lost, but on when — and the distinction is worth one flip-flop.

Continue learning