Skip to content
VLSI Mentor

I²C · Module 11

Why I²C Timing Parameters Exist

Before any parameter is named, separate the three independent questions a single bit has to answer — what it means, when it may change, and how long it must hold. Sixteen numbers in Table 10 are answers to those three questions.

Ten modules have treated time as something that simply works. A bit is a level while SCL is high; SDA may change while SCL is low; a byte is nine pulses. All true, all sufficient to build a write, a read and a combined transaction — and all silent about how long any of it has to last.

Module 11 supplies the numbers. It is the specification's timing table worked through one parameter group at a time, and this chapter is the frame the rest hang on, because the numbers are unintelligible until you know what they are numbers for.

The organising claim is this: a bit has to answer three questions, and they fail independently.

1. Three Questions, Not One

Take a single bit on the wire and ask what could be wrong with it.

#questionwhat settles ithow it fails
1What does it mean?the level of SDA while SCL is highSDA moved during the high phase, so there is no single level
2When may it change?only while SCL is lowSDA moved while SCL was high — and that edge is reserved
3How long must it hold?stable before the rise, and through the whole high phaseit was stable, but not for long enough

These are not three views of one requirement. A bit can be perfectly stable and arrive too late (question 3 fails, 1 and 2 pass). It can be stable for a long time and then move mid-phase (1 and 2 fail, 3 passes). It can be entirely well-defined on a clock whose high phase was too short for any receiver to use (3 fails on a different term).

That independence is why Table 10 has sixteen entries rather than one. Each entry constrains one way of getting time wrong, and the reason the table looks intimidating is that people try to read it as a list of numbers instead of as answers to a small number of questions.

2. What the Specification Actually Constrains

It is worth being precise about the shape of Table 10 before reading any of it, because two structural features surprise people.

Almost every entry is a MINIMUM. tLOW, tHIGH, tSU;DAT, tSU;STA, tHD;STA, tSU;STO, tBUF — all minima. A device that waits longer is always compliant. This is why clock stretching is legal (Module 12) and why a bus can idle for hours: the table almost never tells you to hurry.

The exceptions are the interesting ones. Four entries are maxima, and each is a promise rather than a permission:

symbolwhat it limitswhose promise
fSCLthe clock frequency — equivalently a minimum periodthe master's
tVD;DAT, tVD;ACKhow long a transmitter may take to produce a bitthe transmitter's
tr, tfhow long an edge may takethe board's
tSPthe widest pulse an input must rejectthe receiver's

tSP is the odd one in that list and worth flagging now: it constrains what a device must tolerate, not what it may produce. Chapter 11.8 is about that inversion.

And one entry constrains neither a device nor a transfer:

tBUF spans the gap between transfers, so it is the only parameter about the interval in which nobody owns the bus. Chapter 10.2 measured that interval for a different reason; Chapter 11.6 prices it.

3. A Bit Cell, Drawn

Here is one bit with the three questions marked against it.

A bit cell and the three questions it must answer

10 cycles
Ten intervals showing one bit cell. SCL is low for the first four intervals, high for the next four, and low again for the last two. SDA changes during the second interval while SCL is low, which is legal, then holds its new value through the setup interval and the whole high phase. Marker annotations identify the setup interval before the rising edge, the level during the high phase, and the point at which the value may next change.SCL low: change allowedSCL low: change allowedSCL high: must not moveSCL high: must not movelow againlow againQ2: may change, SCL is lowQ2: may change, SCL is lowQ3: stable before the riseQ3: stable before the riseQ1: the level IS the bitQ1: the level IS the bitsclsdaquestion0Q2Q3Q3Q1Q1Q1Q1Q1Q2t0t1t2t3t4t5t6t7t8t9
One bit cell. The three questions are answered at three different places: the level during the high phase, the absence of an edge within it, and the stability before the rising edge.

This figure uses kind: "signal" for SCL rather than kind: "clock", and that is deliberate: the point of the drawing is that SCL's level at each moment is what licenses or forbids an SDA change, so the figure has to show a particular level per interval rather than a generic square wave. A clock row would render a uniform period and the claims in the markers would no longer be about anything.

Read the question row across and the structure of the chapter is visible: Q2 applies at the moments SDA moves, Q3 to the interval before the rise, Q1 to the high phase itself. Three questions, three regions, no overlap.

4. What a Violation Actually Does

It is easy to treat a timing violation as an abstraction. It is not; each of the three failures does something specific and different to a receiver.

Question 1 fails — SDA moved during the high phase. The receiver samples at some point in that phase and gets whichever level happened to be present. Two receivers on the same bus can disagree about the same bit, which is the worst property a failure can have: the bus is no longer a single source of truth, and two devices' views of the same transfer diverge with nothing reporting a problem.

Question 2 fails — the edge was while SCL was high. Chapter 5.1 established that such an edge is reserved for framing, so the failure is not a corrupt bit. It is a spurious START or STOP inside a byte, and the consequence is structural: every device resets its bus logic and expects an address (Chapter 10.2 quotes the note), so the rest of the transfer is reinterpreted from scratch.

Question 3 fails — too little setup. The receiver's input stage samples a line that is still settling. What it gets is not a wrong value, it is an undefined one — and on a bus with several receivers, they may resolve it differently. This is the marginal failure: it works at room temperature, on that board, with that pull-up, and stops working when any of those change.

5. Measuring Time in Hardware

Before the design, one practical point that governs every block in this module.

Everything is counted in ticks of a sample clock. The specification is in microseconds and nanoseconds; the hardware has a system clock. So a user converts the table once — at 100 MHz one tick is 10 ns, and Fast-mode's tSU;DAT of 100 ns becomes 10 ticks — and from then on the logic never deals in nanoseconds. That is why every parameter in this module is a tick count, and why the testbenches state their sample rate in a header comment.

The resolution of the measurement is one tick. A 100 MHz sampler cannot tell 95 ns from 99 ns. For Fast-mode's 100 ns setup that is a 10 % granularity, which is enough to catch a gross violation and not enough for compliance testing — compliance is a scope measurement. What a hardware checker is for is continuous monitoring of a working system, where the question is "did anything go badly wrong in the last hour" rather than "is this margin 4 ns or 6 ns".

And a measurement must include the cycle in which it is taken. This sounds pedantic and is not: reading a counter register gives its value as of the previous edge, so every interval comes out one tick short, and every comparison against a minimum is shifted by one sample period. §6a records how that was found.

6. The Bit-Cell Classifier in Three Languages

The design watches the two wires and, for every SCL high phase, reports the three verdicts plus the two measurements they rest on.

outputquestionmeaning
cell_value1the level sampled at the rising edge
viol_meaning1SDA moved during the high phase, so there is no single level
edge_while_high2a fact, reported without judgement — see §6a
cell_su_ticks, viol_setup3how long SDA was stable before the rise
cell_high_ticks, viol_high_short3how long the high phase lasted
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_cell_checker.sv — the bit-cell classifier: three questions, three verdicts
   // A BIT-CELL classifier. It watches SDA and SCL and, for every SCL high phase, answers
   // the three questions that between them decide whether a bit is legal:
   //
   //   1. WHAT DOES IT MEAN?      the level of SDA while SCL is HIGH
   //   2. WHEN MAY IT CHANGE?     only while SCL is LOW
   //   3. HOW LONG MUST IT HOLD?  stable for tSU;DAT before the rise, and through the
   //                              whole high phase
   //
   // Those are three separate obligations and a real capture can fail any one of them
   // independently, which is why this block reports three separate verdicts rather than
   // one "bit bad" flag. Chapters 11.3 and 11.4 attach the specification's numbers to
   // questions 3 and 2; this block is the frame they hang on.
   //
   // It is PASSIVE: it observes the two wires and drives nothing.
   //
   // One thing it deliberately does NOT do is decide whether an SDA edge during the high
   // phase is an error. Chapter 5.1 established that such an edge is RESERVED for framing,
   // so it is either a START/STOP (legal, and not a bit at all) or a corrupt bit. Only a
   // frame decoder knows which. So the block reports the FACT -- edge_while_high -- and
   // leaves the interpretation to its consumer.
   module i2c_bit_cell_checker #(
       parameter int TICK_W = 16,
       // tSU;DAT, in sample-clock ticks. Fast-mode is 100 ns, so 10 ticks at 100 MHz.
       parameter int T_SU_DAT = 10,
       // tHIGH minimum, in ticks. Fast-mode is 0.6 us, so 60 ticks at 100 MHz.
       parameter int T_HIGH_MIN = 60
   )(
       input  logic clk,
       input  logic rst_n,
       // Both wires, already synchronised and filtered by the input stage of Module 4.
       input  logic sda_in,
       input  logic scl_in,

       // ---- the cell, reported at the END of each high phase ----
       output logic            cell_valid,      // pulse: a high phase just ended
       output logic            cell_value,      // Q1: the level sampled at the rising edge
       output logic [TICK_W-1:0] cell_high_ticks,  // how long the high phase lasted
       output logic [TICK_W-1:0] cell_su_ticks,    // how long SDA was stable before the rise

       // ---- the three verdicts, one per question ----
       // Q1 fails when the level cannot be determined: SDA moved during the high phase, so
       // there is no single level the cell "means".
       output logic            viol_meaning,
       // Q2 fails when SDA changed while SCL was HIGH. Reported as a fact by
       // edge_while_high and as a violation only once the consumer says it was a data cell.
       output logic            edge_while_high,
       // Q3 fails when SDA was not stable for T_SU_DAT before the rising edge, or when the
       // high phase itself was shorter than T_HIGH_MIN.
       output logic            viol_setup,
       output logic            viol_high_short,

       // ---- running totals, so a capture can be summarised ----
       output logic [TICK_W-1:0] n_cells,
       output logic [TICK_W-1:0] n_bad
   );
       logic sda_q, scl_q;
       logic scl_rise, scl_fall, sda_changed;

       assign scl_rise    = !scl_q &&  scl_in;
       assign scl_fall    =  scl_q && !scl_in;
       assign sda_changed = sda_q != sda_in;

       // How long SDA has been stable, counted continuously. At the rising edge of SCL this
       // counter IS tSU;DAT as observed -- which is the cheapest possible way to measure a
       // setup time: do not start a timer when the edge arrives, because by then it is too
       // late. Run the timer always and sample it.
       logic [TICK_W-1:0] stable_ticks;
       logic [TICK_W-1:0] high_ticks;

       // A measurement must INCLUDE the sample period in which it is taken. Reading the
       // register directly gives the count as of the PREVIOUS edge, which under-reports
       // every interval by exactly one tick -- a systematic error that shifts every
       // comparison against a spec minimum by one sample period, and one that only a test
       // at the exact boundary will find. So both counters are sampled through a
       // combinational "including this cycle" value.
       logic [TICK_W-1:0] stable_now, high_now;
       assign stable_now = sda_changed ? '0 : (stable_ticks + 1'b1);
       assign high_now   = high_ticks + 1'b1;

       // Latched during the high phase, reported when it ends.
       logic in_high;
       logic moved_during_high;
       logic value_at_rise;
       logic [TICK_W-1:0] su_at_rise;

       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;
               stable_ticks      <= '0;
               high_ticks        <= '0;
               in_high           <= 1'b0;
               moved_during_high <= 1'b0;
               value_at_rise     <= 1'b1;
               su_at_rise        <= '0;
               cell_valid        <= 1'b0;
               cell_value        <= 1'b1;
               cell_high_ticks   <= '0;
               cell_su_ticks     <= '0;
               viol_meaning      <= 1'b0;
               edge_while_high   <= 1'b0;
               viol_setup        <= 1'b0;
               viol_high_short   <= 1'b0;
               n_cells           <= '0;
               n_bad             <= '0;
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               cell_valid      <= 1'b0;
               edge_while_high <= 1'b0;

               // ---- the always-running stability timer ----
               stable_ticks <= stable_now;

               if (scl_rise) begin
                   // The rising edge is where Q1 is answered and Q3's setup half is judged.
                   in_high           <= 1'b1;
                   high_ticks        <= '0;
                   moved_during_high <= 1'b0;
                   value_at_rise     <= sda_in;
                   su_at_rise        <= stable_now;
               end else if (scl_fall) begin
                   if (in_high) begin
                       // The cell is over. Report everything at once, so a consumer sees a
                       // complete verdict rather than having to assemble one.
                       in_high         <= 1'b0;
                       cell_valid      <= 1'b1;
                       cell_value      <= value_at_rise;
                       cell_high_ticks <= high_now;
                       cell_su_ticks   <= su_at_rise;

                       // Q1: if SDA moved during the high phase there is no single level
                       // this cell means. That is a different failure from a short setup
                       // time -- the bit is not late, it is undefined.
                       viol_meaning    <= moved_during_high;
                       // Q3, both halves. The setup comparison is STRICTLY LESS THAN, so a
                       // measurement exactly equal to the minimum passes: the spec value is
                       // a minimum, and a design that rejected the boundary would reject
                       // legal traffic.
                       viol_setup      <= (su_at_rise < T_SU_DAT[TICK_W-1:0]);
                       viol_high_short <= (high_now < T_HIGH_MIN[TICK_W-1:0]);

                       n_cells <= n_cells + 1'b1;
                       if (moved_during_high
                           || (su_at_rise < T_SU_DAT[TICK_W-1:0])
                           || (high_now < T_HIGH_MIN[TICK_W-1:0]))
                           n_bad <= n_bad + 1'b1;
                   end
               end else if (in_high) begin
                   high_ticks <= high_now;
                   if (sda_changed) begin
                       // Q2: SDA moved while SCL was high. A FACT, not yet a verdict --
                       // Chapter 5.1 reserves this edge for framing, so it is either a
                       // START/STOP or a corrupt bit, and only a frame decoder knows which.
                       moved_during_high <= 1'b1;
                       edge_while_high   <= 1'b1;
                   end
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_cell_checker_tb.sv — ten scenarios, both boundaries exact
   `timescale 1ns/1ps
   // The sample clock is 100 MHz, so one tick is 10 ns and Fast-mode's parameters are round
   // numbers: tSU;DAT = 100 ns = 10 ticks, tHIGH(min) = 0.6 us = 60 ticks. Every stimulus
   // below is written in TICKS and the expected results are derived from the parameters,
   // not from a table of constants -- so the tests check the comparison, not a transcription.
   module i2c_bit_cell_checker_tb;
       localparam int TICK_W     = 16;
       localparam int T_SU_DAT   = 10;    // Fast-mode tSU;DAT = 100 ns
       localparam int T_HIGH_MIN = 60;    // Fast-mode tHIGH   = 0.6 us

       logic clk = 1'b0;
       always #5 clk = ~clk;              // 100 MHz: one tick per 10 ns

       logic rst_n = 1'b0;
       logic sda_in = 1'b1, scl_in = 1'b1;

       logic cell_valid, cell_value, viol_meaning, edge_while_high;
       logic viol_setup, viol_high_short;
       logic [TICK_W-1:0] cell_high_ticks, cell_su_ticks, n_cells, n_bad;

       int errors = 0;
       int base;
       logic [TICK_W-1:0] cells_before, bad_before;

       i2c_bit_cell_checker #(.TICK_W(TICK_W), .T_SU_DAT(T_SU_DAT), .T_HIGH_MIN(T_HIGH_MIN))
         dut (.*);

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

       // ---- observation: capture each cell's verdict as it is reported ----------------
       logic [TICK_W-1:0] su_log [0:31];
       logic [TICK_W-1:0] hi_log [0:31];
       logic vm_log  [0:31];
       logic vs_log  [0:31];
       logic vh_log  [0:31];
       logic val_log [0:31];
       int n_log;
       always @(posedge clk) if (rst_n && cell_valid && n_log < 32) begin
           su_log[n_log]  = cell_su_ticks;
           hi_log[n_log]  = cell_high_ticks;
           vm_log[n_log]  = viol_meaning;
           vs_log[n_log]  = viol_setup;
           vh_log[n_log]  = viol_high_short;
           val_log[n_log] = cell_value;
           n_log++;
       end

       task automatic tick(input int n);
           begin
               repeat (n) @(negedge clk);
           end
       endtask

       // Drive one bit cell: present the value, wait `su` ticks, raise SCL for `hi` ticks,
       // then drop it and wait `lo` ticks. This is the cell in its three parts, so a test
       // can make any one of them illegal on its own.
       task automatic drive_cell(input logic v, input int su, input int hi, input int lo);
           begin
               scl_in = 1'b0;
               sda_in = v;      tick(su);
               scl_in = 1'b1;   tick(hi);
               scl_in = 1'b0;   tick(lo);
           end
       endtask

       initial begin
           tick(3);
           if (n_cells !== '0) begin $display("FAIL: n_cells out of reset"); errors++; end
           rst_n = 1'b1; tick(2);
           sda_in = 1'b1; scl_in = 1'b0; tick(5);

           // ---- 1: four LEGAL cells with generous margins. Nothing may be flagged, and
           //      the measured setup must be at least the parameter -- a checker that
           //      flagged legal traffic is the failure mode that gets a checker ignored.
           drive_cell(1'b0, 40, 80, 40);
           drive_cell(1'b1, 40, 80, 40);
           drive_cell(1'b0, 40, 80, 40);
           drive_cell(1'b1, 40, 80, 40);
           if (n_log !== 4) begin
               $display("FAIL: %0d cells reported, expected 4", n_log); errors++; end
           if (n_bad !== '0) begin
               $display("FAIL: %0d legal cells flagged bad", n_bad); errors++; end
           // Q1: the values must come back in order.
           if (val_log[0] !== 1'b0 || val_log[1] !== 1'b1
               || val_log[2] !== 1'b0 || val_log[3] !== 1'b1) begin
               $display("FAIL: cell values were %b%b%b%b, expected 0101",
                        val_log[0], val_log[1], val_log[2], val_log[3]); errors++; end
           // Q3: the high phase is measured, not assumed.
           if (hi_log[0] < 16'd75 || hi_log[0] > 16'd85) begin
               $display("FAIL: an 80-tick high phase measured %0d", hi_log[0]); errors++; end

           // ---- 2: a SHORT SETUP. SDA changes only 4 ticks before the rising edge, where
           //      tSU;DAT demands 10. Q3's setup half must fail and NOTHING ELSE may --
           //      the bit's value is perfectly well defined and the high phase is legal.
           begin
               base = n_log;
               drive_cell(1'b0, 4, 80, 40);
               if (vs_log[base] !== 1'b1) begin
                   $display("FAIL: a 4-tick setup was not flagged (tSU;DAT = %0d)", T_SU_DAT);
                   errors++; end
               if (vm_log[base] !== 1'b0) begin
                   $display("FAIL: a short setup also flagged the MEANING -- the two are different questions");
                   errors++; end
               if (vh_log[base] !== 1'b0) begin
                   $display("FAIL: a short setup also flagged a short high phase"); errors++; end
               if (su_log[base] > 16'd6) begin
                   $display("FAIL: a 4-tick setup measured %0d ticks", su_log[base]); errors++; end
           end

           // ---- 3: THE BOUNDARY. A setup of EXACTLY tSU;DAT is legal, because the spec
           //      value is a minimum. A checker that rejects the boundary rejects
           //      compliant traffic, which is worse than missing a violation.
           begin
               base = n_log;
               drive_cell(1'b1, T_SU_DAT, 80, 40);
               if (vs_log[base] !== 1'b0) begin
                   $display("FAIL: a setup of EXACTLY tSU;DAT (%0d ticks) was rejected", T_SU_DAT);
                   errors++; end
           end

           // ---- 4: one tick BELOW the boundary must fail. Testing the last legal value
           //      and the first illegal one is what pins down a comparison; either alone
           //      passes on a design that is off by one in the other direction.
           //
           //      The value is 0 because the PREVIOUS cell drove 1. A setup-time test must
           //      actually CHANGE the signal: driving the same value again produces no edge,
           //      so the stability timer keeps accumulating from the previous cell and the
           //      measured setup is the whole history rather than the interval under test.
           //      This test passed for that reason before the value was alternated.
           begin
               base = n_log;
               drive_cell(1'b0, T_SU_DAT - 1, 80, 40);
               if (vs_log[base] !== 1'b1) begin
                   $display("FAIL: a setup of tSU;DAT-1 (%0d ticks) was accepted", T_SU_DAT - 1);
                   errors++; end
           end

           // ---- 5: SDA MOVES DURING THE HIGH PHASE. Q2's fact must be reported and Q1's
           //      verdict must fail -- there is no single level this cell means. The setup
           //      was generous, so Q3 must NOT fail: the bit is not late, it is undefined.
           begin
               base = n_log;
               scl_in = 1'b0; sda_in = 1'b0; tick(40);
               scl_in = 1'b1;                tick(30);
               sda_in = 1'b1;                tick(30);   // an edge while SCL is HIGH
               scl_in = 1'b0;                tick(40);
               if (vm_log[base] !== 1'b1) begin
                   $display("FAIL: an SDA edge during the high phase did not fail the MEANING test");
                   errors++; end
               if (vs_log[base] !== 1'b0) begin
                   $display("FAIL: an undefined cell was also reported as a setup violation");
                   errors++; end
               if (val_log[base] !== 1'b0) begin
                   $display("FAIL: the reported value should be the level AT THE RISING EDGE (0)");
                   errors++; end
           end

           // ---- 5b: WHICH SAMPLE IS THE BIT. When SDA changes in the same cycle the rising
           //      edge is observed -- a zero setup time -- the block must report the level AT the
           //      edge, not the one before it. The choice has to be specified either way; what
           //      cannot happen is for it to be accidental, because then two implementations of
           //      the same checker disagree about what a marginal capture contained.
           //
           //      The setup is zero, so viol_setup must also fire: this is simultaneously the
           //      worst possible setup time and a well-defined level.
           begin
               base = n_log;
               scl_in = 1'b0; sda_in = 1'b1; tick(40);
               // one negedge: SDA changes and SCL rises together
               sda_in = 1'b0; scl_in = 1'b1; tick(80);
               scl_in = 1'b0; tick(40);
               if (val_log[base] !== 1'b0) begin
                   $display("FAIL: with SDA changing at the rising edge the reported value was %b -- the block sampled the PREVIOUS level, not the level at the edge",
                            val_log[base]); errors++; end
               if (vs_log[base] !== 1'b1) begin
                   $display("FAIL: a zero setup time was not flagged"); errors++; end
           end

           // ---- 6: a SHORT HIGH PHASE. 40 ticks where tHIGH(min) demands 60. Only Q3's
           //      high-phase half may fail.
           begin
               base = n_log;
               drive_cell(1'b0, 40, 40, 40);
               if (vh_log[base] !== 1'b1) begin
                   $display("FAIL: a 40-tick high phase was not flagged (tHIGH min = %0d)",
                            T_HIGH_MIN); errors++; end
               if (vm_log[base] !== 1'b0 || vs_log[base] !== 1'b0) begin
                   $display("FAIL: a short high phase also flagged another question"); errors++; end
           end

           // ---- 7: tHIGH exactly at the minimum is legal.
           begin
               base = n_log;
               drive_cell(1'b1, 40, T_HIGH_MIN, 40);
               if (vh_log[base] !== 1'b0) begin
                   $display("FAIL: a high phase of EXACTLY tHIGH(min) was rejected"); errors++; end
           end

           // ---- 8: THREE questions failing INDEPENDENTLY is the whole point of the
           //      block, so check that a cell can fail two of them at once and report both.
           begin
               base = n_log;
               scl_in = 1'b0; sda_in = 1'b0; tick(3);     // short setup ...
               scl_in = 1'b1;                tick(15);
               sda_in = 1'b1;                tick(15);    // ... AND an edge while high
               scl_in = 1'b0;                tick(40);    // ... AND a 30-tick high phase
               if (!(vs_log[base] && vm_log[base] && vh_log[base])) begin
                   $display("FAIL: a cell failing all three questions reported %b%b%b",
                            vs_log[base], vm_log[base], vh_log[base]); errors++; end
           end

           // ---- 9: an IDLE bus reports nothing. No SCL activity means no cells, and the
           //      bus spends most of its life here.
           begin
               cells_before = n_cells;
               sda_in = 1'b1; scl_in = 1'b1; tick(200);
               if (n_cells !== cells_before) begin
                   $display("FAIL: an idle bus produced %0d cells", n_cells - cells_before); errors++; end
           end

           // ---- 10: SCL activity with NO SDA movement at all -- a clock running on an
           //      idle-high SDA. Every cell is legal and reads 1. This is what a bus
           //      recovery sequence looks like, and it must not be flagged.
           begin
               bad_before = n_bad;
               sda_in = 1'b1;
               repeat (9) drive_cell(1'b1, 40, 80, 40);
               if (n_bad !== bad_before) begin
                   $display("FAIL: nine clean recovery pulses produced %0d violations",
                            n_bad - bad_before); errors++; end
           end

           if (errors == 0)
               $display("PASS: the three questions are judged independently, both boundaries are exact, an undefined cell is not a late cell");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_cell_checker.v — the same classifier in Verilog-2001
   // A BIT-CELL classifier.  (Verilog-2001) It watches SDA and SCL and, for every SCL high phase, answers
   // the three questions that between them decide whether a bit is legal:
   //
   //   1. WHAT DOES IT MEAN?      the level of SDA while SCL is HIGH
   //   2. WHEN MAY IT CHANGE?     only while SCL is LOW
   //   3. HOW LONG MUST IT HOLD?  stable for tSU;DAT before the rise, and through the
   //                              whole high phase
   //
   // Those are three separate obligations and a real capture can fail any one of them
   // independently, which is why this block reports three separate verdicts rather than
   // one "bit bad" flag. Chapters 11.3 and 11.4 attach the specification's numbers to
   // questions 3 and 2; this block is the frame they hang on.
   //
   // It is PASSIVE: it observes the two wires and drives nothing.
   //
   // One thing it deliberately does NOT do is decide whether an SDA edge during the high
   // phase is an error. Chapter 5.1 established that such an edge is RESERVED for framing,
   // so it is either a START/STOP (legal, and not a bit at all) or a corrupt bit. Only a
   // frame decoder knows which. So the block reports the FACT -- edge_while_high -- and
   // leaves the interpretation to its consumer.
   module i2c_bit_cell_checker #(
       parameter TICK_W = 16,
       // tSU;DAT, in sample-clock ticks. Fast-mode is 100 ns, so 10 ticks at 100 MHz.
       parameter T_SU_DAT = 10,
       // tHIGH minimum, in ticks. Fast-mode is 0.6 us, so 60 ticks at 100 MHz.
       parameter T_HIGH_MIN = 60
   )(
       input  wire  clk,
       input  wire  rst_n,
       // Both wires, already synchronised and filtered by the input stage of Module 4.
       input  wire  sda_in,
       input  wire  scl_in,

       // ---- the cell, reported at the END of each high phase ----
       output reg              cell_valid,      // pulse: a high phase just ended
       output reg              cell_value,      // Q1: the level sampled at the rising edge
       output reg   [TICK_W-1:0] cell_high_ticks,  // how long the high phase lasted
       output reg   [TICK_W-1:0] cell_su_ticks,    // how long SDA was stable before the rise

       // ---- the three verdicts, one per question ----
       // Q1 fails when the level cannot be determined: SDA moved during the high phase, so
       // there is no single level the cell "means".
       output reg              viol_meaning,
       // Q2 fails when SDA changed while SCL was HIGH. Reported as a fact by
       // edge_while_high and as a violation only once the consumer says it was a data cell.
       output reg              edge_while_high,
       // Q3 fails when SDA was not stable for T_SU_DAT before the rising edge, or when the
       // high phase itself was shorter than T_HIGH_MIN.
       output reg              viol_setup,
       output reg              viol_high_short,

       // ---- running totals, so a capture can be summarised ----
       output reg   [TICK_W-1:0] n_cells,
       output reg   [TICK_W-1:0] n_bad
   );
       reg sda_q, scl_q;
       wire scl_rise, scl_fall, sda_changed;

       assign scl_rise    = !scl_q &&  scl_in;
       assign scl_fall    =  scl_q && !scl_in;
       assign sda_changed = sda_q != sda_in;

       // How long SDA has been stable, counted continuously. At the rising edge of SCL this
       // counter IS tSU;DAT as observed -- which is the cheapest possible way to measure a
       // setup time: do not start a timer when the edge arrives, because by then it is too
       // late. Run the timer always and sample it.
       reg [TICK_W-1:0] stable_ticks;
       reg [TICK_W-1:0] high_ticks;

       // A measurement must INCLUDE the sample period in which it is taken. Reading the
       // register directly gives the count as of the PREVIOUS edge, which under-reports
       // every interval by exactly one tick -- a systematic error that shifts every
       // comparison against a spec minimum by one sample period, and one that only a test
       // at the exact boundary will find. So both counters are sampled through a
       // combinational "including this cycle" value.
       wire [TICK_W-1:0] stable_now, high_now;
       assign stable_now = sda_changed ? {TICK_W{1'b0}} : (stable_ticks + 1'b1);
       assign high_now   = high_ticks + 1'b1;

       // Latched during the high phase, reported when it ends.
       reg in_high;
       reg moved_during_high;
       reg value_at_rise;
       reg [TICK_W-1:0] su_at_rise;

       always @(posedge clk) begin
           if (!rst_n) begin
               sda_q             <= 1'b1;   // an idle bus is high on both wires
               scl_q             <= 1'b1;
               stable_ticks      <= {TICK_W{1'b0}};
               high_ticks        <= {TICK_W{1'b0}};
               in_high           <= 1'b0;
               moved_during_high <= 1'b0;
               value_at_rise     <= 1'b1;
               su_at_rise        <= {TICK_W{1'b0}};
               cell_valid        <= 1'b0;
               cell_value        <= 1'b1;
               cell_high_ticks   <= {TICK_W{1'b0}};
               cell_su_ticks     <= {TICK_W{1'b0}};
               viol_meaning      <= 1'b0;
               edge_while_high   <= 1'b0;
               viol_setup        <= 1'b0;
               viol_high_short   <= 1'b0;
               n_cells           <= {TICK_W{1'b0}};
               n_bad             <= {TICK_W{1'b0}};
           end else begin
               sda_q <= sda_in;
               scl_q <= scl_in;

               cell_valid      <= 1'b0;
               edge_while_high <= 1'b0;

               // ---- the always-running stability timer ----
               stable_ticks <= stable_now;

               if (scl_rise) begin
                   // The rising edge is where Q1 is answered and Q3's setup half is judged.
                   in_high           <= 1'b1;
                   high_ticks        <= {TICK_W{1'b0}};
                   moved_during_high <= 1'b0;
                   value_at_rise     <= sda_in;
                   su_at_rise        <= stable_now;
               end else if (scl_fall) begin
                   if (in_high) begin
                       // The cell is over. Report everything at once, so a consumer sees a
                       // complete verdict rather than having to assemble one.
                       in_high         <= 1'b0;
                       cell_valid      <= 1'b1;
                       cell_value      <= value_at_rise;
                       cell_high_ticks <= high_now;
                       cell_su_ticks   <= su_at_rise;

                       // Q1: if SDA moved during the high phase there is no single level
                       // this cell means. That is a different failure from a short setup
                       // time -- the bit is not late, it is undefined.
                       viol_meaning    <= moved_during_high;
                       // Q3, both halves. The setup comparison is STRICTLY LESS THAN, so a
                       // measurement exactly equal to the minimum passes: the spec value is
                       // a minimum, and a design that rejected the boundary would reject
                       // legal traffic.
                       viol_setup      <= (su_at_rise < T_SU_DAT);
                       viol_high_short <= (high_now < T_HIGH_MIN);

                       n_cells <= n_cells + 1'b1;
                       if (moved_during_high
                           || (su_at_rise < T_SU_DAT)
                           || (high_now < T_HIGH_MIN))
                           n_bad <= n_bad + 1'b1;
                   end
               end else if (in_high) begin
                   high_ticks <= high_now;
                   if (sda_changed) begin
                       // Q2: SDA moved while SCL was high. A FACT, not yet a verdict --
                       // Chapter 5.1 reserves this edge for framing, so it is either a
                       // START/STOP or a corrupt bit, and only a frame decoder knows which.
                       moved_during_high <= 1'b1;
                       edge_while_high   <= 1'b1;
                   end
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_cell_checker_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // The sample clock is 100 MHz, so one tick is 10 ns and Fast-mode's parameters are round
   // numbers: tSU;DAT = 100 ns = 10 ticks, tHIGH(min) = 0.6 us = 60 ticks. Every stimulus
   // below is written in TICKS and the expected results are derived from the parameters,
   // not from a table of constants -- so the tests check the comparison, not a transcription.
   module i2c_bit_cell_checker_tb;   // Verilog-2001
       localparam TICK_W     = 16;
       localparam T_SU_DAT   = 10;    // Fast-mode tSU;DAT = 100 ns
       localparam T_HIGH_MIN = 60;    // Fast-mode tHIGH   = 0.6 us

       reg clk = 1'b0;
       always #5 clk = ~clk;              // 100 MHz: one tick per 10 ns

       reg rst_n = 1'b0;
       reg sda_in = 1'b1, scl_in = 1'b1;

       wire cell_valid, cell_value, viol_meaning, edge_while_high;
       wire viol_setup, viol_high_short;
       wire [TICK_W-1:0] cell_high_ticks, cell_su_ticks, n_cells, n_bad;

       integer errors = 0;
       integer base = 0;
       reg [TICK_W-1:0] cells_before, bad_before;

       i2c_bit_cell_checker #(.TICK_W(TICK_W), .T_SU_DAT(T_SU_DAT), .T_HIGH_MIN(T_HIGH_MIN))
         dut (
           .clk(clk), .rst_n(rst_n), .sda_in(sda_in), .scl_in(scl_in), .cell_valid(cell_valid),
           .cell_value(cell_value), .cell_high_ticks(cell_high_ticks),
           .cell_su_ticks(cell_su_ticks), .viol_meaning(viol_meaning),
           .edge_while_high(edge_while_high), .viol_setup(viol_setup),
           .viol_high_short(viol_high_short), .n_cells(n_cells), .n_bad(n_bad));

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

       // ---- observation: capture each cell's verdict as it is reported ----------------
       reg [TICK_W-1:0] su_log [0:31];
       reg [TICK_W-1:0] hi_log [0:31];
       reg vm_log [0:31];
       reg vs_log [0:31];
       reg vh_log [0:31];
       reg val_log [0:31];
       integer n_log = 0;
       always @(posedge clk) if (rst_n && cell_valid && n_log < 32) begin
           su_log[n_log]  = cell_su_ticks;
           hi_log[n_log]  = cell_high_ticks;
           vm_log[n_log]  = viol_meaning;
           vs_log[n_log]  = viol_setup;
           vh_log[n_log]  = viol_high_short;
           val_log[n_log] = cell_value;
           n_log = n_log + 1;
       end

       task tick;
           input integer n;
       begin
               repeat (n) @(negedge clk);
               end
       endtask

       // Drive one bit cell: present the value, wait `su` ticks, raise SCL for `hi` ticks,
       // then drop it and wait `lo` ticks. This is the cell in its three parts, so a test
       // can make any one of them illegal on its own.
       task drive_cell;
           input v;
           input integer su;
           input integer hi;
           input integer lo;
       begin
               scl_in = 1'b0;
               sda_in = v;      tick(su);
               scl_in = 1'b1;   tick(hi);
               scl_in = 1'b0;   tick(lo);
               end
       endtask

       initial begin
           tick(3);
           if (n_cells !== {TICK_W{1'b0}}) begin $display("FAIL: n_cells out of reset"); errors = errors + 1; end
           rst_n = 1'b1; tick(2);
           sda_in = 1'b1; scl_in = 1'b0; tick(5);

           // ---- 1: four LEGAL cells with generous margins. Nothing may be flagged, and
           //      the measured setup must be at least the parameter -- a checker that
           //      flagged legal traffic is the failure mode that gets a checker ignored.
           drive_cell(1'b0, 40, 80, 40);
           drive_cell(1'b1, 40, 80, 40);
           drive_cell(1'b0, 40, 80, 40);
           drive_cell(1'b1, 40, 80, 40);
           if (n_log !== 4) begin
               $display("FAIL: %0d cells reported, expected 4", n_log); errors = errors + 1; end
           if (n_bad !== {TICK_W{1'b0}}) begin
               $display("FAIL: %0d legal cells flagged bad", n_bad); errors = errors + 1; end
           // Q1: the values must come back in order.
           if (val_log[0] !== 1'b0 || val_log[1] !== 1'b1
               || val_log[2] !== 1'b0 || val_log[3] !== 1'b1) begin
               $display("FAIL: cell values were %b%b%b%b, expected 0101",
                        val_log[0], val_log[1], val_log[2], val_log[3]); errors = errors + 1; end
           // Q3: the high phase is measured, not assumed.
           if (hi_log[0] < 16'd75 || hi_log[0] > 16'd85) begin
               $display("FAIL: an 80-tick high phase measured %0d", hi_log[0]); errors = errors + 1; end

           // ---- 2: a SHORT SETUP. SDA changes only 4 ticks before the rising edge, where
           //      tSU;DAT demands 10. Q3's setup half must fail and NOTHING ELSE may --
           //      the bit's value is perfectly well defined and the high phase is legal.
           begin
               base = n_log;
               drive_cell(1'b0, 4, 80, 40);
               if (vs_log[base] !== 1'b1) begin
                   $display("FAIL: a 4-tick setup was not flagged (tSU;DAT = %0d)", T_SU_DAT);
                   errors = errors + 1; end
               if (vm_log[base] !== 1'b0) begin
                   $display("FAIL: a short setup also flagged the MEANING -- the two are different questions");
                   errors = errors + 1; end
               if (vh_log[base] !== 1'b0) begin
                   $display("FAIL: a short setup also flagged a short high phase"); errors = errors + 1; end
               if (su_log[base] > 16'd6) begin
                   $display("FAIL: a 4-tick setup measured %0d ticks", su_log[base]); errors = errors + 1; end
           end

           // ---- 3: THE BOUNDARY. A setup of EXACTLY tSU;DAT is legal, because the spec
           //      value is a minimum. A checker that rejects the boundary rejects
           //      compliant traffic, which is worse than missing a violation.
           begin
               base = n_log;
               drive_cell(1'b1, T_SU_DAT, 80, 40);
               if (vs_log[base] !== 1'b0) begin
                   $display("FAIL: a setup of EXACTLY tSU;DAT (%0d ticks) was rejected", T_SU_DAT);
                   errors = errors + 1; end
           end

           // ---- 4: one tick BELOW the boundary must fail. Testing the last legal value
           //      and the first illegal one is what pins down a comparison; either alone
           //      passes on a design that is off by one in the other direction.
           //
           //      The value is 0 because the PREVIOUS cell drove 1. A setup-time test must
           //      actually CHANGE the signal: driving the same value again produces no edge,
           //      so the stability timer keeps accumulating from the previous cell and the
           //      measured setup is the whole history rather than the interval under test.
           //      This test passed for that reason before the value was alternated.
           begin
               base = n_log;
               drive_cell(1'b0, T_SU_DAT - 1, 80, 40);
               if (vs_log[base] !== 1'b1) begin
                   $display("FAIL: a setup of tSU;DAT-1 (%0d ticks) was accepted", T_SU_DAT - 1);
                   errors = errors + 1; end
           end

           // ---- 5: SDA MOVES DURING THE HIGH PHASE. Q2's fact must be reported and Q1's
           //      verdict must fail -- there is no single level this cell means. The setup
           //      was generous, so Q3 must NOT fail: the bit is not late, it is undefined.
           begin
               base = n_log;
               scl_in = 1'b0; sda_in = 1'b0; tick(40);
               scl_in = 1'b1;                tick(30);
               sda_in = 1'b1;                tick(30);   // an edge while SCL is HIGH
               scl_in = 1'b0;                tick(40);
               if (vm_log[base] !== 1'b1) begin
                   $display("FAIL: an SDA edge during the high phase did not fail the MEANING test");
                   errors = errors + 1; end
               if (vs_log[base] !== 1'b0) begin
                   $display("FAIL: an undefined cell was also reported as a setup violation");
                   errors = errors + 1; end
               if (val_log[base] !== 1'b0) begin
                   $display("FAIL: the reported value should be the level AT THE RISING EDGE (0)");
                   errors = errors + 1; end
           end

           // ---- 5b: WHICH SAMPLE IS THE BIT. When SDA changes in the same cycle the rising
           //      edge is observed -- a zero setup time -- the block must report the level AT the
           //      edge, not the one before it. The choice has to be specified either way; what
           //      cannot happen is for it to be accidental, because then two implementations of
           //      the same checker disagree about what a marginal capture contained.
           //
           //      The setup is zero, so viol_setup must also fire: this is simultaneously the
           //      worst possible setup time and a well-defined level.
           begin
               base = n_log;
               scl_in = 1'b0; sda_in = 1'b1; tick(40);
               // one negedge: SDA changes and SCL rises together
               sda_in = 1'b0; scl_in = 1'b1; tick(80);
               scl_in = 1'b0; tick(40);
               if (val_log[base] !== 1'b0) begin
                   $display("FAIL: with SDA changing at the rising edge the reported value was %b -- the block sampled the PREVIOUS level, not the level at the edge",
                            val_log[base]); errors = errors + 1; end
               if (vs_log[base] !== 1'b1) begin
                   $display("FAIL: a zero setup time was not flagged"); errors = errors + 1; end
           end

           // ---- 6: a SHORT HIGH PHASE. 40 ticks where tHIGH(min) demands 60. Only Q3's
           //      high-phase half may fail.
           begin
               base = n_log;
               drive_cell(1'b0, 40, 40, 40);
               if (vh_log[base] !== 1'b1) begin
                   $display("FAIL: a 40-tick high phase was not flagged (tHIGH min = %0d)",
                            T_HIGH_MIN); errors = errors + 1; end
               if (vm_log[base] !== 1'b0 || vs_log[base] !== 1'b0) begin
                   $display("FAIL: a short high phase also flagged another question"); errors = errors + 1; end
           end

           // ---- 7: tHIGH exactly at the minimum is legal.
           begin
               base = n_log;
               drive_cell(1'b1, 40, T_HIGH_MIN, 40);
               if (vh_log[base] !== 1'b0) begin
                   $display("FAIL: a high phase of EXACTLY tHIGH(min) was rejected"); errors = errors + 1; end
           end

           // ---- 8: THREE questions failing INDEPENDENTLY is the whole point of the
           //      block, so check that a cell can fail two of them at once and report both.
           begin
               base = n_log;
               scl_in = 1'b0; sda_in = 1'b0; tick(3);     // short setup ...
               scl_in = 1'b1;                tick(15);
               sda_in = 1'b1;                tick(15);    // ... AND an edge while high
               scl_in = 1'b0;                tick(40);    // ... AND a 30-tick high phase
               if (!(vs_log[base] && vm_log[base] && vh_log[base])) begin
                   $display("FAIL: a cell failing all three questions reported %b%b%b",
                            vs_log[base], vm_log[base], vh_log[base]); errors = errors + 1; end
           end

           // ---- 9: an IDLE bus reports nothing. No SCL activity means no cells, and the
           //      bus spends most of its life here.
           begin
               cells_before = n_cells;
               sda_in = 1'b1; scl_in = 1'b1; tick(200);
               if (n_cells !== cells_before) begin
                   $display("FAIL: an idle bus produced %0d cells", n_cells - cells_before); errors = errors + 1; end
           end

           // ---- 10: SCL activity with NO SDA movement at all -- a clock running on an
           //      idle-high SDA. Every cell is legal and reads 1. This is what a bus
           //      recovery sequence looks like, and it must not be flagged.
           begin
               bad_before = n_bad;
               sda_in = 1'b1;
               repeat (9) drive_cell(1'b1, 40, 80, 40);
               if (n_bad !== bad_before) begin
                   $display("FAIL: nine clean recovery pulses produced %0d violations",
                            n_bad - bad_before); errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: the three questions are judged independently, both boundaries are exact, an undefined cell is not a late cell");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_cell_checker.vhd — the same classifier in VHDL
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- A BIT-CELL classifier. For every SCL high phase it answers the three questions that
   -- between them decide whether a bit is legal:
   --
   --   1. WHAT DOES IT MEAN?      the level of SDA while SCL is HIGH
   --   2. WHEN MAY IT CHANGE?     only while SCL is LOW
   --   3. HOW LONG MUST IT HOLD?  stable for tSU;DAT before the rise, and through the whole
   --                              high phase
   --
   -- Those are three separate obligations and a real capture can fail any one independently,
   -- which is why this block reports three separate verdicts rather than one "bit bad" flag.
   --
   -- It deliberately does NOT decide whether an SDA edge during the high phase is an error.
   -- Chapter 5.1 established that such an edge is RESERVED for framing, so it is either a
   -- START/STOP (legal, and not a bit at all) or a corrupt bit -- and only a frame decoder knows
   -- which. The block reports the FACT, edge_while_high, and leaves the interpretation to its
   -- consumer.
   --
   -- PASSIVE: observes the two wires and drives nothing.
   entity i2c_bit_cell_checker is
       generic (
           TICK_W : positive := 16;
           -- tSU;DAT in sample-clock ticks. Fast-mode is 100 ns, so 10 ticks at 100 MHz.
           T_SU_DAT : natural := 10;
           -- tHIGH minimum in ticks. Fast-mode is 0.6 us, so 60 ticks at 100 MHz.
           T_HIGH_MIN : natural := 60
       );
       port (
           clk    : in std_logic;
           rst_n  : in std_logic;
           -- Both wires, already synchronised and filtered by the input stage of Module 4.
           sda_in : in std_logic;
           scl_in : in std_logic;

           -- the cell, reported at the END of each high phase
           cell_valid      : out std_logic;
           cell_value      : out std_logic;
           cell_high_ticks : out unsigned(TICK_W - 1 downto 0);
           cell_su_ticks   : out unsigned(TICK_W - 1 downto 0);

           -- the three verdicts, one per question
           viol_meaning    : out std_logic;   -- Q1: no single level; SDA moved during the phase
           edge_while_high : out std_logic;   -- Q2: a FACT, not yet a verdict
           viol_setup      : out std_logic;   -- Q3a: shorter than tSU;DAT
           viol_high_short : out std_logic;   -- Q3b: the high phase itself was too short

           n_cells : out unsigned(TICK_W - 1 downto 0);
           n_bad   : out unsigned(TICK_W - 1 downto 0)
       );
   end entity;

   architecture rtl of i2c_bit_cell_checker is
       signal sda_q, scl_q : std_logic := '1';
       signal scl_rise, scl_fall, sda_changed : std_logic;

       signal stable_ticks, high_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');

       -- A measurement must INCLUDE the sample period in which it is taken. Reading the register
       -- directly gives the count as of the PREVIOUS edge, which under-reports every interval by
       -- exactly one tick -- a systematic error that shifts every comparison against a spec
       -- minimum by one sample period, and one that only a test at the exact boundary will find.
       signal stable_now, high_now : unsigned(TICK_W - 1 downto 0);

       signal in_high, moved_during_high, value_at_rise : std_logic := '0';
       signal su_at_rise : unsigned(TICK_W - 1 downto 0) := (others => '0');
   begin
       scl_rise    <= (not scl_q) and scl_in;
       scl_fall    <= scl_q and (not scl_in);
       sda_changed <= '1' when sda_q /= sda_in else '0';

       -- How long SDA has been stable, counted continuously. At the rising edge of SCL this
       -- counter IS tSU;DAT as observed -- the cheapest way to measure a setup time: do not start
       -- a timer when the edge arrives, because by then the interval is over. Run it always.
       stable_now <= (others => '0') when sda_changed = '1' else stable_ticks + 1;
       high_now   <= high_ticks + 1;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   sda_q             <= '1';   -- an idle bus is high on both wires
                   scl_q             <= '1';
                   stable_ticks      <= (others => '0');
                   high_ticks        <= (others => '0');
                   in_high           <= '0';
                   moved_during_high <= '0';
                   value_at_rise     <= '1';
                   su_at_rise        <= (others => '0');
                   cell_valid        <= '0';
                   cell_value        <= '1';
                   cell_high_ticks   <= (others => '0');
                   cell_su_ticks     <= (others => '0');
                   viol_meaning      <= '0';
                   edge_while_high   <= '0';
                   viol_setup        <= '0';
                   viol_high_short   <= '0';
                   n_cells           <= (others => '0');
                   n_bad             <= (others => '0');
               else
                   sda_q <= sda_in;
                   scl_q <= scl_in;

                   cell_valid      <= '0';
                   edge_while_high <= '0';

                   stable_ticks <= stable_now;

                   if scl_rise = '1' then
                       -- The rising edge is where Q1 is answered and Q3's setup half is judged.
                       in_high           <= '1';
                       high_ticks        <= (others => '0');
                       moved_during_high <= '0';
                       value_at_rise     <= sda_in;
                       su_at_rise        <= stable_now;
                   elsif scl_fall = '1' then
                       if in_high = '1' then
                           -- The cell is over. Report everything at once, so a consumer sees a
                           -- complete verdict rather than having to assemble one.
                           in_high         <= '0';
                           cell_valid      <= '1';
                           cell_value      <= value_at_rise;
                           cell_high_ticks <= high_now;
                           cell_su_ticks   <= su_at_rise;

                           -- Q1: if SDA moved during the high phase there is no single level
                           -- this cell means. A different failure from a short setup time --
                           -- the bit is not late, it is undefined.
                           viol_meaning <= moved_during_high;

                           -- Q3, both halves. The comparisons are STRICTLY LESS THAN, so a
                           -- measurement exactly equal to a specified minimum passes: the
                           -- table's values are minima, and rejecting the boundary would reject
                           -- compliant traffic.
                           if su_at_rise < to_unsigned(T_SU_DAT, TICK_W) then
                               viol_setup <= '1';
                           else
                               viol_setup <= '0';
                           end if;
                           if high_now < to_unsigned(T_HIGH_MIN, TICK_W) then
                               viol_high_short <= '1';
                           else
                               viol_high_short <= '0';
                           end if;

                           n_cells <= n_cells + 1;
                           if moved_during_high = '1'
                              or su_at_rise < to_unsigned(T_SU_DAT, TICK_W)
                              or high_now < to_unsigned(T_HIGH_MIN, TICK_W) then
                               n_bad <= n_bad + 1;
                           end if;
                       end if;
                   elsif in_high = '1' then
                       high_ticks <= high_now;
                       if sda_changed = '1' then
                           -- Q2: SDA moved while SCL was high. A FACT, not yet a verdict.
                           moved_during_high <= '1';
                           edge_while_high   <= '1';
                       end if;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_bit_cell_checker_tb.vhd — the VHDL testbench, single-writer throughout
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- 100 MHz sample clock, so one tick is 10 ns and Fast-mode's parameters are round numbers:
   -- tSU;DAT = 100 ns = 10 ticks, tHIGH(min) = 0.6 us = 60 ticks. Every stimulus is written in
   -- TICKS and every expectation is derived from the generics rather than from constants, so the
   -- tests check the comparisons and not a transcription.
   entity i2c_bit_cell_checker_tb is
   end entity;

   architecture sim of i2c_bit_cell_checker_tb is
       constant TICK_W     : positive := 16;
       constant T_SU_DAT   : natural  := 10;
       constant T_HIGH_MIN : natural  := 60;

       signal clk   : std_logic := '0';
       signal rst_n : std_logic := '0';
       signal sda_in, scl_in : std_logic := '1';

       signal cell_valid, cell_value, viol_meaning, edge_while_high : std_logic;
       signal viol_setup, viol_high_short : std_logic;
       signal cell_high_ticks, cell_su_ticks : unsigned(TICK_W - 1 downto 0);
       signal n_cells, n_bad : unsigned(TICK_W - 1 downto 0);

       -- observation, owned solely by the observe process
       type tick_arr is array (0 to 31) of unsigned(TICK_W - 1 downto 0);
       type bit_arr  is array (0 to 31) of std_logic;
       signal su_log, hi_log : tick_arr := (others => (others => '0'));
       signal vm_log, vs_log, vh_log, val_log : bit_arr := (others => '0');
       signal n_log : natural := 0;

       signal test_done : std_logic := '0';
   begin
       dut : entity work.i2c_bit_cell_checker
           generic map (TICK_W => TICK_W, T_SU_DAT => T_SU_DAT, T_HIGH_MIN => T_HIGH_MIN)
           port map (clk => clk, rst_n => rst_n, sda_in => sda_in, scl_in => scl_in,
                     cell_valid => cell_valid, cell_value => cell_value,
                     cell_high_ticks => cell_high_ticks, cell_su_ticks => cell_su_ticks,
                     viol_meaning => viol_meaning, edge_while_high => edge_while_high,
                     viol_setup => viol_setup, viol_high_short => viol_high_short,
                     n_cells => n_cells, n_bad => n_bad);

       clk <= not clk after 5 ns;

       watchdog : process
       begin
           wait for 500 us;
           if test_done = '0' then report "watchdog expired" severity failure; end if;
           wait;
       end process;

       observe : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and cell_valid = '1' and n_log < 32 then
               su_log(n_log)  <= cell_su_ticks;
               hi_log(n_log)  <= cell_high_ticks;
               vm_log(n_log)  <= viol_meaning;
               vs_log(n_log)  <= viol_setup;
               vh_log(n_log)  <= viol_high_short;
               val_log(n_log) <= cell_value;
               n_log          <= n_log + 1;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable base : natural;
           variable cells_before, bad_before : unsigned(TICK_W - 1 downto 0);

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

           -- Drive one bit cell: present the value, wait `su` ticks, raise SCL for `hi` ticks,
           -- then drop it and wait `lo` ticks. The cell in its three parts, so a test can make
           -- any one of them illegal on its own.
           procedure drive_cell (v : in std_logic; su, hi, lo : in positive) is
           begin
               scl_in <= '0';
               sda_in <= v;   tick(su);
               scl_in <= '1'; tick(hi);
               scl_in <= '0'; tick(lo);
           end procedure;
       begin
           tick(3);
           if n_cells /= to_unsigned(0, TICK_W) then
               report "n_cells out of reset" severity error; errs := errs + 1; end if;
           rst_n <= '1'; tick(2);
           sda_in <= '1'; scl_in <= '0'; tick(5);

           -- 1: four LEGAL cells with generous margins. Nothing may be flagged -- a checker
           -- that flags legal traffic is the failure mode that gets a checker ignored.
           drive_cell('0', 40, 80, 40);
           drive_cell('1', 40, 80, 40);
           drive_cell('0', 40, 80, 40);
           drive_cell('1', 40, 80, 40);
           if n_log /= 4 then
               report "wrong number of cells reported, expected 4" severity error;
               errs := errs + 1; end if;
           if n_bad /= to_unsigned(0, TICK_W) then
               report "legal cells were flagged bad" severity error; errs := errs + 1; end if;
           if val_log(0) /= '0' or val_log(1) /= '1' or val_log(2) /= '0' or val_log(3) /= '1' then
               report "cell values wrong, expected 0101" severity error; errs := errs + 1; end if;
           if hi_log(0) < to_unsigned(75, TICK_W) or hi_log(0) > to_unsigned(85, TICK_W) then
               report "an 80-tick high phase measured out of range" severity error;
               errs := errs + 1; end if;

           -- 2: a SHORT SETUP. Only Q3's setup half may fail -- the bit's value is perfectly
           -- well defined and the high phase is legal.
           base := n_log;
           drive_cell('0', 4, 80, 40);
           if vs_log(base) /= '1' then
               report "a 4-tick setup was not flagged" severity error; errs := errs + 1; end if;
           if vm_log(base) /= '0' then
               report "a short setup also flagged the MEANING -- different questions"
                   severity error; errs := errs + 1; end if;
           if vh_log(base) /= '0' then
               report "a short setup also flagged a short high phase" severity error;
               errs := errs + 1; end if;

           -- 3: THE BOUNDARY. A setup of EXACTLY tSU;DAT is legal, because the spec value is a
           -- minimum. A checker that rejects the boundary rejects compliant traffic.
           base := n_log;
           drive_cell('1', T_SU_DAT, 80, 40);
           if vs_log(base) /= '0' then
               report "a setup of EXACTLY tSU;DAT was rejected" severity error;
               errs := errs + 1; end if;

           -- 4: one tick BELOW the boundary must fail. Testing the last legal value and the
           -- first illegal one is what pins down a comparison.
           --
           -- The value is 0 because the PREVIOUS cell drove 1. A setup-time test must actually
           -- CHANGE the signal: driving the same value again produces no edge, so the stability
           -- timer keeps accumulating and the measured setup is the whole history rather than
           -- the interval under test.
           base := n_log;
           drive_cell('0', T_SU_DAT - 1, 80, 40);
           if vs_log(base) /= '1' then
               report "a setup of tSU;DAT-1 was accepted" severity error; errs := errs + 1; end if;

           -- 5: SDA MOVES DURING THE HIGH PHASE. Q1's verdict must fail -- there is no single
           -- level this cell means. The setup was generous, so Q3 must NOT fail: the bit is not
           -- late, it is undefined.
           base := n_log;
           scl_in <= '0'; sda_in <= '0'; tick(40);
           scl_in <= '1';                tick(30);
           sda_in <= '1';                tick(30);   -- an edge while SCL is HIGH
           scl_in <= '0';                tick(40);
           if vm_log(base) /= '1' then
               report "an SDA edge during the high phase did not fail the MEANING test"
                   severity error; errs := errs + 1; end if;
           if vs_log(base) /= '0' then
               report "an undefined cell was also reported as a setup violation" severity error;
               errs := errs + 1; end if;
           if val_log(base) /= '0' then
               report "the reported value should be the level AT THE RISING EDGE" severity error;
               errs := errs + 1; end if;

           -- 5b: WHICH SAMPLE IS THE BIT. When SDA changes in the same cycle the rising edge is
           -- observed -- a zero setup time -- the block must report the level AT the edge, not the
           -- one before it. The choice has to be specified either way; what cannot happen is for it
           -- to be accidental, because then two implementations of the same checker disagree about
           -- what a marginal capture contained. The setup is zero, so viol_setup must fire too.
           base := n_log;
           scl_in <= '0'; sda_in <= '1'; tick(40);
           sda_in <= '0'; scl_in <= '1'; tick(80);   -- SDA changes and SCL rises together
           scl_in <= '0';                tick(40);
           if val_log(base) /= '0' then
               report "with SDA changing at the rising edge the block sampled the PREVIOUS level"
                   severity error; errs := errs + 1; end if;
           if vs_log(base) /= '1' then
               report "a zero setup time was not flagged" severity error; errs := errs + 1; end if;

           -- 6: a SHORT HIGH PHASE. Only Q3's high-phase half may fail.
           base := n_log;
           drive_cell('0', 40, 40, 40);
           if vh_log(base) /= '1' then
               report "a 40-tick high phase was not flagged" severity error; errs := errs + 1; end if;
           if vm_log(base) /= '0' or vs_log(base) /= '0' then
               report "a short high phase also flagged another question" severity error;
               errs := errs + 1; end if;

           -- 7: tHIGH exactly at the minimum is legal.
           base := n_log;
           drive_cell('1', 40, T_HIGH_MIN, 40);
           if vh_log(base) /= '0' then
               report "a high phase of EXACTLY tHIGH(min) was rejected" severity error;
               errs := errs + 1; end if;

           -- 8: THREE questions failing INDEPENDENTLY is the whole point, so a cell can fail
           -- all three at once and must report all three.
           base := n_log;
           scl_in <= '0'; sda_in <= '0'; tick(3);     -- short setup ...
           scl_in <= '1';                tick(15);
           sda_in <= '1';                tick(15);    -- ... AND an edge while high
           scl_in <= '0';                tick(40);    -- ... AND a 30-tick high phase
           if not (vs_log(base) = '1' and vm_log(base) = '1' and vh_log(base) = '1') then
               report "a cell failing all three questions did not report all three"
                   severity error; errs := errs + 1; end if;

           -- 9: an IDLE bus reports nothing. No SCL activity means no cells.
           cells_before := n_cells;
           sda_in <= '1'; scl_in <= '1'; tick(200);
           if n_cells /= cells_before then
               report "an idle bus produced cells" severity error; errs := errs + 1; end if;

           -- 10: SCL activity with NO SDA movement -- a clock running on an idle-high SDA. Every
           -- cell is legal and reads 1. This is what a bus recovery sequence looks like and it
           -- must not be flagged.
           bad_before := n_bad;
           sda_in <= '1';
           for i in 1 to 9 loop drive_cell('1', 40, 80, 40); end loop;
           if n_bad /= bad_before then
               report "nine clean recovery pulses produced violations" severity error;
               errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_bit_cell_checker self-check complete: the three questions are judged "
                    & "independently, both boundaries are exact, an undefined cell is not a late "
                    & "cell" severity note;
           else
               report "i2c_bit_cell_checker self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

6a. Four Decisions Worth Defending

Question 2 is reported as a fact, not judged as a violation. edge_while_high says an SDA edge occurred while SCL was high. Whether that is an error depends on something this block cannot know: Chapter 5.1 reserves such an edge for framing, so it is either a legal START or STOP — in which case it is not a bit at all — or a corrupt bit. Only a frame decoder can tell. A block that guessed would flag every START condition on the bus, which is the false-positive failure Chapter 10.3 §7 argues destroys a checker's usefulness.

The setup time is measured by a timer that runs continuously, not one armed by the edge. This is forced rather than stylistic. A setup interval ends at its reference edge, so by the time the rising edge arrives the interval being measured is already over — there is nothing left to time. The only way to know how long SDA had been stable is to have been counting all along and to sample the counter at the edge. Chapter 11.5 §5a draws the general rule: whether a parameter can be measured with an armed counter is decided by which end of it the reference edge sits at.

Every measurement includes the sample period in which it is taken. Both counters are read through a combinational "including this cycle" value rather than straight out of the register. Without it each interval reads one tick short, which shifts every comparison against a spec minimum by one sample period — a systematic error, in the unsafe direction, that no amount of ordinary stimulus reveals. It was found by the boundary tests in §7, which is precisely what boundary tests are for.

The comparisons are strictly less-than, so a measurement exactly equal to the minimum passes. Table 10's values are minima; a device that hits one exactly is compliant. A checker that rejected the boundary would report violations on conforming traffic, and mutation A1 in §8 injects exactly that.

6b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o d1 i2c_bit_cell_checker.sv i2c_bit_cell_checker_tb.sv && ./d1
   PASS: the three questions are judged independently, both boundaries are exact, an undefined
   cell is not a late cell
   i2c_bit_cell_checker_tb.sv:236: $finish called at 33060000 (1ps)

   $ iverilog -g2005 -o v1 i2c_bit_cell_checker.v i2c_bit_cell_checker_tb.v && ./v1
   PASS: the three questions are judged independently, both boundaries are exact, an undefined
   cell is not a late cell
   i2c_bit_cell_checker_tb.v:246: $finish called at 33060000 (1ps)

   $ nvc -a i2c_bit_cell_checker.vhd i2c_bit_cell_checker_tb.vhd
   $ nvc -e i2c_bit_cell_checker_tb && nvc -r i2c_bit_cell_checker_tb --stop-time=600us
   ** Note: 33060ns+0: i2c_bit_cell_checker self-check complete: the three questions are judged
      independently, both boundaries are exact, an undefined cell is not a late cell

All three at 33060 ns.

7. What the Testbench Proves

The stimulus drives one bit cell at a time with its three parts independently settable — the setup interval, the high phase and the low tail — so each question can be made to fail on its own.

#stimuluswhat it establishes
1four legal cellsnothing flagged; the values come back in order
2a 4-tick setuponly Q3's setup half fails
3a setup of exactly tSU;DATlegal — the boundary is inclusive
4one tick below the minimumillegal — the boundary is pinned from both sides
5SDA moves during the high phaseQ1 fails; Q3 does not — an undefined cell is not a late one
5bSDA changes in the same cycle as the risethe reported level is the one at the edge, and the zero setup is flagged
6a 40-tick high phaseonly Q3's high-phase half fails
7tHIGH exactly at the minimumlegal
8all three wrong at onceall three verdicts fire together
9an idle busno cells reported at all
10nine clean pulses on an idle-high SDAnothing flagged — this is a bus-recovery sequence

Tests 3 and 4 are the pair that found the off-by-one of §6a, and neither alone would have. Test 3 passes on a design that measures one tick long; test 4 passes on one that measures one tick short. Only requiring both — the last legal value accepted and the first illegal one rejected — pins the comparison down.

Test 5 is the one that keeps the three questions genuinely separate. The stimulus gives a generous setup and then moves SDA mid-phase, so a design that lumped the failures together would report a setup violation as well. Test 8 is the converse: a cell that is wrong in all three ways must report all three, not just the first one found.

Test 10 deserves a note because it is the case a naive checker gets wrong. Nine SCL pulses with SDA released high is the Chapter 5.5 bus-recovery sequence, and it is entirely legal — SDA never moves, so there is no setup interval to fail. A checker that demanded a transition per cell would flag every recovery attempt.

8. Mutation Testing

Six defects injected into the SystemVerilog classifier.

#injected defectoutcome
A1the setup comparison rejects the boundarykilled
A2the measurement excludes the sampling cyclekilled
A3the high phase is not checked at allkilled
A4an SDA edge during the high phase is ignoredkilled
A5the cell value is sampled at the falling edgekilled
A6the stability timer never resetskilled

Six injected, six killed — and across all nine of this module's designs the tally is 48 injected, 48 killed, no survivors. Several mutations survived their first run and were closed either by a new test or by separating two parameters the specification gives the same value, and one was removed as a provably equivalent mutant. §8 of the later chapters records each of those where it arose.

A5 is the one worth dwelling on, because closing it required specifying something rather than testing something. Sampling sda_q instead of sda_in at the rising edge reads the previous sample. On every legal cell those are the same value — SDA has been stable for at least tSU;DAT — so the mutation is invisible until SDA changes in the very cycle the edge is observed, which is a zero setup time.

So the test that kills it had to decide what the block should report in that case, and the answer is the level at the edge. Either choice is defensible; what is not defensible is leaving it accidental, because then two implementations of the same checker disagree about what a marginal capture contained — and a marginal capture is exactly when you need them to agree. Test 5b makes the choice explicit and asserts it.

A2 is the off-by-one, and its interest is that it is caught only at a boundary. With the measurement one tick short, every setup and every high phase reads low by one sample period. Tests 1, 2, 5 and 6 all still pass, because their margins are tens of ticks. Only tests 3 and 4, sitting exactly on the minimum, notice.

9. Verification Connection — Timing Belongs in an Interface, Not a Test

A timing parameter is a property of the interface, so in a UVM environment it belongs to the interface and its assertions rather than to any sequence. A test that happened to check setup times would check them only where it looked; an interface assertion checks every bit of every transfer in every test ever written against it.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_if.sv — the three questions as continuous interface assertions
   interface i2c_timing_if #(parameter int T_SU_DAT = 10, parameter int T_HIGH_MIN = 60)
                           (input logic clk);
      logic sda, scl;

      // A continuously running stability timer, for the reason section 6a gives: a setup
      // interval ends at its reference edge, so it cannot be timed by a counter armed there.
      int stable_ticks;
      always_ff @(posedge clk)
         stable_ticks <= ($changed(sda)) ? 0 : stable_ticks + 1;

      // ---- QUESTION 3: the data was stable long enough before the rising edge ----
      property p_setup;
         @(posedge clk) $rose(scl) |-> (stable_ticks >= T_SU_DAT);
      endproperty
      assert property (p_setup)
         else $error("tSU;DAT violated: SDA stable for only %0d ticks", stable_ticks);

      // ---- QUESTION 1: the level does not move during the high phase ----
      //
      // Note the exclusion. An SDA edge while SCL is high is RESERVED for framing, so this
      // property must not fire on a legal START or STOP -- and it cannot tell the difference
      // itself. `framing_expected` is driven by the monitor that decodes frames. An assertion
      // that omitted it would flag every START on the bus, which is the false-positive
      // failure that gets a checker switched off.
      property p_stable_while_high;
         @(posedge clk) disable iff (framing_expected)
         (scl && $past(scl)) |-> $stable(sda);
      endproperty
      assert property (p_stable_while_high)
         else $error("SDA moved while SCL was high outside a framing event");

      // ---- QUESTION 3, the other half: the high phase was usable ----
      int high_ticks;
      always_ff @(posedge clk)
         high_ticks <= scl ? high_ticks + 1 : 0;
      property p_high_long_enough;
         @(posedge clk) $fell(scl) |-> (high_ticks >= T_HIGH_MIN);
      endproperty
      assert property (p_high_long_enough)
         else $error("tHIGH violated: the high phase lasted %0d ticks", high_ticks);

      logic framing_expected;   // driven by the frame monitor
   endinterface

And the coverage, which for timing is about how close to the limit traffic actually came — because a suite that only ever ran with enormous margins has not tested the timing at all.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_margin_cov.sv — margin buckets, not value buckets
   covergroup i2c_setup_margin_cg with function sample(int measured, int minimum);
      // The bins are RELATIVE to the specified minimum, not absolute tick counts. That is the
      // only formulation that is reusable across speed modes -- an absolute bin set written for
      // Fast-mode says nothing useful about Fast-mode Plus, where the same nanosecond figure is
      // a different fraction of the budget.
      margin: coverpoint (measured - minimum) {
         bins violation    = {[$:-1]};        // below the minimum: must be reachable in a
                                              // directed test, and must never occur in a
                                              // randomised one
         bins exactly_at   = {0};             // the boundary -- the case section 7 pins down
         bins tight        = {[1:5]};
         bins comfortable  = {[6:50]};
         bins vast         = {[51:$]};
      }

      // Crossing the margin with WHICH question is being measured is what stops one comfortable
      // parameter from masking another that is permanently tight.
      which: coverpoint measured iff (measured >= 0);
   endgroup

10. FPGA and ASIC Implications

The block is two counters and a handful of state. At TICK_W = 16 it is roughly 50 flops. There is no arithmetic beyond increments and two magnitude compares against parameters, both on paths with a whole bit time to settle.

Sizing the sample clock is the real decision, and it is a resolution-versus-area trade. A 100 MHz sampler gives 10 ns granularity, which resolves Fast-mode's 100 ns setup to 10 % and Fast-mode Plus's 50 ns to 20 %. Going faster improves the resolution linearly and costs a faster clock domain; the honest answer is that a monitoring checker does not need compliance-grade resolution, and if you need compliance-grade resolution you need a scope, not more flops.

The two wires must be synchronised before they arrive. This block samples sda_in and scl_in on the system clock and compares consecutive samples. Feeding raw pads in would let a metastable sample produce a phantom edge — and a phantom edge here is a phantom bit cell with garbage measurements. The input stage of Module 4 is a prerequisite, not an optimisation.

And the spike filter of Chapter 11.8 belongs in front of it, with a consequence worth knowing now. That filter delays every transition by its threshold, equally on both lines. Because this block measures intervals between transitions on the two lines, a delay applied equally to both cancels out of the measurement — the interval is unchanged. What does not cancel is the filter's effect on the budget, which Chapter 11.9 spends explicitly.

11. Debugging — The Bus That Worked Until the Board Got Colder

Pitfall — a setup-time violation that is invisible in a logic-analyser capture
Buggy Code
// A sensor read on a Fast-mode bus. The master's bit engine presents the next data bit
// on the falling edge of SCL and releases SCL again after a fixed delay:
//
//     on SCL falling:   sda_out <= next_bit;
//     after N cycles:   scl_out <= 1;          // release SCL
//
// N was chosen from the tLOW minimum -- 1.3 us at Fast-mode, so N covers 1.3 us -- and the
// logic is correct as far as that goes. What was not accounted for is that sda_out drives a
// pad, the pad drives a 300 pF bus through a 4.7 kohm pull-up, and SDA's RISING edge takes
// most of a microsecond to get anywhere near VIH.
//
// So on a bit that goes from 0 to 1, the sequence is:
//
//     SCL falls -> sda_out released -> SDA rises SLOWLY -> SCL released
//                                      __ still climbing when SCL goes high __/
//
// tSU;DAT is measured to the point SDA is VALID, not to the point the driver let go.
Symptom

It worked. On the bench, for months, across two board revisions.

Then a unit in a cold-chamber test started returning corrupted sensor values -- not every read, perhaps one in fifty, and only below about 5 degrees. Warming the board made it stop. Nothing in the firmware had changed.

A logic-analyser capture at 24 MHz showed nothing wrong. Every byte was well formed, every acknowledge present, the addresses correct, the framing textbook. The captured SDA trace showed each bit sitting at the right level for the whole high phase, because a logic analyser reports a THRESHOLDED value: once SDA crosses the analyser's own threshold it reads as 1, and the analyser's threshold is not the receiving device's.

The theory that held longest was a marginal supply, because temperature-dependent and intermittent points that way. Considerable time went into the regulator.

A scope on SDA and SCL together resolved it in one screen. On a 0-to-1 bit, SDA was still rising as SCL crossed its own high threshold -- the two edges overlapped. The setup time was not small, it was NEGATIVE: the clock was sampling before the data had arrived. Cold made the pull-up's effective drive slightly worse and the CMOS input thresholds slightly higher, and the margin that had been a few nanoseconds went away.

Root Cause

The master timed tLOW from when it RELEASED SDA rather than from when SDA became valid, so the bus's rise time was spent inside the setup interval instead of before it.

The specification accounts for exactly this: the low phase has to hold tVD;DAT, then the rise, then tSU;DAT, and Chapter 11.9 shows that in Fast-mode those three sum to exactly the tLOW minimum with nothing left over. Designing to tLOW(min) while ignoring the rise means over-committing the low phase by the rise time -- which on a lightly loaded bench board is small enough to get away with, and on a real one is not.

12. Common Misconceptions

"A timing violation means a corrupted bit." Sometimes. A question-1 violation gives different receivers different values, which is worse; a question-2 violation produces a spurious START or STOP and changes the frame's structure; a question-3 violation gives an undefined level that may resolve either way.

"If the capture looks clean the timing is fine." A logic analyser reports a thresholded value against its own threshold, so it cannot show a setup violation at all. §11 is that mistake costing weeks.

"Table 10 is a list of numbers to look up." It is a list of answers to a small number of questions, and almost every entry is a minimum — meaning waiting longer is always compliant. The four maxima are the ones that constrain a design's speed.

"The three questions are really one requirement." They fail independently, which is exactly why the table has sixteen entries. A stable bit can be late; an undefined bit can have a generous setup; a well-defined bit can sit on a clock phase too short to use.

"An SDA edge during the high phase is always an error." It is reserved for framing. A START or STOP is exactly that edge, legally. Only a frame decoder can tell which it was, which is why §6's block reports the fact and refuses to judge it.

"A faster sample clock makes the checker compliance-grade." It improves resolution linearly and never reaches scope territory. A hardware checker's job is continuous monitoring of a working system, not qualification.

13. Reason It Through

A capture shows every byte well formed and the system is intermittently wrong. Which of the three questions do you suspect, and what instrument do you reach for?

Question 3. Questions 1 and 2 change what a capture looks like — an SDA edge inside the high phase, or a frame that restructures — so a clean capture largely rules them out. A setup violation leaves the capture perfect, because the analyser thresholds the signal before recording it. The instruments are a scope, or a checker like §6's that measures the interval in the system itself.

Why can a setup time not be measured with a counter started by the rising edge?

Because the interval ends at that edge. By the time the reference event arrives there is nothing left to time — the question "how long had SDA been stable" is about the past. The only implementation is a timer that runs continuously and is sampled at the edge, which is why §6's block has a free-running stability counter. Contrast a hold time, whose interval begins at its reference edge and which an armed counter measures directly.

A checker reports a setup of exactly the specified minimum as a violation. Why is that worse than missing a real violation?

Because it fires on compliant traffic. A device that hits a minimum exactly is legal, so the checker is now reporting faults that are not faults — and within a few weeks its output is ignored, at which point it occupies the place a working checker would have had. Chapter 10.3 §7 makes the same argument about atomicity: a false positive removes coverage.

SDA changes in the same cycle the rising edge is observed. What should the checker report, and why does it matter that the answer is specified rather than accidental?

Either answer is defensible — §6's block reports the level at the edge. What matters is that it is decided, because this is precisely the marginal case where two implementations of the same checker would otherwise disagree, and a marginal capture is when you most need them to agree. Mutation A5 is unobservable on every legal cell and only separable here, so closing it meant writing the specification down.

The spike filter of Chapter 11.8 delays every transition by its threshold. Does that corrupt this block's measurements?

No, because this block measures intervals between transitions and the filter delays both lines equally, so the delay cancels. What it does affect is the budget: the filter's latency has to come from somewhere inside the low phase, which is Chapter 11.9's subject. A delay that cancels out of a measurement can still consume a margin.

14. Understanding Check

15. Summary

A bit answers three independent questions, and Table 10 has sixteen entries because each can fail alone. What the bit means is the level while SCL is high; when it may change is only while SCL is low; how long it must hold is the setup before the rise plus the whole high phase.

The three failures are not equally visible, and that ordering matters more than the numbers. A question-2 failure restructures the frame and shows up immediately. A question-1 failure is visible in a capture. A question-3 failure leaves the capture perfect and appears as "it works on my board" — which is why it is the one worth building a checker for.

Almost every parameter is a minimum. Waiting longer is compliant, which is why clock stretching is legal and why a bus may idle indefinitely. The four maxima — the clock frequency, the data-valid times, the edge rates, and the spike width to reject — are the ones that constrain how fast a design may be.

A setup time and a hold time are measured differently, and the reason is structural. A setup interval ends at its reference edge so it needs a free-running timer sampled there; a hold interval begins at its edge so an armed counter suffices. Which end the reference edge sits at decides the implementation, and that rule recurs in every chapter of this module.

Include the sampling cycle in every measurement. Omitting it under-reports every interval by one tick, in the unsafe direction, invisibly — until a test sits exactly on a boundary.

Report facts separately from judgements. An SDA edge during the high phase is a fact; whether it is a fault depends on framing that this block cannot see. A checker that guesses fires on legal traffic, and a checker that fires on legal traffic gets switched off.

16. What Comes Next

Chapter 11.2 takes question 3's first half — the clock's own phases — and finds that a legal clock is three constraints rather than one frequency. The surprising case is a clock at a perfectly compliant 400 kHz whose duty cycle is illegal, and the chapter also opens an identity that runs through the whole module: tLOW(min) + tHIGH(min) + tr(max) + tf(max) equals 1/fSCL(max) exactly, in all three speed modes. The period budget is fully accounted for, which means every real design has to beat at least one of the four terms.

From there the module works outward: Chapter 11.3 constrains the data against the clock, Chapter 11.4 constrains how fast a transmitter must be, Chapters 11.5 and 11.6 put numbers on the framing margins, Chapter 11.7 prices the edges, Chapter 11.8 builds the one block in the module that sits in the datapath, and Chapter 11.9 closes a real budget by hand and checks it in hardware.

Continue learning