Skip to content
VLSI Mentor

I²C · Module 14

Standard, Fast and Fast-Mode Plus — The Mainstream Modes

Three modes, one protocol, four numbers. Derives the Table 10 budget identity that makes those four numbers a single constraint, shows why a faster edge can push a clock out of specification, and explains why Fast-mode compliance is about synchronizing and stretching rather than throughput.

Thirteen modules have been silent about speed. Every rule so far — the wired-AND, the acknowledge, stretching, synchronization, arbitration — reads identically at 100 kHz and at 1 MHz, and that is not an accident of presentation. The protocol genuinely does not change.

So this module has a narrow question to answer: what actually differs between Standard-mode, Fast-mode and Fast-mode Plus?

The answer is four numbers in Table 10, and one number in Table 9. Nothing else. No new conditions, no new byte formats, no new addresses, no new states.

That sounds like an anticlimax. It is the opposite. Those four numbers turn out not to be four independent limits but one budget, and once you see that, three things that look like separate facts collapse into consequences of it — including the reason a faster edge can make a clock illegal, and the reason Chapter 11.7's pull-up window came out empty at the table's own limits.

1. Five Categories, Two Buses

Read the shape of that list rather than the numbers. It is not a ladder of five rungs; it is four rungs and a separate thing.

The four bidirectional modes are one family. A device from any of them can be operated at any lower rate, which is what "downward-compatible" means, and the reason it is possible at all is that they share the open-drain electrical contract of Chapter 2.5. Ultra Fast-mode is not on that ladder. It is push-pull and unidirectional, and the specification says so in the same breath as the compatibility claim — because the compatibility claim does not extend to it. Chapter 14.3 is about what that costs.

This chapter takes the first three rungs. Chapter 14.2 takes the fourth, which is the only one that changes the protocol rather than the numbers.

2. What Is Identical

Worth stating explicitly, because the list is longer than the list of differences.

So across all three modes, unchanged:

unchangedestablished in
START and STOP conditionsChapter 5.2
the 9-bit byte frame and the acknowledgeChapter 7.2
7-bit and 10-bit addressingChapter 6.1
the reserved addressesChapter 6.3
clock stretchingModule 12
clock synchronization and arbitrationModule 13
VIL = 0.3 VDD, VIH = 0.7 VDDChapter 2.6
VnL = 0.1 VDD, VnH = 0.2 VDD noise marginsChapter 11.7
Cb maximum of 400 pF (Standard and Fast; 550 pF in Fm+)Chapter 11.7

A Standard-mode transaction capture and a Fast-mode one are the same picture at different scales. That is the whole reason the previous eleven modules could be written without mentioning speed.

3. What Differs — Four Numbers

And one number that is not in Table 10 at all, which turns out to matter more than any of them:

Note what that means. Standard-mode and Fast-mode have the same output stage. Fast-mode is not a stronger driver; it is the same driver asked to settle faster. Fm+ is where the silicon actually changes, and Chapter 14.4 shows that the 20 mA, not the timing, is what buys Fm+ its extra capacitance.

4. The Budget Identity

Here is the result this module is built on. Chapter 11.9 derived it for the three modes one at a time; stated together it is more striking.

For every one of the three modes, tLOW(min) + tHIGH(min) + tr(max) + tf(max)= 1 /fSCL(max) — exactly.

modetLOWtHIGHtrtfsum1/fSCL(max)slack
Standard47004000100030010000 ns10000 ns0
Fast13006003003002500 ns2500 ns0
Fm+5002601201201000 ns1000 ns0

Three modes, three exact hits. That is not a coincidence and it is not rounding: it is how the table was constructed. The four numbers are one constraint expressed four ways, and the specification states the relationship itself in §7.2.1:

Equation 3 is normally read as a way to derate a slow bus. Read it the other way and it is the definition of the headline rate: put each mode's own minima and maxima in, and the mode's own advertised frequency comes out. The design in §7 does exactly that and the check is in the transcript.

4a. The Consequence Nobody Expects

If the four limits were independent, a bit cell would be legal whenever each one was individually satisfied. Because they are one budget, that is false — and false in a direction that catches people out.

Take a Fast-mode cell sitting exactly on both phase minima, tLOW = 1300 ns and tHIGH = 600 ns, with a tf of 300 ns. Now make the rising edge faster: 100 ns instead of 300 ns. Every individual parameter is comfortably legal — 100 ns is inside Table 10's 20…300 ns window for tr, and neither phase moved.

The period is now 1300 + 100 + 600 + 300 = 2300 ns. That is 435 kHz. The clock is over the Fast-mode limit, and the only thing that changed was an edge getting better.

A quicker edge is not a free win. It is period you have to spend somewhere, and if you do not spend it on a phase you have overclocked the bus.

This is not a contrived case. It is what happens when someone strengthens a pull-up to fix a marginal rise time without revisiting the clock divider — the edge problem goes away and a frequency violation appears in its place. Test 4 in §7 is that exact cell, and it is the reason the monitor reports a period violation with every other flag clear.

5. "Fast-Mode Device" Does Not Mean "Runs At 400 kbit/s"

The definition is one sentence and it is not the one most engineers would write.

So the compliance bar for a Fast-mode device is not throughput. It is:

  1. Can synchronize with a 400 kbit/s clock — the mechanism of Chapter 13.2.
  2. Can stretch to slow the transfer down — the mechanism of Module 12.

A device that can only manage 150 kbit/s of actual work is a conforming Fast-mode device provided it can track a 400 kbit/s clock's edges and hold SCL low when it needs time. Both of those are capabilities the previous two modules built, which is why they had to come first.

Speed conformance is defined in terms of keeping up with the edges and being able to ask for more time — not in terms of the rate you achieve.

That is a much more useful definition for a real system, because it makes the fast/slow mixture on a real board a matter of negotiation rather than of exclusion. And it explains why the two capabilities are mandatory in Table 2 while raw speed is nowhere in it.

6. Downward Compatible, Not Upward Compatible

The specification is unusually blunt here, and the asymmetry is worth taking seriously.

"Unpredictable states" is strong language for a document this careful. It is also exactly what the budget identity predicts, and §7's monitor demonstrates it in both directions on the same two waveforms:

A legal Standard-mode cell, judged as Fast-mode. tLOW 4700 is fine, tHIGH 4000 is fine, the period is four times longer than required. It fails on one parameter: the 1000 ns rise time is 3.3 times the Fast-mode allowance of 300 ns. That single number is the whole of the incompatibility, and it is an electrical number, not a protocol one.

A legal Fast-mode cell, judged as Standard-mode. Three failures at once: tLOW too short, tHIGH too short, and the period too fast. A Standard-mode part asked to receive this simply does not see a valid clock.

the celljudged as Standardjudged as Fast
4700 / 1000 / 4000 / 300legalfails on tr only
1300 / 300 / 600 / 300fails tLOW, tHIGH, periodlegal

The asymmetry is therefore not a matter of politeness. Going down means one device's edges are slower than another needs — recoverable, because slower edges on a shared bus are simply the bus's edges. Going up means a device is being asked to resolve phases shorter than its input path can register, and nothing on the bus tells it that is happening.

7. One Bit Cell, Two Verdicts

A legal Standard-mode bit cell, and the one parameter that makes it illegal in Fast-mode

10 cycles
Ten intervals showing one SCL bit cell shaped to Standard-mode's limiting values: four intervals of low phase, one rising edge, four intervals of high phase, and one falling edge. Two annotation rows beneath give the verdict of the same cell under Standard-mode and under Fast-mode rules. The Standard row reads ok across every region. The Fast row reads ok for the low phase, the high phase and the fall, and FAIL for the rising edge, because the 1000 nanosecond rise exceeds Fast-mode's 300 nanosecond allowance.tLOW 4700 nstLOW 4700 nstHIGH 4000 nstHIGH 4000 nstr 1000 ns: Fast allows 300 nstr 1000 ns: Fast allows 300nstf 300 ns: legal in bothtf 300 ns: legal in bothsclregiontLOWtLOWtLOWtLOWtrtHIGHtHIGHtHIGHtHIGHtfStdokokokokokokokokokokFastokokokokFAILokokokokokt0t1t2t3t4t5t6t7t8t9

One waveform, two verdicts, and the difference is a single interval. Everything else about the cell — the phases, the frame, the data on SDA that is not drawn — is identical and irrelevant to the outcome.

8. The Envelope Monitor in Three Languages

The design measures one SCL bit cell the way Table 10 defines it and reports which constraint is tightest, not merely whether the cell passed. That choice is the point: on a bus that is failing intermittently, knowing that the binding constraint is the rise time rather than the low phase is the difference between changing a resistor and changing a clock divider.

The line arrives as two comparator outputs rather than one logic level, because a digital level cannot express an edge rate. th30 is high when the line is above 0.3 VDD, th70 when it is above 0.7 VDD, and the four intervals fall out of the crossings exactly as the specification measures them.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_envelope.sv — the Table 10 envelope as one budget, not four limits
   // -----------------------------------------------------------------------------
   // i2c_mode_envelope.sv
   // Per-mode SCL timing-envelope monitor for Standard-mode, Fast-mode and
   // Fast-mode Plus.
   //
   // Measures one SCL bit cell the way UM10204 defines it -- between the 30 % and
   // 70 % threshold crossings -- and checks it against the Table 10 limits of the
   // selected mode:
   //
   //     tLOW   30 % falling  -> 30 % rising     must be >= tLOW(min)
   //     tr     30 % rising   -> 70 % rising     must be <= tr(max), >= tr(min)
   //     tHIGH  70 % rising   -> 70 % falling    must be >= tHIGH(min)
   //     tf     70 % falling  -> 30 % falling    must be <= tf(max)
   //     period tLOW+tr+tHIGH+tf                 must be >= 1/fSCL(max)
   //
   // The period check is the interesting one. In all three modes Table 10 is
   // internally exact:
   //     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   // so the four limits are not independent -- they are one budget. A cell can
   // satisfy every individual minimum and still be illegal because the edges ate
   // the slack. This monitor reports which of the five constraints is the tightest
   // (`binding`), not merely whether the cell passed.
   //
   // tf(min) is deliberately NOT checked: Table 10 specifies it as
   // 20 x (VDD / 5.5 V) ns, a supply-dependent value this monitor is not given.
   // tr(min) IS checked -- Table 10 states it as a flat 20 ns for Fast-mode and
   // leaves it unspecified ("-") for Standard-mode and Fast-mode Plus.
   //
   // The line is presented as two comparator outputs rather than one logic level,
   // because a digital level cannot express an edge rate.
   // -----------------------------------------------------------------------------

   module i2c_mode_envelope #(
      parameter int TICK_NS = 10,   // ns per clk; every Table 10 value is a multiple of 10
      parameter int CNT_W   = 16
   ) (
      input  logic              clk,
      input  logic              rst_n,

      input  logic [1:0]        mode,    // 0 = Standard, 1 = Fast, 2 = Fast-mode Plus
      input  logic              th30,    // 1 when the line is above 30 % of VDD
      input  logic              th70,    // 1 when the line is above 70 % of VDD

      output logic [CNT_W-1:0]  meas_tlow,    // ns
      output logic [CNT_W-1:0]  meas_thigh,   // ns
      output logic [CNT_W-1:0]  meas_tr,      // ns
      output logic [CNT_W-1:0]  meas_tf,      // ns
      output logic [CNT_W-1:0]  meas_period,  // ns, = tlow + tr + thigh + tf

      output logic              cell_valid,   // one pulse per completed bit cell
      output logic              legal,        // that cell violated nothing
      output logic [2:0]        binding,      // tightest constraint, see BIND_* below
      output logic signed [CNT_W:0] worst_margin, // ns of slack on `binding` (negative = violated)

      output logic              viol_tlow,
      output logic              viol_thigh,
      output logic              viol_tr,
      output logic              viol_tf,
      output logic              viol_period,

      output logic [CNT_W-1:0]  cells_seen,
      output logic [CNT_W-1:0]  cells_legal
   );

      localparam [1:0] MODE_STD  = 2'd0;
      localparam [1:0] MODE_FAST = 2'd1;
      localparam [1:0] MODE_FMP  = 2'd2;

      localparam [2:0] BIND_NONE   = 3'd0;
      localparam [2:0] BIND_TLOW   = 3'd1;
      localparam [2:0] BIND_THIGH  = 3'd2;
      localparam [2:0] BIND_TR     = 3'd3;
      localparam [2:0] BIND_TF     = 3'd4;
      localparam [2:0] BIND_PERIOD = 3'd5;

      // ---------------------------------------------------------------------
      // Table 10 limits, in ns. Standard / Fast / Fast-mode Plus.
      // ---------------------------------------------------------------------
      function [CNT_W-1:0] lim_tlow_min (input [1:0] m);
         case (m)
            MODE_STD : lim_tlow_min = 16'd4700;
            MODE_FAST: lim_tlow_min = 16'd1300;
            default  : lim_tlow_min = 16'd500;
         endcase
      endfunction

      function [CNT_W-1:0] lim_thigh_min (input [1:0] m);
         case (m)
            MODE_STD : lim_thigh_min = 16'd4000;
            MODE_FAST: lim_thigh_min = 16'd600;
            default  : lim_thigh_min = 16'd260;
         endcase
      endfunction

      function [CNT_W-1:0] lim_tr_max (input [1:0] m);
         case (m)
            MODE_STD : lim_tr_max = 16'd1000;
            MODE_FAST: lim_tr_max = 16'd300;
            default  : lim_tr_max = 16'd120;
         endcase
      endfunction

      // Table 10 gives tr(min) only for Fast-mode. "-" elsewhere means no limit.
      function [CNT_W-1:0] lim_tr_min (input [1:0] m);
         case (m)
            MODE_FAST: lim_tr_min = 16'd20;
            default  : lim_tr_min = 16'd0;
         endcase
      endfunction

      function [CNT_W-1:0] lim_tf_max (input [1:0] m);
         case (m)
            MODE_STD : lim_tf_max = 16'd300;
            MODE_FAST: lim_tf_max = 16'd300;
            default  : lim_tf_max = 16'd120;
         endcase
      endfunction

      // 1 / fSCL(max): 100 kHz, 400 kHz, 1000 kHz.
      function [CNT_W-1:0] lim_period_min (input [1:0] m);
         case (m)
            MODE_STD : lim_period_min = 16'd10000;
            MODE_FAST: lim_period_min = 16'd2500;
            default  : lim_period_min = 16'd1000;
         endcase
      endfunction

      // ---------------------------------------------------------------------
      // Threshold-crossing edge detection
      // ---------------------------------------------------------------------
      logic th30_q, th70_q;
      wire th30_rise =  th30 && !th30_q;
      wire th30_fall = !th30 &&  th30_q;
      wire th70_rise =  th70 && !th70_q;
      wire th70_fall = !th70 &&  th70_q;

      // ---------------------------------------------------------------------
      // Measurement FSM. The bit cell boundary is the 30 % falling edge.
      // ---------------------------------------------------------------------
      localparam [2:0] S_SYNC = 3'd0,  // wait for the first 30 % falling edge
                       S_LOW  = 3'd1,
                       S_RISE = 3'd2,
                       S_HIGH = 3'd3,
                       S_FALL = 3'd4;

      logic [2:0]       state;
      logic [CNT_W-1:0] cnt;
      logic [CNT_W-1:0] acc_tlow, acc_tr, acc_thigh;

      localparam [CNT_W-1:0] TICK = TICK_NS;

      // ---------------------------------------------------------------------
      // Evaluation of a completed cell. Margins are signed: negative = violated.
      // ---------------------------------------------------------------------
      logic signed [CNT_W:0] m_tlow, m_thigh, m_tr, m_tf, m_period;
      logic signed [CNT_W:0] best;
      logic        [2:0]     best_id;
      logic                  cell_ok;
      logic signed [CNT_W:0] m_tr_hi, m_tr_lo;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            th30_q       <= 1'b1;
            th70_q       <= 1'b1;
            state        <= S_SYNC;
            cnt          <= {CNT_W{1'b0}};
            acc_tlow     <= {CNT_W{1'b0}};
            acc_tr       <= {CNT_W{1'b0}};
            acc_thigh    <= {CNT_W{1'b0}};
            meas_tlow    <= {CNT_W{1'b0}};
            meas_thigh   <= {CNT_W{1'b0}};
            meas_tr      <= {CNT_W{1'b0}};
            meas_tf      <= {CNT_W{1'b0}};
            meas_period  <= {CNT_W{1'b0}};
            cell_valid   <= 1'b0;
            legal        <= 1'b0;
            binding      <= BIND_NONE;
            worst_margin <= {(CNT_W+1){1'b0}};
            viol_tlow    <= 1'b0;
            viol_thigh   <= 1'b0;
            viol_tr      <= 1'b0;
            viol_tf      <= 1'b0;
            viol_period  <= 1'b0;
            cells_seen   <= {CNT_W{1'b0}};
            cells_legal  <= {CNT_W{1'b0}};
         end else begin
            th30_q     <= th30;
            th70_q     <= th70;
            cell_valid <= 1'b0;

            case (state)
               // Do not measure a partial cell: wait for a clean cell boundary.
               S_SYNC: begin
                  if (th30_fall) begin
                     state <= S_LOW;
                     cnt   <= {CNT_W{1'b0}};
                  end
               end

               S_LOW: begin
                  if (th30_rise) begin
                     acc_tlow <= cnt + TICK;   // the crossing tick belongs to the phase
                     cnt      <= {CNT_W{1'b0}};
                     state    <= S_RISE;
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               S_RISE: begin
                  if (th70_rise) begin
                     acc_tr <= cnt + TICK;
                     cnt    <= {CNT_W{1'b0}};
                     state  <= S_HIGH;
                  end else if (th30_fall) begin
                     // Never reached 70 %: an aborted edge. Resynchronise.
                     state <= S_LOW;
                     cnt   <= {CNT_W{1'b0}};
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               S_HIGH: begin
                  if (th70_fall) begin
                     acc_thigh <= cnt + TICK;
                     cnt       <= {CNT_W{1'b0}};
                     state     <= S_FALL;
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               S_FALL: begin
                  if (th30_fall) begin
                     meas_tlow   <= acc_tlow;
                     meas_thigh  <= acc_thigh;
                     meas_tr     <= acc_tr;
                     meas_tf     <= cnt + TICK;
                     meas_period <= acc_tlow + acc_tr + acc_thigh + cnt + TICK;
                     cell_valid  <= 1'b1;
                     cells_seen  <= cells_seen + 1'b1;
                     // ---- verdict for the cell that just completed ----
                     m_tlow   = $signed({1'b0, acc_tlow})         - $signed({1'b0, lim_tlow_min(mode)});
                     m_thigh  = $signed({1'b0, acc_thigh})        - $signed({1'b0, lim_thigh_min(mode)});
                     // tr is bounded on BOTH sides in Fast-mode (20 ns .. 300 ns), so its
                     // margin is the tighter of the two. Where Table 10 gives no minimum
                     // (Standard, Fm+) lim_tr_min is 0 and the upper bound always wins.
                     m_tr_hi  = $signed({1'b0, lim_tr_max(mode)}) - $signed({1'b0, acc_tr});
                     m_tr_lo  = $signed({1'b0, acc_tr})           - $signed({1'b0, lim_tr_min(mode)});
                     m_tr     = (lim_tr_min(mode) != 0 && m_tr_lo < m_tr_hi) ? m_tr_lo : m_tr_hi;
                     m_tf     = $signed({1'b0, lim_tf_max(mode)}) - $signed({1'b0, cnt + TICK});
                     m_period = $signed({1'b0, acc_tlow + acc_tr + acc_thigh + cnt + TICK})
                                                                  - $signed({1'b0, lim_period_min(mode)});

                     viol_tlow   <= (m_tlow   < 0);
                     viol_thigh  <= (m_thigh  < 0);
                     viol_tr     <= (m_tr     < 0);
                     viol_tf     <= (m_tf     < 0);
                     viol_period <= (m_period < 0);

                     cell_ok = !(m_tlow < 0) && !(m_thigh < 0) && !(m_tr < 0)
                               && !(m_tf < 0) && !(m_period < 0);
                     legal <= cell_ok;
                     if (cell_ok) cells_legal <= cells_legal + 1'b1;

                     // Tightest constraint. Ties resolve in table order, so a cell
                     // sitting exactly on two limits reports the earlier one.
                     best    = m_tlow;  best_id = BIND_TLOW;
                     if (m_thigh  < best) begin best = m_thigh;  best_id = BIND_THIGH;  end
                     if (m_tr     < best) begin best = m_tr;     best_id = BIND_TR;     end
                     if (m_tf     < best) begin best = m_tf;     best_id = BIND_TF;     end
                     if (m_period < best) begin best = m_period; best_id = BIND_PERIOD; end
                     worst_margin <= best;
                     binding      <= best_id;
                     cnt         <= {CNT_W{1'b0}};
                     state       <= S_LOW;   // next cell starts at this same edge
                  end else if (th70_rise) begin
                     // The line came back up without crossing 30 %: not a cell end.
                     cnt   <= cnt + TICK;
                     state <= S_HIGH;
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               default: state <= S_SYNC;
            endcase
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_envelope_tb.sv — fifteen cells, including the two that are legal in one mode and illegal in another
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_mode_envelope_tb.sv
   // Independent oracle for i2c_mode_envelope.
   //
   // The bench drives threshold-crossing pairs that describe a bit cell of a known
   // shape, then checks the DUT's measurement AND its verdict against limits the
   // bench holds separately. The bench never reads the DUT's own limit functions.
   //
   // The point of the suite is the budget identity of Table 10:
   //     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   // Tests 1-3 show it holds exactly in all three modes. Test 4 shows the
   // consequence that surprises people: making an edge FASTER while the phases sit
   // at their minima produces an ILLEGAL clock, because the period floor is a
   // floor, not a ceiling.
   // -----------------------------------------------------------------------------
   module i2c_mode_envelope_tb;

      localparam integer TICK_NS = 10;

      localparam [1:0] M_STD  = 2'd0;
      localparam [1:0] M_FAST = 2'd1;
      localparam [1:0] M_FMP  = 2'd2;

      localparam [2:0] BIND_NONE = 3'd0, BIND_TLOW = 3'd1, BIND_THIGH = 3'd2,
                       BIND_TR   = 3'd3, BIND_TF   = 3'd4, BIND_PERIOD = 3'd5;

      logic        clk = 1'b0;
      logic        rst_n = 1'b0;
      logic [1:0]  mode = M_FAST;
      logic        th30 = 1'b1;
      logic        th70 = 1'b1;

      logic [15:0] meas_tlow, meas_thigh, meas_tr, meas_tf, meas_period;
      logic [15:0] cells_seen, cells_legal;
      logic        cell_valid, legal;
      logic [2:0]  binding;
      logic signed [16:0] worst_margin;
      logic        viol_tlow, viol_thigh, viol_tr, viol_tf, viol_period;

      integer errors = 0;
      integer k;    // used by hold() only
      integer n;    // outer loops -- must not be k, hold() clobbers it

      i2c_mode_envelope #(.TICK_NS(TICK_NS), .CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n), .mode(mode), .th30(th30), .th70(th70),
         .meas_tlow(meas_tlow), .meas_thigh(meas_thigh), .meas_tr(meas_tr),
         .meas_tf(meas_tf), .meas_period(meas_period),
         .cell_valid(cell_valid), .legal(legal), .binding(binding),
         .worst_margin(worst_margin),
         .viol_tlow(viol_tlow), .viol_thigh(viol_thigh), .viol_tr(viol_tr),
         .viol_tf(viol_tf), .viol_period(viol_period),
         .cells_seen(cells_seen), .cells_legal(cells_legal));

      always #(TICK_NS/2) clk = ~clk;

      // -------------------------------------------------------------------
      // Stimulus. Levels change on the negedge so the DUT never has to resolve
      // a transition that lands on its own sampling edge.
      // -------------------------------------------------------------------
      task hold (input integer ns_, input integer b30, input integer b70);
         begin
            @(negedge clk);
            th30 = b30[0];
            th70 = b70[0];
            for (k = 0; k < ns_/TICK_NS; k = k + 1) @(posedge clk);
         end
      endtask

      // One bit cell: LOW, then the rising edge, then HIGH, then the falling edge.
      // The cell is reported by the DUT at the START of the next LOW phase.
      task emit_cell (input integer tlow_, input integer tr_,
                      input integer thigh_, input integer tf_);
         begin
            hold(tlow_,  0, 0);
            hold(tr_,    1, 0);
            hold(thigh_, 1, 1);
            hold(tf_,    1, 0);
         end
      endtask

      task flush;   // a trailing LOW so the last cell is reported
         begin hold(200, 0, 0); end
      endtask

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; th30 = 1'b1; th70 = 1'b1;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            @(posedge clk);
         end
      endtask

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

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

      // Full expectation for one measured cell.
      task expect_cell (input [200*8:1] tag,
                        input integer e_tlow, input integer e_tr,
                        input integer e_thigh, input integer e_tf,
                        input e_legal,
                        input e_vlow, input e_vhigh, input e_vtr, input e_vtf, input e_vper,
                        input [2:0] e_bind, input integer e_margin);
         begin
            $display("%0s", tag);
            ck_int("meas_tlow",   meas_tlow,   e_tlow);
            ck_int("meas_tr",     meas_tr,     e_tr);
            ck_int("meas_thigh",  meas_thigh,  e_thigh);
            ck_int("meas_tf",     meas_tf,     e_tf);
            ck_int("meas_period", meas_period, e_tlow + e_tr + e_thigh + e_tf);
            ck_bit("legal",       legal,       e_legal);
            ck_bit("viol_tlow",   viol_tlow,   e_vlow);
            ck_bit("viol_thigh",  viol_thigh,  e_vhigh);
            ck_bit("viol_tr",     viol_tr,     e_vtr);
            ck_bit("viol_tf",     viol_tf,     e_vtf);
            ck_bit("viol_period", viol_period, e_vper);
            ck_int("binding",     binding,     e_bind);
            ck_int("worst_margin", worst_margin, e_margin);
         end
      endtask

      initial begin
         $display("=== i2c_mode_envelope: Table 10 envelope checks ===");

         // ----------------------------------------------------------------
         // 1. Standard-mode exactly on the budget.
         //    4700 + 1000 + 4000 + 300 = 10000 ns = 1/100 kHz.
         //    Every one of the five margins is zero at once.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_STD;
         emit_cell(4700, 1000, 4000, 300);
         flush;
         expect_cell("T1  Standard-mode, all five limits met exactly",
                     4700, 1000, 4000, 300, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
         ck_int("T1 cells_seen",  cells_seen,  1);
         ck_int("T1 cells_legal", cells_legal, 1);

         // ----------------------------------------------------------------
         // 2. Fast-mode exactly on the budget. 1300+300+600+300 = 2500.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 300, 600, 300);
         flush;
         expect_cell("T2  Fast-mode, all five limits met exactly",
                     1300, 300, 600, 300, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);

         // ----------------------------------------------------------------
         // 3. Fast-mode Plus exactly on the budget. 500+120+260+120 = 1000.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FMP;
         emit_cell(500, 120, 260, 120);
         flush;
         expect_cell("T3  Fast-mode Plus, all five limits met exactly",
                     500, 120, 260, 120, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);

         // ----------------------------------------------------------------
         // 4. THE CONSEQUENCE OF THE IDENTITY.
         //    Fast-mode, phases at their minima, but a FASTER rise (100 ns, well
         //    inside the 20..300 ns window). Every individual parameter is legal;
         //    the cell is illegal because the period fell to 2300 ns, i.e. the
         //    clock is running at 435 kHz. A quicker edge is not a free win.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 100, 600, 300);
         flush;
         expect_cell("T4  Fast-mode: every parameter legal, period floor broken",
                     1300, 100, 600, 300, 1'b0,
                     1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -200);

         // ----------------------------------------------------------------
         // 5. tLOW below its minimum, in isolation. tHIGH is stretched so the
         //    period floor is still met and only one flag can fire.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1200, 300, 1300, 300);
         flush;
         expect_cell("T5  Fast-mode: tLOW 100 ns short, nothing else",
                     1200, 300, 1300, 300, 1'b0,
                     1'b1,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, -100);

         // ----------------------------------------------------------------
         // 6. tHIGH below its minimum, in isolation.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(2000, 300, 500, 300);
         flush;
         expect_cell("T6  Fast-mode: tHIGH 100 ns short, nothing else",
                     2000, 300, 500, 300, 1'b0,
                     1'b0,1'b1,1'b0,1'b0,1'b0, BIND_THIGH, -100);

         // ----------------------------------------------------------------
         // 7. Rise time above tr(max).
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 400, 600, 300);
         flush;
         expect_cell("T7  Fast-mode: tr 100 ns too slow",
                     1300, 400, 600, 300, 1'b0,
                     1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -100);

         // ----------------------------------------------------------------
         // 8. Rise time below tr(min). Table 10 gives Fast-mode a 20 ns FLOOR on
         //    the rise time; an edge can be too fast as well as too slow. The
         //    margin is two-sided, so this ranks as the binding constraint even
         //    though the upper bound has 290 ns of room.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(2000, 10, 600, 300);
         flush;
         expect_cell("T8  Fast-mode: tr 10 ns, below the 20 ns floor",
                     2000, 10, 600, 300, 1'b0,
                     1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -10);

         // ----------------------------------------------------------------
         // 9. Fall time above tf(max).
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 300, 600, 400);
         flush;
         expect_cell("T9  Fast-mode: tf 100 ns too slow",
                     1300, 300, 600, 400, 1'b0,
                     1'b0,1'b0,1'b0,1'b1,1'b0, BIND_TF, -100);

         // ----------------------------------------------------------------
         // 10. "Downward-compatible, not upward-compatible" (UM10204 5.1),
         //     measured rather than asserted.
         //     (a) A perfectly legal STANDARD-mode cell, judged as Fast-mode:
         //         illegal on the rise time alone. Its 1000 ns edge is 3.3x the
         //         Fast-mode allowance.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(4700, 1000, 4000, 300);
         flush;
         expect_cell("T10a legal Standard cell judged as Fast: fails on tr only",
                     4700, 1000, 4000, 300, 1'b0,
                     1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -700);

         //     (b) A perfectly legal FAST-mode cell, judged as Standard-mode:
         //         both phases too short AND the clock too fast. Three flags.
         do_reset;
         mode = M_STD;
         emit_cell(1300, 300, 600, 300);
         flush;
         expect_cell("T10b legal Fast cell judged as Standard: three violations",
                     1300, 300, 600, 300, 1'b0,
                     1'b1,1'b1,1'b0,1'b0,1'b1, BIND_PERIOD, -7500);

         // ----------------------------------------------------------------
         // 11. An aborted rising edge. The line clears 30 % but sags back below
         //     it without ever reaching 70 %. That is not a bit cell and must not
         //     be measured as one; the monitor resynchronises and reports exactly
         //     one cell -- the good one that follows.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         hold(1300, 0, 0);       // LOW
         hold(200,  1, 0);       // above 30 %, never reaches 70 %
         hold(1300, 0, 0);       // back down: aborted
         hold(300,  1, 0);       // now a real edge
         hold(600,  1, 1);
         hold(300,  1, 0);
         flush;
         $display("T11  aborted rising edge is not counted as a cell");
         ck_int("T11 cells_seen",  cells_seen,  1);
         ck_int("T11 cells_legal", cells_legal, 1);
         ck_int("T11 meas_tlow",   meas_tlow,   1300);
         ck_int("T11 meas_tr",     meas_tr,     300);

         // ----------------------------------------------------------------
         // 12. Five back-to-back legal Fast-mode cells: the counters track.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         for (n = 0; n < 5; n = n + 1) emit_cell(1300, 300, 600, 300);
         flush;
         $display("T12  five back-to-back legal cells");
         ck_int("T12 cells_seen",  cells_seen,  5);
         ck_int("T12 cells_legal", cells_legal, 5);

         // ----------------------------------------------------------------
         // 13. Mixed run: 3 legal then 2 illegal. cells_legal must not follow
         //     cells_seen.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 400, 600, 300);   // tr too slow
         emit_cell(1200, 300, 1300, 300);  // tLOW short
         flush;
         $display("T13  three legal then two illegal cells");
         ck_int("T13 cells_seen",  cells_seen,  5);
         ck_int("T13 cells_legal", cells_legal, 3);
         ck_bit("T13 last legal",  legal,       1'b0);
         ck_int("T13 last binding", binding,    BIND_TLOW);

         // ----------------------------------------------------------------
         // 14. Fast-mode Plus one tick under the period floor: 500+110+260+120
         //     = 990 ns. The rise time is legal (110 <= 120) and both phases are
         //     at their minima, so again only the budget catches it.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FMP;
         emit_cell(500, 110, 260, 120);
         flush;
         expect_cell("T14 Fm+ 10 ns under the 1000 ns period floor",
                     500, 110, 260, 120, 1'b0,
                     1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -10);

         // ----------------------------------------------------------------
         // 15. Standard-mode has no tr(min): a 10 ns rise is legal there, and the
         //     same 10 ns rise was a violation in Fast-mode (T8). Same edge, two
         //     verdicts, because Table 10 specifies the floor only for Fast-mode.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_STD;
         emit_cell(6000, 10, 4000, 300);
         flush;
         // tHIGH and tf both sit exactly on their limits here, so the margin is 0
         // twice over and the documented tie-break (table order) reports tHIGH.
         expect_cell("T15 Standard-mode: a 10 ns rise is legal (no tr floor)",
                     6000, 10, 4000, 300, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_THIGH, 0);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_envelope.v — the same monitor in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_mode_envelope.sv
   // Per-mode SCL timing-envelope monitor for Standard-mode, Fast-mode and
   // Fast-mode Plus.
   //
   // Measures one SCL bit cell the way UM10204 defines it -- between the 30 % and
   // 70 % threshold crossings -- and checks it against the Table 10 limits of the
   // selected mode:
   //
   //     tLOW   30 % falling  -> 30 % rising     must be >= tLOW(min)
   //     tr     30 % rising   -> 70 % rising     must be <= tr(max), >= tr(min)
   //     tHIGH  70 % rising   -> 70 % falling    must be >= tHIGH(min)
   //     tf     70 % falling  -> 30 % falling    must be <= tf(max)
   //     period tLOW+tr+tHIGH+tf                 must be >= 1/fSCL(max)
   //
   // The period check is the interesting one. In all three modes Table 10 is
   // internally exact:
   //     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   // so the four limits are not independent -- they are one budget. A cell can
   // satisfy every individual minimum and still be illegal because the edges ate
   // the slack. This monitor reports which of the five constraints is the tightest
   // (`binding`), not merely whether the cell passed.
   //
   // tf(min) is deliberately NOT checked: Table 10 specifies it as
   // 20 x (VDD / 5.5 V) ns, a supply-dependent value this monitor is not given.
   // tr(min) IS checked -- Table 10 states it as a flat 20 ns for Fast-mode and
   // leaves it unspecified ("-") for Standard-mode and Fast-mode Plus.
   //
   // The line is presented as two comparator outputs rather than one logic level,
   // because a digital level cannot express an edge rate.
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_mode_envelope #(
      parameter TICK_NS = 10,   // ns per clk; every Table 10 value is a multiple of 10
      parameter CNT_W   = 16
   ) (
      input  wire              clk,
      input  wire              rst_n,

      input  wire [1:0]        mode,    // 0 = Standard, 1 = Fast, 2 = Fast-mode Plus
      input  wire              th30,    // 1 when the line is above 30 % of VDD
      input  wire              th70,    // 1 when the line is above 70 % of VDD

      output reg  [CNT_W-1:0]  meas_tlow,    // ns
      output reg  [CNT_W-1:0]  meas_thigh,   // ns
      output reg  [CNT_W-1:0]  meas_tr,      // ns
      output reg  [CNT_W-1:0]  meas_tf,      // ns
      output reg  [CNT_W-1:0]  meas_period,  // ns, = tlow + tr + thigh + tf

      output reg               cell_valid,   // one pulse per completed bit cell
      output reg               legal,        // that cell violated nothing
      output reg  [2:0]        binding,      // tightest constraint, see BIND_* below
      output reg signed [CNT_W:0] worst_margin, // ns of slack on `binding` (negative = violated)

      output reg               viol_tlow,
      output reg               viol_thigh,
      output reg               viol_tr,
      output reg               viol_tf,
      output reg               viol_period,

      output reg  [CNT_W-1:0]  cells_seen,
      output reg  [CNT_W-1:0]  cells_legal
   );

      localparam [1:0] MODE_STD  = 2'd0;
      localparam [1:0] MODE_FAST = 2'd1;
      localparam [1:0] MODE_FMP  = 2'd2;

      localparam [2:0] BIND_NONE   = 3'd0;
      localparam [2:0] BIND_TLOW   = 3'd1;
      localparam [2:0] BIND_THIGH  = 3'd2;
      localparam [2:0] BIND_TR     = 3'd3;
      localparam [2:0] BIND_TF     = 3'd4;
      localparam [2:0] BIND_PERIOD = 3'd5;

      // ---------------------------------------------------------------------
      // Table 10 limits, in ns. Standard / Fast / Fast-mode Plus.
      // ---------------------------------------------------------------------
      function [CNT_W-1:0] lim_tlow_min (input [1:0] m);
         case (m)
            MODE_STD : lim_tlow_min = 16'd4700;
            MODE_FAST: lim_tlow_min = 16'd1300;
            default  : lim_tlow_min = 16'd500;
         endcase
      endfunction

      function [CNT_W-1:0] lim_thigh_min (input [1:0] m);
         case (m)
            MODE_STD : lim_thigh_min = 16'd4000;
            MODE_FAST: lim_thigh_min = 16'd600;
            default  : lim_thigh_min = 16'd260;
         endcase
      endfunction

      function [CNT_W-1:0] lim_tr_max (input [1:0] m);
         case (m)
            MODE_STD : lim_tr_max = 16'd1000;
            MODE_FAST: lim_tr_max = 16'd300;
            default  : lim_tr_max = 16'd120;
         endcase
      endfunction

      // Table 10 gives tr(min) only for Fast-mode. "-" elsewhere means no limit.
      function [CNT_W-1:0] lim_tr_min (input [1:0] m);
         case (m)
            MODE_FAST: lim_tr_min = 16'd20;
            default  : lim_tr_min = 16'd0;
         endcase
      endfunction

      function [CNT_W-1:0] lim_tf_max (input [1:0] m);
         case (m)
            MODE_STD : lim_tf_max = 16'd300;
            MODE_FAST: lim_tf_max = 16'd300;
            default  : lim_tf_max = 16'd120;
         endcase
      endfunction

      // 1 / fSCL(max): 100 kHz, 400 kHz, 1000 kHz.
      function [CNT_W-1:0] lim_period_min (input [1:0] m);
         case (m)
            MODE_STD : lim_period_min = 16'd10000;
            MODE_FAST: lim_period_min = 16'd2500;
            default  : lim_period_min = 16'd1000;
         endcase
      endfunction

      // ---------------------------------------------------------------------
      // Threshold-crossing edge detection
      // ---------------------------------------------------------------------
      reg th30_q, th70_q;
      wire th30_rise =  th30 && !th30_q;
      wire th30_fall = !th30 &&  th30_q;
      wire th70_rise =  th70 && !th70_q;
      wire th70_fall = !th70 &&  th70_q;

      // ---------------------------------------------------------------------
      // Measurement FSM. The bit cell boundary is the 30 % falling edge.
      // ---------------------------------------------------------------------
      localparam [2:0] S_SYNC = 3'd0,  // wait for the first 30 % falling edge
                       S_LOW  = 3'd1,
                       S_RISE = 3'd2,
                       S_HIGH = 3'd3,
                       S_FALL = 3'd4;

      reg [2:0]       state;
      reg [CNT_W-1:0] cnt;
      reg [CNT_W-1:0] acc_tlow, acc_tr, acc_thigh;

      localparam [CNT_W-1:0] TICK = TICK_NS;

      // ---------------------------------------------------------------------
      // Evaluation of a completed cell. Margins are signed: negative = violated.
      // ---------------------------------------------------------------------
      reg signed [CNT_W:0] m_tlow, m_thigh, m_tr, m_tf, m_period;
      reg signed [CNT_W:0] best;
      reg        [2:0]     best_id;
      reg                  cell_ok;
      reg signed [CNT_W:0] m_tr_hi, m_tr_lo;

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            th30_q       <= 1'b1;
            th70_q       <= 1'b1;
            state        <= S_SYNC;
            cnt          <= {CNT_W{1'b0}};
            acc_tlow     <= {CNT_W{1'b0}};
            acc_tr       <= {CNT_W{1'b0}};
            acc_thigh    <= {CNT_W{1'b0}};
            meas_tlow    <= {CNT_W{1'b0}};
            meas_thigh   <= {CNT_W{1'b0}};
            meas_tr      <= {CNT_W{1'b0}};
            meas_tf      <= {CNT_W{1'b0}};
            meas_period  <= {CNT_W{1'b0}};
            cell_valid   <= 1'b0;
            legal        <= 1'b0;
            binding      <= BIND_NONE;
            worst_margin <= {(CNT_W+1){1'b0}};
            viol_tlow    <= 1'b0;
            viol_thigh   <= 1'b0;
            viol_tr      <= 1'b0;
            viol_tf      <= 1'b0;
            viol_period  <= 1'b0;
            cells_seen   <= {CNT_W{1'b0}};
            cells_legal  <= {CNT_W{1'b0}};
         end else begin
            th30_q     <= th30;
            th70_q     <= th70;
            cell_valid <= 1'b0;

            case (state)
               // Do not measure a partial cell: wait for a clean cell boundary.
               S_SYNC: begin
                  if (th30_fall) begin
                     state <= S_LOW;
                     cnt   <= {CNT_W{1'b0}};
                  end
               end

               S_LOW: begin
                  if (th30_rise) begin
                     acc_tlow <= cnt + TICK;   // the crossing tick belongs to the phase
                     cnt      <= {CNT_W{1'b0}};
                     state    <= S_RISE;
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               S_RISE: begin
                  if (th70_rise) begin
                     acc_tr <= cnt + TICK;
                     cnt    <= {CNT_W{1'b0}};
                     state  <= S_HIGH;
                  end else if (th30_fall) begin
                     // Never reached 70 %: an aborted edge. Resynchronise.
                     state <= S_LOW;
                     cnt   <= {CNT_W{1'b0}};
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               S_HIGH: begin
                  if (th70_fall) begin
                     acc_thigh <= cnt + TICK;
                     cnt       <= {CNT_W{1'b0}};
                     state     <= S_FALL;
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               S_FALL: begin
                  if (th30_fall) begin
                     meas_tlow   <= acc_tlow;
                     meas_thigh  <= acc_thigh;
                     meas_tr     <= acc_tr;
                     meas_tf     <= cnt + TICK;
                     meas_period <= acc_tlow + acc_tr + acc_thigh + cnt + TICK;
                     cell_valid  <= 1'b1;
                     cells_seen  <= cells_seen + 1'b1;
                     // ---- verdict for the cell that just completed ----
                     m_tlow   = $signed({1'b0, acc_tlow})         - $signed({1'b0, lim_tlow_min(mode)});
                     m_thigh  = $signed({1'b0, acc_thigh})        - $signed({1'b0, lim_thigh_min(mode)});
                     // tr is bounded on BOTH sides in Fast-mode (20 ns .. 300 ns), so its
                     // margin is the tighter of the two. Where Table 10 gives no minimum
                     // (Standard, Fm+) lim_tr_min is 0 and the upper bound always wins.
                     m_tr_hi  = $signed({1'b0, lim_tr_max(mode)}) - $signed({1'b0, acc_tr});
                     m_tr_lo  = $signed({1'b0, acc_tr})           - $signed({1'b0, lim_tr_min(mode)});
                     m_tr     = (lim_tr_min(mode) != 0 && m_tr_lo < m_tr_hi) ? m_tr_lo : m_tr_hi;
                     m_tf     = $signed({1'b0, lim_tf_max(mode)}) - $signed({1'b0, cnt + TICK});
                     m_period = $signed({1'b0, acc_tlow + acc_tr + acc_thigh + cnt + TICK})
                                                                  - $signed({1'b0, lim_period_min(mode)});

                     viol_tlow   <= (m_tlow   < 0);
                     viol_thigh  <= (m_thigh  < 0);
                     viol_tr     <= (m_tr     < 0);
                     viol_tf     <= (m_tf     < 0);
                     viol_period <= (m_period < 0);

                     cell_ok = !(m_tlow < 0) && !(m_thigh < 0) && !(m_tr < 0)
                               && !(m_tf < 0) && !(m_period < 0);
                     legal <= cell_ok;
                     if (cell_ok) cells_legal <= cells_legal + 1'b1;

                     // Tightest constraint. Ties resolve in table order, so a cell
                     // sitting exactly on two limits reports the earlier one.
                     best    = m_tlow;  best_id = BIND_TLOW;
                     if (m_thigh  < best) begin best = m_thigh;  best_id = BIND_THIGH;  end
                     if (m_tr     < best) begin best = m_tr;     best_id = BIND_TR;     end
                     if (m_tf     < best) begin best = m_tf;     best_id = BIND_TF;     end
                     if (m_period < best) begin best = m_period; best_id = BIND_PERIOD; end
                     worst_margin <= best;
                     binding      <= best_id;
                     cnt         <= {CNT_W{1'b0}};
                     state       <= S_LOW;   // next cell starts at this same edge
                  end else if (th70_rise) begin
                     // The line came back up without crossing 30 %: not a cell end.
                     cnt   <= cnt + TICK;
                     state <= S_HIGH;
                  end else begin
                     cnt <= cnt + TICK;
                  end
               end

               default: state <= S_SYNC;
            endcase
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_envelope_tb.v — the same fifteen cells, independently
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_mode_envelope_tb.sv
   // Independent oracle for i2c_mode_envelope.
   //
   // The bench drives threshold-crossing pairs that describe a bit cell of a known
   // shape, then checks the DUT's measurement AND its verdict against limits the
   // bench holds separately. The bench never reads the DUT's own limit functions.
   //
   // The point of the suite is the budget identity of Table 10:
   //     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   // Tests 1-3 show it holds exactly in all three modes. Test 4 shows the
   // consequence that surprises people: making an edge FASTER while the phases sit
   // at their minima produces an ILLEGAL clock, because the period floor is a
   // floor, not a ceiling.
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_mode_envelope_tb;

      localparam integer TICK_NS = 10;

      localparam [1:0] M_STD  = 2'd0;
      localparam [1:0] M_FAST = 2'd1;
      localparam [1:0] M_FMP  = 2'd2;

      localparam [2:0] BIND_NONE = 3'd0, BIND_TLOW = 3'd1, BIND_THIGH = 3'd2,
                       BIND_TR   = 3'd3, BIND_TF   = 3'd4, BIND_PERIOD = 3'd5;

      reg        clk = 1'b0;
      reg        rst_n = 1'b0;
      reg [1:0]  mode = M_FAST;
      reg        th30 = 1'b1;
      reg        th70 = 1'b1;

      wire [15:0] meas_tlow, meas_thigh, meas_tr, meas_tf, meas_period;
      wire [15:0] cells_seen, cells_legal;
      wire        cell_valid, legal;
      wire [2:0]  binding;
      wire signed [16:0] worst_margin;
      wire        viol_tlow, viol_thigh, viol_tr, viol_tf, viol_period;

      integer errors = 0;
      integer k;    // used by hold() only
      integer n;    // outer loops -- must not be k, hold() clobbers it

      i2c_mode_envelope #(.TICK_NS(TICK_NS), .CNT_W(16)) dut (
         .clk(clk), .rst_n(rst_n), .mode(mode), .th30(th30), .th70(th70),
         .meas_tlow(meas_tlow), .meas_thigh(meas_thigh), .meas_tr(meas_tr),
         .meas_tf(meas_tf), .meas_period(meas_period),
         .cell_valid(cell_valid), .legal(legal), .binding(binding),
         .worst_margin(worst_margin),
         .viol_tlow(viol_tlow), .viol_thigh(viol_thigh), .viol_tr(viol_tr),
         .viol_tf(viol_tf), .viol_period(viol_period),
         .cells_seen(cells_seen), .cells_legal(cells_legal));

      always #(TICK_NS/2) clk = ~clk;

      // -------------------------------------------------------------------
      // Stimulus. Levels change on the negedge so the DUT never has to resolve
      // a transition that lands on its own sampling edge.
      // -------------------------------------------------------------------
      task hold (input integer ns_, input integer b30, input integer b70);
         begin
            @(negedge clk);
            th30 = b30[0];
            th70 = b70[0];
            for (k = 0; k < ns_/TICK_NS; k = k + 1) @(posedge clk);
         end
      endtask

      // One bit cell: LOW, then the rising edge, then HIGH, then the falling edge.
      // The cell is reported by the DUT at the START of the next LOW phase.
      task emit_cell (input integer tlow_, input integer tr_,
                      input integer thigh_, input integer tf_);
         begin
            hold(tlow_,  0, 0);
            hold(tr_,    1, 0);
            hold(thigh_, 1, 1);
            hold(tf_,    1, 0);
         end
      endtask

      task flush;   // a trailing LOW so the last cell is reported
         begin hold(200, 0, 0); end
      endtask

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; th30 = 1'b1; th70 = 1'b1;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            @(posedge clk);
         end
      endtask

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

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

      // Full expectation for one measured cell.
      task expect_cell (input [200*8:1] tag,
                        input integer e_tlow, input integer e_tr,
                        input integer e_thigh, input integer e_tf,
                        input e_legal,
                        input e_vlow, input e_vhigh, input e_vtr, input e_vtf, input e_vper,
                        input [2:0] e_bind, input integer e_margin);
         begin
            $display("%0s", tag);
            ck_int("meas_tlow",   meas_tlow,   e_tlow);
            ck_int("meas_tr",     meas_tr,     e_tr);
            ck_int("meas_thigh",  meas_thigh,  e_thigh);
            ck_int("meas_tf",     meas_tf,     e_tf);
            ck_int("meas_period", meas_period, e_tlow + e_tr + e_thigh + e_tf);
            ck_bit("legal",       legal,       e_legal);
            ck_bit("viol_tlow",   viol_tlow,   e_vlow);
            ck_bit("viol_thigh",  viol_thigh,  e_vhigh);
            ck_bit("viol_tr",     viol_tr,     e_vtr);
            ck_bit("viol_tf",     viol_tf,     e_vtf);
            ck_bit("viol_period", viol_period, e_vper);
            ck_int("binding",     binding,     e_bind);
            ck_int("worst_margin", worst_margin, e_margin);
         end
      endtask

      initial begin
         $display("=== i2c_mode_envelope: Table 10 envelope checks ===");

         // ----------------------------------------------------------------
         // 1. Standard-mode exactly on the budget.
         //    4700 + 1000 + 4000 + 300 = 10000 ns = 1/100 kHz.
         //    Every one of the five margins is zero at once.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_STD;
         emit_cell(4700, 1000, 4000, 300);
         flush;
         expect_cell("T1  Standard-mode, all five limits met exactly",
                     4700, 1000, 4000, 300, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
         ck_int("T1 cells_seen",  cells_seen,  1);
         ck_int("T1 cells_legal", cells_legal, 1);

         // ----------------------------------------------------------------
         // 2. Fast-mode exactly on the budget. 1300+300+600+300 = 2500.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 300, 600, 300);
         flush;
         expect_cell("T2  Fast-mode, all five limits met exactly",
                     1300, 300, 600, 300, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);

         // ----------------------------------------------------------------
         // 3. Fast-mode Plus exactly on the budget. 500+120+260+120 = 1000.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FMP;
         emit_cell(500, 120, 260, 120);
         flush;
         expect_cell("T3  Fast-mode Plus, all five limits met exactly",
                     500, 120, 260, 120, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);

         // ----------------------------------------------------------------
         // 4. THE CONSEQUENCE OF THE IDENTITY.
         //    Fast-mode, phases at their minima, but a FASTER rise (100 ns, well
         //    inside the 20..300 ns window). Every individual parameter is legal;
         //    the cell is illegal because the period fell to 2300 ns, i.e. the
         //    clock is running at 435 kHz. A quicker edge is not a free win.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 100, 600, 300);
         flush;
         expect_cell("T4  Fast-mode: every parameter legal, period floor broken",
                     1300, 100, 600, 300, 1'b0,
                     1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -200);

         // ----------------------------------------------------------------
         // 5. tLOW below its minimum, in isolation. tHIGH is stretched so the
         //    period floor is still met and only one flag can fire.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1200, 300, 1300, 300);
         flush;
         expect_cell("T5  Fast-mode: tLOW 100 ns short, nothing else",
                     1200, 300, 1300, 300, 1'b0,
                     1'b1,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, -100);

         // ----------------------------------------------------------------
         // 6. tHIGH below its minimum, in isolation.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(2000, 300, 500, 300);
         flush;
         expect_cell("T6  Fast-mode: tHIGH 100 ns short, nothing else",
                     2000, 300, 500, 300, 1'b0,
                     1'b0,1'b1,1'b0,1'b0,1'b0, BIND_THIGH, -100);

         // ----------------------------------------------------------------
         // 7. Rise time above tr(max).
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 400, 600, 300);
         flush;
         expect_cell("T7  Fast-mode: tr 100 ns too slow",
                     1300, 400, 600, 300, 1'b0,
                     1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -100);

         // ----------------------------------------------------------------
         // 8. Rise time below tr(min). Table 10 gives Fast-mode a 20 ns FLOOR on
         //    the rise time; an edge can be too fast as well as too slow. The
         //    margin is two-sided, so this ranks as the binding constraint even
         //    though the upper bound has 290 ns of room.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(2000, 10, 600, 300);
         flush;
         expect_cell("T8  Fast-mode: tr 10 ns, below the 20 ns floor",
                     2000, 10, 600, 300, 1'b0,
                     1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -10);

         // ----------------------------------------------------------------
         // 9. Fall time above tf(max).
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 300, 600, 400);
         flush;
         expect_cell("T9  Fast-mode: tf 100 ns too slow",
                     1300, 300, 600, 400, 1'b0,
                     1'b0,1'b0,1'b0,1'b1,1'b0, BIND_TF, -100);

         // ----------------------------------------------------------------
         // 10. "Downward-compatible, not upward-compatible" (UM10204 5.1),
         //     measured rather than asserted.
         //     (a) A perfectly legal STANDARD-mode cell, judged as Fast-mode:
         //         illegal on the rise time alone. Its 1000 ns edge is 3.3x the
         //         Fast-mode allowance.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(4700, 1000, 4000, 300);
         flush;
         expect_cell("T10a legal Standard cell judged as Fast: fails on tr only",
                     4700, 1000, 4000, 300, 1'b0,
                     1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -700);

         //     (b) A perfectly legal FAST-mode cell, judged as Standard-mode:
         //         both phases too short AND the clock too fast. Three flags.
         do_reset;
         mode = M_STD;
         emit_cell(1300, 300, 600, 300);
         flush;
         expect_cell("T10b legal Fast cell judged as Standard: three violations",
                     1300, 300, 600, 300, 1'b0,
                     1'b1,1'b1,1'b0,1'b0,1'b1, BIND_PERIOD, -7500);

         // ----------------------------------------------------------------
         // 11. An aborted rising edge. The line clears 30 % but sags back below
         //     it without ever reaching 70 %. That is not a bit cell and must not
         //     be measured as one; the monitor resynchronises and reports exactly
         //     one cell -- the good one that follows.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         hold(1300, 0, 0);       // LOW
         hold(200,  1, 0);       // above 30 %, never reaches 70 %
         hold(1300, 0, 0);       // back down: aborted
         hold(300,  1, 0);       // now a real edge
         hold(600,  1, 1);
         hold(300,  1, 0);
         flush;
         $display("T11  aborted rising edge is not counted as a cell");
         ck_int("T11 cells_seen",  cells_seen,  1);
         ck_int("T11 cells_legal", cells_legal, 1);
         ck_int("T11 meas_tlow",   meas_tlow,   1300);
         ck_int("T11 meas_tr",     meas_tr,     300);

         // ----------------------------------------------------------------
         // 12. Five back-to-back legal Fast-mode cells: the counters track.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         for (n = 0; n < 5; n = n + 1) emit_cell(1300, 300, 600, 300);
         flush;
         $display("T12  five back-to-back legal cells");
         ck_int("T12 cells_seen",  cells_seen,  5);
         ck_int("T12 cells_legal", cells_legal, 5);

         // ----------------------------------------------------------------
         // 13. Mixed run: 3 legal then 2 illegal. cells_legal must not follow
         //     cells_seen.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FAST;
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 400, 600, 300);   // tr too slow
         emit_cell(1200, 300, 1300, 300);  // tLOW short
         flush;
         $display("T13  three legal then two illegal cells");
         ck_int("T13 cells_seen",  cells_seen,  5);
         ck_int("T13 cells_legal", cells_legal, 3);
         ck_bit("T13 last legal",  legal,       1'b0);
         ck_int("T13 last binding", binding,    BIND_TLOW);

         // ----------------------------------------------------------------
         // 14. Fast-mode Plus one tick under the period floor: 500+110+260+120
         //     = 990 ns. The rise time is legal (110 <= 120) and both phases are
         //     at their minima, so again only the budget catches it.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_FMP;
         emit_cell(500, 110, 260, 120);
         flush;
         expect_cell("T14 Fm+ 10 ns under the 1000 ns period floor",
                     500, 110, 260, 120, 1'b0,
                     1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -10);

         // ----------------------------------------------------------------
         // 15. Standard-mode has no tr(min): a 10 ns rise is legal there, and the
         //     same 10 ns rise was a violation in Fast-mode (T8). Same edge, two
         //     verdicts, because Table 10 specifies the floor only for Fast-mode.
         // ----------------------------------------------------------------
         do_reset;
         mode = M_STD;
         emit_cell(6000, 10, 4000, 300);
         flush;
         // tHIGH and tf both sit exactly on their limits here, so the margin is 0
         // twice over and the documented tie-break (table order) reports tHIGH.
         expect_cell("T15 Standard-mode: a 10 ns rise is legal (no tr floor)",
                     6000, 10, 4000, 300, 1'b1,
                     1'b0,1'b0,1'b0,1'b0,1'b0, BIND_THIGH, 0);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_envelope.vhd — the same monitor in VHDL-2008
   -- ---------------------------------------------------------------------------
   -- i2c_mode_envelope.vhd
   -- Per-mode SCL timing-envelope monitor for Standard-mode, Fast-mode and
   -- Fast-mode Plus. Behavioural twin of i2c_mode_envelope.sv / .v.
   --
   -- Measures one SCL bit cell the way UM10204 defines it -- between the 30 % and
   -- 70 % threshold crossings -- and checks it against the Table 10 limits of the
   -- selected mode:
   --
   --     tLOW   30 % falling  -> 30 % rising     must be >= tLOW(min)
   --     tr     30 % rising   -> 70 % rising     must be <= tr(max), >= tr(min)
   --     tHIGH  70 % rising   -> 70 % falling    must be >= tHIGH(min)
   --     tf     70 % falling  -> 30 % falling    must be <= tf(max)
   --     period tLOW+tr+tHIGH+tf                 must be >= 1/fSCL(max)
   --
   -- In all three modes Table 10 is internally exact:
   --     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   -- so the four limits are one budget, not four independent numbers. A cell can
   -- satisfy every individual minimum and still be illegal because the edges ate
   -- the slack, and this monitor reports which constraint is tightest.
   --
   -- tf(min) is deliberately NOT checked: Table 10 specifies it as
   -- 20 x (VDD / 5.5 V) ns, a supply-dependent value this monitor is not given.
   -- tr(min) IS checked -- a flat 20 ns for Fast-mode, unspecified elsewhere.
   --
   -- The margins are held in plain unconstrained `integer`. A range-constrained
   -- integer subtype would turn an out-of-range intermediate into a simulation
   -- crash instead of a reported violation, which is the wrong failure mode for a
   -- monitor whose whole job is to report violations.
   -- ---------------------------------------------------------------------------

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

   entity i2c_mode_envelope is
      generic (
         TICK_NS : integer := 10;   -- ns per clk; every Table 10 value is a multiple of 10
         CNT_W   : integer := 16
      );
      port (
         clk   : in  std_logic;
         rst_n : in  std_logic;

         mode  : in  std_logic_vector(1 downto 0);  -- 0 = Std, 1 = Fast, 2 = Fm+
         th30  : in  std_logic;                     -- 1 when the line is above 30 % of VDD
         th70  : in  std_logic;                     -- 1 when the line is above 70 % of VDD

         meas_tlow    : out unsigned(CNT_W-1 downto 0);
         meas_thigh   : out unsigned(CNT_W-1 downto 0);
         meas_tr      : out unsigned(CNT_W-1 downto 0);
         meas_tf      : out unsigned(CNT_W-1 downto 0);
         meas_period  : out unsigned(CNT_W-1 downto 0);

         cell_valid   : out std_logic;
         legal        : out std_logic;
         binding      : out unsigned(2 downto 0);
         worst_margin : out integer;

         viol_tlow    : out std_logic;
         viol_thigh   : out std_logic;
         viol_tr      : out std_logic;
         viol_tf      : out std_logic;
         viol_period  : out std_logic;

         cells_seen   : out unsigned(CNT_W-1 downto 0);
         cells_legal  : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_mode_envelope;

   architecture rtl of i2c_mode_envelope is

      constant MODE_STD  : std_logic_vector(1 downto 0) := "00";
      constant MODE_FAST : std_logic_vector(1 downto 0) := "01";

      constant BIND_TLOW   : integer := 1;
      constant BIND_THIGH  : integer := 2;
      constant BIND_TR     : integer := 3;
      constant BIND_TF     : integer := 4;
      constant BIND_PERIOD : integer := 5;

      -- Table 10 limits, in ns.
      function lim_tlow_min (m : std_logic_vector(1 downto 0)) return integer is
      begin
         if    m = MODE_STD  then return 4700;
         elsif m = MODE_FAST then return 1300;
         else                     return 500;
         end if;
      end function;

      function lim_thigh_min (m : std_logic_vector(1 downto 0)) return integer is
      begin
         if    m = MODE_STD  then return 4000;
         elsif m = MODE_FAST then return 600;
         else                     return 260;
         end if;
      end function;

      function lim_tr_max (m : std_logic_vector(1 downto 0)) return integer is
      begin
         if    m = MODE_STD  then return 1000;
         elsif m = MODE_FAST then return 300;
         else                     return 120;
         end if;
      end function;

      -- Table 10 gives tr(min) only for Fast-mode; "-" elsewhere means no limit.
      function lim_tr_min (m : std_logic_vector(1 downto 0)) return integer is
      begin
         if m = MODE_FAST then return 20; else return 0; end if;
      end function;

      function lim_tf_max (m : std_logic_vector(1 downto 0)) return integer is
      begin
         if    m = MODE_STD  then return 300;
         elsif m = MODE_FAST then return 300;
         else                     return 120;
         end if;
      end function;

      -- 1 / fSCL(max): 100 kHz, 400 kHz, 1000 kHz.
      function lim_period_min (m : std_logic_vector(1 downto 0)) return integer is
      begin
         if    m = MODE_STD  then return 10000;
         elsif m = MODE_FAST then return 2500;
         else                     return 1000;
         end if;
      end function;

      -- Measurement FSM. The bit cell boundary is the 30 % falling edge.
      type state_t is (S_SYNC, S_LOW, S_RISE, S_HIGH, S_FALL);
      signal state : state_t := S_SYNC;

      signal th30_q, th70_q : std_logic := '1';
      signal cnt            : integer := 0;
      signal acc_tlow       : integer := 0;
      signal acc_tr         : integer := 0;
      signal acc_thigh      : integer := 0;

      signal n_seen, n_legal : integer := 0;

   begin

      process (clk, rst_n)
         variable m_tlow, m_thigh, m_tr, m_tf, m_period : integer;
         variable m_tr_hi, m_tr_lo                      : integer;
         variable best                                  : integer;
         variable best_id                               : integer;
         variable cell_ok                               : boolean;
         variable tf_now, per_now                       : integer;
         variable th30_rise, th30_fall                  : boolean;
         variable th70_rise, th70_fall                  : boolean;
      begin
         if rst_n = '0' then
            th30_q       <= '1';
            th70_q       <= '1';
            state        <= S_SYNC;
            cnt          <= 0;
            acc_tlow     <= 0;
            acc_tr       <= 0;
            acc_thigh    <= 0;
            meas_tlow    <= (others => '0');
            meas_thigh   <= (others => '0');
            meas_tr      <= (others => '0');
            meas_tf      <= (others => '0');
            meas_period  <= (others => '0');
            cell_valid   <= '0';
            legal        <= '0';
            binding      <= (others => '0');
            worst_margin <= 0;
            viol_tlow    <= '0';
            viol_thigh   <= '0';
            viol_tr      <= '0';
            viol_tf      <= '0';
            viol_period  <= '0';
            n_seen       <= 0;
            n_legal      <= 0;
            cells_seen   <= (others => '0');
            cells_legal  <= (others => '0');

         elsif rising_edge(clk) then
            th30_rise := (th30 = '1') and (th30_q = '0');
            th30_fall := (th30 = '0') and (th30_q = '1');
            th70_rise := (th70 = '1') and (th70_q = '0');
            th70_fall := (th70 = '0') and (th70_q = '1');

            th30_q     <= th30;
            th70_q     <= th70;
            cell_valid <= '0';

            case state is

               -- Do not measure a partial cell: wait for a clean cell boundary.
               when S_SYNC =>
                  if th30_fall then
                     state <= S_LOW;
                     cnt   <= 0;
                  end if;

               when S_LOW =>
                  if th30_rise then
                     acc_tlow <= cnt + TICK_NS;   -- the crossing tick belongs to the phase
                     cnt      <= 0;
                     state    <= S_RISE;
                  else
                     cnt <= cnt + TICK_NS;
                  end if;

               when S_RISE =>
                  if th70_rise then
                     acc_tr <= cnt + TICK_NS;
                     cnt    <= 0;
                     state  <= S_HIGH;
                  elsif th30_fall then
                     -- Never reached 70 %: an aborted edge. Resynchronise.
                     state <= S_LOW;
                     cnt   <= 0;
                  else
                     cnt <= cnt + TICK_NS;
                  end if;

               when S_HIGH =>
                  if th70_fall then
                     acc_thigh <= cnt + TICK_NS;
                     cnt       <= 0;
                     state     <= S_FALL;
                  else
                     cnt <= cnt + TICK_NS;
                  end if;

               when S_FALL =>
                  if th30_fall then
                     tf_now  := cnt + TICK_NS;
                     per_now := acc_tlow + acc_tr + acc_thigh + tf_now;

                     meas_tlow   <= to_unsigned(acc_tlow,  CNT_W);
                     meas_thigh  <= to_unsigned(acc_thigh, CNT_W);
                     meas_tr     <= to_unsigned(acc_tr,    CNT_W);
                     meas_tf     <= to_unsigned(tf_now,    CNT_W);
                     meas_period <= to_unsigned(per_now,   CNT_W);
                     cell_valid  <= '1';
                     n_seen      <= n_seen + 1;
                     cells_seen  <= to_unsigned(n_seen + 1, CNT_W);
                     cnt         <= 0;
                     state       <= S_LOW;   -- next cell starts at this same edge

                     -- ---- verdict for the cell that just completed ----
                     m_tlow   := acc_tlow  - lim_tlow_min(mode);
                     m_thigh  := acc_thigh - lim_thigh_min(mode);
                     -- tr is bounded on BOTH sides in Fast-mode (20 ns .. 300 ns), so
                     -- its margin is the tighter of the two. Where Table 10 gives no
                     -- minimum, lim_tr_min is 0 and the upper bound always wins.
                     m_tr_hi  := lim_tr_max(mode) - acc_tr;
                     m_tr_lo  := acc_tr - lim_tr_min(mode);
                     if lim_tr_min(mode) /= 0 and m_tr_lo < m_tr_hi then
                        m_tr := m_tr_lo;
                     else
                        m_tr := m_tr_hi;
                     end if;
                     m_tf     := lim_tf_max(mode) - tf_now;
                     m_period := per_now - lim_period_min(mode);

                     if m_tlow   < 0 then viol_tlow   <= '1'; else viol_tlow   <= '0'; end if;
                     if m_thigh  < 0 then viol_thigh  <= '1'; else viol_thigh  <= '0'; end if;
                     if m_tr     < 0 then viol_tr     <= '1'; else viol_tr     <= '0'; end if;
                     if m_tf     < 0 then viol_tf     <= '1'; else viol_tf     <= '0'; end if;
                     if m_period < 0 then viol_period <= '1'; else viol_period <= '0'; end if;

                     cell_ok := (m_tlow >= 0) and (m_thigh >= 0) and (m_tr >= 0)
                                and (m_tf >= 0) and (m_period >= 0);
                     if cell_ok then
                        legal       <= '1';
                        n_legal     <= n_legal + 1;
                        cells_legal <= to_unsigned(n_legal + 1, CNT_W);
                     else
                        legal <= '0';
                     end if;

                     -- Tightest constraint. Ties resolve in table order, so a cell
                     -- sitting exactly on two limits reports the earlier one.
                     best    := m_tlow;  best_id := BIND_TLOW;
                     if m_thigh  < best then best := m_thigh;  best_id := BIND_THIGH;  end if;
                     if m_tr     < best then best := m_tr;     best_id := BIND_TR;     end if;
                     if m_tf     < best then best := m_tf;     best_id := BIND_TF;     end if;
                     if m_period < best then best := m_period; best_id := BIND_PERIOD; end if;
                     worst_margin <= best;
                     binding      <= to_unsigned(best_id, 3);

                  elsif th70_rise then
                     -- The line came back up without crossing 30 %: not a cell end.
                     cnt   <= cnt + TICK_NS;
                     state <= S_HIGH;
                  else
                     cnt <= cnt + TICK_NS;
                  end if;

            end case;
         end if;
      end process;

   end architecture rtl;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_envelope_tb.vhd — the same fifteen cells again, with margins held in unconstrained integers
   -- ---------------------------------------------------------------------------
   -- i2c_mode_envelope_tb.vhd
   -- Independent oracle for i2c_mode_envelope. Behavioural twin of the
   -- SystemVerilog and Verilog benches, written against the same limits held
   -- separately from the design.
   --
   -- The point of the suite is the budget identity of Table 10:
   --     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   -- Tests 1-3 show it holds exactly in all three modes. Test 4 shows the
   -- consequence that surprises people: making an edge FASTER while the phases sit
   -- at their minima produces an ILLEGAL clock, because the period floor is a
   -- floor, not a ceiling.
   -- ---------------------------------------------------------------------------

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

   entity i2c_mode_envelope_tb is
   end entity i2c_mode_envelope_tb;

   architecture sim of i2c_mode_envelope_tb is

      constant TICK_NS : integer := 10;
      constant CNT_W   : integer := 16;
      constant TCLK    : time    := 10 ns;

      constant M_STD  : std_logic_vector(1 downto 0) := "00";
      constant M_FAST : std_logic_vector(1 downto 0) := "01";
      constant M_FMP  : std_logic_vector(1 downto 0) := "10";

      constant BIND_TLOW   : integer := 1;
      constant BIND_THIGH  : integer := 2;
      constant BIND_TR     : integer := 3;
      constant BIND_TF     : integer := 4;
      constant BIND_PERIOD : integer := 5;

      signal clk   : std_logic := '0';
      signal rst_n : std_logic := '0';
      signal mode  : std_logic_vector(1 downto 0) := M_FAST;
      signal th30  : std_logic := '1';
      signal th70  : std_logic := '1';

      signal meas_tlow, meas_thigh, meas_tr, meas_tf, meas_period : unsigned(CNT_W-1 downto 0);
      signal cells_seen, cells_legal : unsigned(CNT_W-1 downto 0);
      signal cell_valid, legal : std_logic;
      signal binding : unsigned(2 downto 0);
      signal worst_margin : integer;
      signal viol_tlow, viol_thigh, viol_tr, viol_tf, viol_period : std_logic;

      signal halt   : boolean := false;
      signal errors : integer := 0;

   begin

      dut : entity work.i2c_mode_envelope
         generic map (TICK_NS => TICK_NS, CNT_W => CNT_W)
         port map (
            clk => clk, rst_n => rst_n, mode => mode, th30 => th30, th70 => th70,
            meas_tlow => meas_tlow, meas_thigh => meas_thigh, meas_tr => meas_tr,
            meas_tf => meas_tf, meas_period => meas_period,
            cell_valid => cell_valid, legal => legal, binding => binding,
            worst_margin => worst_margin,
            viol_tlow => viol_tlow, viol_thigh => viol_thigh, viol_tr => viol_tr,
            viol_tf => viol_tf, viol_period => viol_period,
            cells_seen => cells_seen, cells_legal => cells_legal);

      clkgen : process
      begin
         while not halt loop
            clk <= '0'; wait for TCLK/2;
            clk <= '1'; wait for TCLK/2;
         end loop;
         wait;
      end process;

      stim : process
         variable err : integer := 0;

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

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

         -- Levels change on the falling edge so the DUT never has to resolve a
         -- transition that lands on its own sampling edge.
         procedure hold (ns_v : integer; b30 : std_logic; b70 : std_logic) is
         begin
            wait until falling_edge(clk);
            th30 <= b30;
            th70 <= b70;
            for k in 0 to (ns_v/TICK_NS) - 1 loop
               wait until rising_edge(clk);
            end loop;
         end procedure;

         -- One bit cell: LOW, the rising edge, HIGH, the falling edge. The cell is
         -- reported by the DUT at the START of the next LOW phase.
         procedure emit_cell (tlow_v : integer; tr_v : integer;
                              thigh_v : integer; tf_v : integer) is
         begin
            hold(tlow_v,  '0', '0');
            hold(tr_v,    '1', '0');
            hold(thigh_v, '1', '1');
            hold(tf_v,    '1', '0');
         end procedure;

         procedure flush is           -- a trailing LOW so the last cell is reported
         begin
            hold(200, '0', '0');
         end procedure;

         procedure do_reset is
         begin
            wait until falling_edge(clk);
            rst_n <= '0'; th30 <= '1'; th70 <= '1';
            for k in 0 to 2 loop wait until rising_edge(clk); end loop;
            wait until falling_edge(clk);
            rst_n <= '1';
            wait until rising_edge(clk);
         end procedure;

         procedure expect_cell (tag : string;
                                e_tlow, e_tr, e_thigh, e_tf : integer;
                                e_legal : std_logic;
                                e_vlow, e_vhigh, e_vtr, e_vtf, e_vper : std_logic;
                                e_bind : integer; e_margin : integer) is
         begin
            report tag severity note;
            ck_int("meas_tlow",    to_integer(meas_tlow),   e_tlow);
            ck_int("meas_tr",      to_integer(meas_tr),     e_tr);
            ck_int("meas_thigh",   to_integer(meas_thigh),  e_thigh);
            ck_int("meas_tf",      to_integer(meas_tf),     e_tf);
            ck_int("meas_period",  to_integer(meas_period), e_tlow + e_tr + e_thigh + e_tf);
            ck_bit("legal",        legal,        e_legal);
            ck_bit("viol_tlow",    viol_tlow,    e_vlow);
            ck_bit("viol_thigh",   viol_thigh,   e_vhigh);
            ck_bit("viol_tr",      viol_tr,      e_vtr);
            ck_bit("viol_tf",      viol_tf,      e_vtf);
            ck_bit("viol_period",  viol_period,  e_vper);
            ck_int("binding",      to_integer(binding), e_bind);
            ck_int("worst_margin", worst_margin, e_margin);
         end procedure;

      begin
         report "=== i2c_mode_envelope: Table 10 envelope checks ===" severity note;

         -- 1. Standard-mode exactly on the budget.
         --    4700 + 1000 + 4000 + 300 = 10000 ns = 1/100 kHz.
         --    Every one of the five margins is zero at once.
         do_reset;
         mode <= M_STD;
         emit_cell(4700, 1000, 4000, 300);
         flush;
         expect_cell("T1  Standard-mode, all five limits met exactly",
                     4700, 1000, 4000, 300, '1', '0','0','0','0','0', BIND_TLOW, 0);
         ck_int("T1 cells_seen",  to_integer(cells_seen),  1);
         ck_int("T1 cells_legal", to_integer(cells_legal), 1);

         -- 2. Fast-mode exactly on the budget. 1300+300+600+300 = 2500.
         do_reset;
         mode <= M_FAST;
         emit_cell(1300, 300, 600, 300);
         flush;
         expect_cell("T2  Fast-mode, all five limits met exactly",
                     1300, 300, 600, 300, '1', '0','0','0','0','0', BIND_TLOW, 0);

         -- 3. Fast-mode Plus exactly on the budget. 500+120+260+120 = 1000.
         do_reset;
         mode <= M_FMP;
         emit_cell(500, 120, 260, 120);
         flush;
         expect_cell("T3  Fast-mode Plus, all five limits met exactly",
                     500, 120, 260, 120, '1', '0','0','0','0','0', BIND_TLOW, 0);

         -- 4. THE CONSEQUENCE OF THE IDENTITY. Fast-mode, phases at their minima,
         --    but a FASTER rise (100 ns, well inside the 20..300 ns window). Every
         --    individual parameter is legal; the cell is illegal because the period
         --    fell to 2300 ns, i.e. the clock is running at 435 kHz.
         do_reset;
         mode <= M_FAST;
         emit_cell(1300, 100, 600, 300);
         flush;
         expect_cell("T4  Fast-mode: every parameter legal, period floor broken",
                     1300, 100, 600, 300, '0', '0','0','0','0','1', BIND_PERIOD, -200);

         -- 5. tLOW below its minimum, in isolation.
         do_reset;
         mode <= M_FAST;
         emit_cell(1200, 300, 1300, 300);
         flush;
         expect_cell("T5  Fast-mode: tLOW 100 ns short, nothing else",
                     1200, 300, 1300, 300, '0', '1','0','0','0','0', BIND_TLOW, -100);

         -- 6. tHIGH below its minimum, in isolation.
         do_reset;
         mode <= M_FAST;
         emit_cell(2000, 300, 500, 300);
         flush;
         expect_cell("T6  Fast-mode: tHIGH 100 ns short, nothing else",
                     2000, 300, 500, 300, '0', '0','1','0','0','0', BIND_THIGH, -100);

         -- 7. Rise time above tr(max).
         do_reset;
         mode <= M_FAST;
         emit_cell(1300, 400, 600, 300);
         flush;
         expect_cell("T7  Fast-mode: tr 100 ns too slow",
                     1300, 400, 600, 300, '0', '0','0','1','0','0', BIND_TR, -100);

         -- 8. Rise time below tr(min). Table 10 gives Fast-mode a 20 ns FLOOR on the
         --    rise time; an edge can be too fast as well as too slow.
         do_reset;
         mode <= M_FAST;
         emit_cell(2000, 10, 600, 300);
         flush;
         expect_cell("T8  Fast-mode: tr 10 ns, below the 20 ns floor",
                     2000, 10, 600, 300, '0', '0','0','1','0','0', BIND_TR, -10);

         -- 9. Fall time above tf(max).
         do_reset;
         mode <= M_FAST;
         emit_cell(1300, 300, 600, 400);
         flush;
         expect_cell("T9  Fast-mode: tf 100 ns too slow",
                     1300, 300, 600, 400, '0', '0','0','0','1','0', BIND_TF, -100);

         -- 10. "Downward-compatible, not upward-compatible" (UM10204 5.1), measured
         --     rather than asserted.
         --     (a) A legal STANDARD-mode cell judged as Fast-mode: illegal on the
         --         rise time alone. Its 1000 ns edge is 3.3x the Fast-mode allowance.
         do_reset;
         mode <= M_FAST;
         emit_cell(4700, 1000, 4000, 300);
         flush;
         expect_cell("T10a legal Standard cell judged as Fast: fails on tr only",
                     4700, 1000, 4000, 300, '0', '0','0','1','0','0', BIND_TR, -700);

         --     (b) A legal FAST-mode cell judged as Standard-mode: both phases too
         --         short AND the clock too fast. Three flags.
         do_reset;
         mode <= M_STD;
         emit_cell(1300, 300, 600, 300);
         flush;
         expect_cell("T10b legal Fast cell judged as Standard: three violations",
                     1300, 300, 600, 300, '0', '1','1','0','0','1', BIND_PERIOD, -7500);

         -- 11. An aborted rising edge. The line clears 30 % but sags back below it
         --     without ever reaching 70 %. That is not a bit cell and must not be
         --     measured as one.
         do_reset;
         mode <= M_FAST;
         hold(1300, '0', '0');      -- LOW
         hold(200,  '1', '0');      -- above 30 %, never reaches 70 %
         hold(1300, '0', '0');      -- back down: aborted
         hold(300,  '1', '0');      -- now a real edge
         hold(600,  '1', '1');
         hold(300,  '1', '0');
         flush;
         report "T11  aborted rising edge is not counted as a cell" severity note;
         ck_int("T11 cells_seen",  to_integer(cells_seen),  1);
         ck_int("T11 cells_legal", to_integer(cells_legal), 1);
         ck_int("T11 meas_tlow",   to_integer(meas_tlow),   1300);
         ck_int("T11 meas_tr",     to_integer(meas_tr),     300);

         -- 12. Five back-to-back legal Fast-mode cells: the counters track.
         do_reset;
         mode <= M_FAST;
         for k in 0 to 4 loop emit_cell(1300, 300, 600, 300); end loop;
         flush;
         report "T12  five back-to-back legal cells" severity note;
         ck_int("T12 cells_seen",  to_integer(cells_seen),  5);
         ck_int("T12 cells_legal", to_integer(cells_legal), 5);

         -- 13. Mixed run: 3 legal then 2 illegal.
         do_reset;
         mode <= M_FAST;
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 300, 600, 300);
         emit_cell(1300, 400, 600, 300);    -- tr too slow
         emit_cell(1200, 300, 1300, 300);   -- tLOW short
         flush;
         report "T13  three legal then two illegal cells" severity note;
         ck_int("T13 cells_seen",   to_integer(cells_seen),  5);
         ck_int("T13 cells_legal",  to_integer(cells_legal), 3);
         ck_bit("T13 last legal",   legal, '0');
         ck_int("T13 last binding", to_integer(binding), BIND_TLOW);

         -- 14. Fast-mode Plus one tick under the period floor: 500+110+260+120 = 990.
         do_reset;
         mode <= M_FMP;
         emit_cell(500, 110, 260, 120);
         flush;
         expect_cell("T14 Fm+ 10 ns under the 1000 ns period floor",
                     500, 110, 260, 120, '0', '0','0','0','0','1', BIND_PERIOD, -10);

         -- 15. Standard-mode has no tr(min): a 10 ns rise is legal there, and the
         --     same 10 ns rise was a violation in Fast-mode (T8). tHIGH and tf both
         --     sit exactly on their limits, so the tie-break reports tHIGH.
         do_reset;
         mode <= M_STD;
         emit_cell(6000, 10, 4000, 300);
         flush;
         expect_cell("T15 Standard-mode: a 10 ns rise is legal (no tr floor)",
                     6000, 10, 4000, 300, '1', '0','0','0','0','0', BIND_THIGH, 0);

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

   end architecture sim;

8a. Six Decisions Worth Defending

The cell boundary is the 30 % falling edge. Any of the four crossings could have been chosen, and choosing the 30 % fall makes the four phases measurable in one pass with no carry-over between cells: tLOW, then tr, then tHIGH, then tf, then report and immediately begin the next tLOW at the same edge. A boundary anywhere else leaves one phase spanning the report.

The crossing tick belongs to the phase. Each measurement adds one tick when the terminating crossing is seen, so a phase driven for n ticks measures n ticks. Mutation S6 drops that + TICK and 51 checks fail — because every one of the fifteen cells is then short by one tick and the budget no longer closes.

tr has a two-sided margin. Table 10 bounds Fast-mode's rise time above and below. Ranking only the upper bound would report a 10 ns edge as having 290 ns of margin while flagging it as a violation, which is incoherent. Mutation S11 is that incoherence and four checks catch it.

tf(min) is deliberately not checked, and the omission is documented in the source. Table 10 specifies it as 20 × (VDD / 5.5 V) ns — a supply-dependent value this monitor is not given. Implementing it against an assumed supply would be worse than omitting it, because the check would then be wrong rather than absent. This is the same discipline Chapter 11.8 applies to the spike filter's unspecified Standard-mode behaviour.

Ties resolve in table order. A cell sitting exactly on two limits has two margins of zero. Reporting the earlier parameter is arbitrary but it is stated, in the source and in the testbench, so test 15 can assert it rather than discover it. Mutation S8 reorders the ranking and six checks fail.

An aborted edge resynchronises rather than measuring. A line that clears 30 % and sags back without reaching 70 % is not a bit cell, and counting it as one would corrupt the cells_seen total that everything else is judged against. Mutation S9 accepts it and test 11 kills it.

8b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, fifteen cells, one finish time
   $ iverilog -g2012 -o d i2c_mode_envelope.sv i2c_mode_envelope_tb.sv && ./d
   === i2c_mode_envelope: Table 10 envelope checks ===
   T1  Standard-mode, all five limits met exactly
   T2  Fast-mode, all five limits met exactly
   T3  Fast-mode Plus, all five limits met exactly
   T4  Fast-mode: every parameter legal, period floor broken
   T5  Fast-mode: tLOW 100 ns short, nothing else
   T6  Fast-mode: tHIGH 100 ns short, nothing else
   T7  Fast-mode: tr 100 ns too slow
   T8  Fast-mode: tr 10 ns, below the 20 ns floor
   T9  Fast-mode: tf 100 ns too slow
   T10a legal Standard cell judged as Fast: fails on tr only
   T10b legal Fast cell judged as Standard: three violations
   T11  aborted rising edge is not counted as a cell
   T12  five back-to-back legal cells
   T13  three legal then two illegal cells
   T14 Fm+ 10 ns under the 1000 ns period floor
   T15 Standard-mode: a 10 ns rise is legal (no tr floor)
   === i2c_mode_envelope: ALL CHECKS PASSED ===
   i2c_mode_envelope_tb.sv:363: $finish called at 87455000 (1ps)

   $ iverilog -g2005 -o v i2c_mode_envelope.v i2c_mode_envelope_tb.v && ./v
   === i2c_mode_envelope: Table 10 envelope checks ===
   T1  Standard-mode, all five limits met exactly
   T2  Fast-mode, all five limits met exactly
   T3  Fast-mode Plus, all five limits met exactly
   T4  Fast-mode: every parameter legal, period floor broken
   T5  Fast-mode: tLOW 100 ns short, nothing else
   T6  Fast-mode: tHIGH 100 ns short, nothing else
   T7  Fast-mode: tr 100 ns too slow
   T8  Fast-mode: tr 10 ns, below the 20 ns floor
   T9  Fast-mode: tf 100 ns too slow
   T10a legal Standard cell judged as Fast: fails on tr only
   T10b legal Fast cell judged as Standard: three violations
   T11  aborted rising edge is not counted as a cell
   T12  five back-to-back legal cells
   T13  three legal then two illegal cells
   T14 Fm+ 10 ns under the 1000 ns period floor
   T15 Standard-mode: a 10 ns rise is legal (no tr floor)
   === i2c_mode_envelope: ALL CHECKS PASSED ===
   i2c_mode_envelope_tb.v:364: $finish called at 87455000 (1ps)

   $ nvc --std=2008 -a i2c_mode_envelope.vhd i2c_mode_envelope_tb.vhd
   $ nvc --std=2008 -e i2c_mode_envelope_tb && nvc --std=2008 -r i2c_mode_envelope_tb --stop-time=500us
   ** Note: 0ms+0: === i2c_mode_envelope: Table 10 envelope checks ===
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
   ** Note: 10245ns+1: T1  Standard-mode, all five limits met exactly
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:168
   ** Note: 12985ns+1: T2  Fast-mode, all five limits met exactly
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:178
   ** Note: 14225ns+1: T3  Fast-mode Plus, all five limits met exactly
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:186
   ** Note: 16765ns+1: T4  Fast-mode: every parameter legal, period floor broken
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:197
   ** Note: 20105ns+1: T5  Fast-mode: tLOW 100 ns short, nothing else
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:205
   ** Note: 23445ns+1: T6  Fast-mode: tHIGH 100 ns short, nothing else
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:213
   ** Note: 26285ns+1: T7  Fast-mode: tr 100 ns too slow
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:221
   ** Note: 29435ns+1: T8  Fast-mode: tr 10 ns, below the 20 ns floor
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:230
   ** Note: 32275ns+1: T9  Fast-mode: tf 100 ns too slow
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:238
   ** Note: 42515ns+1: T10a legal Standard cell judged as Fast: fails on tr only
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:249
   ** Note: 45255ns+1: T10b legal Fast cell judged as Standard: three violations
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:258
   ** Note: 49495ns+1: T11  aborted rising edge is not counted as a cell
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
   ** Note: 62235ns+1: T12  five back-to-back legal cells
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
   ** Note: 75675ns+1: T13  three legal then two illegal cells
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
   ** Note: 76905ns+1: T14 Fm+ 10 ns under the 1000 ns period floor
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:308
   ** Note: 87455ns+1: T15 Standard-mode: a 10 ns rise is legal (no tr floor)
      Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:318
   ** Note: 87455ns+1: === i2c_mode_envelope: ALL CHECKS PASSED ===
      Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77

All three at 87455 ns, and the finish time is worth a sentence. The three implementations are independent — a SystemVerilog original, a Verilog-2001 port, and a VHDL-2008 rewrite whose margins live in unconstrained integer rather than in sized vectors — and they agree on the cycle. A divergence of even one tick would mean the measurement convention had drifted between languages, which is precisely the failure the VHDL port of Chapter 12.2 caught when both Verilog suites had passed it.

8c. What the Testbench Proves

#stimuluswhat it establishes
1Standard-mode, 4700 / 1000 / 4000 / 300all five margins zero at once — the budget is exact
2Fast-mode, 1300 / 300 / 600 / 300same, at 2500 ns
3Fm+, 500 / 120 / 260 / 120same, at 1000 ns
4Fast-mode with a faster 100 ns riseevery parameter legal, period floor broken
5tLOW 100 ns short, tHIGH stretched to compensatetLOW alone flagged, margin −100
6tHIGH 100 ns short, tLOW stretchedtHIGH alone flagged
7tr = 400 nstr alone flagged
8tr = 10 nstr flagged on its lower bound, margin −10
9tf = 400 nstf alone flagged
10athe Standard cell, judged as Fastfails on tr only
10bthe Fast cell, judged as Standardthree violations
11an aborted rising edge, then a good cellexactly one cell counted
12five back-to-back legal cellsthe counters track
13three legal then two illegalcells_legal does not follow cells_seen
14Fm+ 10 ns under the period flooronly the budget catches it
15Standard-mode with a 10 ns riselegal — no tr floor outside Fast-mode

Tests 1 to 3 are the chapter's central claim, measured. Each drives a cell built from one mode's own table values and asserts that all five margins are simultaneously zero. Any of the six limit functions being wrong by a nanosecond breaks at least one of them.

Test 4 is §4a. It is the only test where the cell is illegal while every individual parameter is legal, and it exists to stop anyone treating Table 10 as a checklist.

Tests 5 and 6 need the compensating stretch, and that is itself instructive. Because the budget is exact, you cannot shorten one phase below its minimum while holding everything else at the limits — the period would fail too, and two flags would fire. Isolating a single violation requires stretching the other phase past its minimum, which is legal. The bench has to work around the identity to test around it.

Test 8 and test 15 are the same 10 ns edge with opposite verdicts. Legal in Standard-mode, a violation in Fast-mode, because Table 10 states the floor only for Fast-mode. A monitor with one hard-coded edge rule gets one of the two wrong.

Test 11 is about the denominator. If an aborted edge were counted, cells_seen would climb on a bus that is glitching rather than transferring, and the legality ratio everything else reports would be measured against a number that means nothing.

9. Mutation Testing

Eleven defects injected into the SystemVerilog monitor, each a single-token change, each required to make the suite fail.

#injected defectoutcome
S1Standard tLOW(min) 4700 → 4600 nskilled — test 1's margin is no longer zero
S2Fast tHIGH(min) 600 → 500 nskilled — 3 checks
S3Fast tr(max) 300 → 400 nskilled — 6 checks, tests 7 and 10a
S4the Fast tr(min) floor removedkilled — test 8
S5Fast period floor 2500 → 2300 nskilled — test 4 stops failing
S6the crossing tick dropped from tLOWkilled — 51 checks
S7the period margin invertedkilled — 30 checks
S8the period ranked first, breaking the tie orderkilled — 6 checks
S9an aborted rising edge accepted as a cellkilled — test 11
S10every cell counted as legalkilled — test 13
S11the tr margin made one-sided againkilled — 4 checks

Eleven of eleven. Three are worth a comment.

S1 changes a limit by 100 ns and exactly one check fails. That is the signature of a suite testing the boundary rather than the interior: the only cell that can detect a 2 % shift in tLOW(min) is the one sitting exactly on it. A suite built from comfortable mid-range stimulus would have missed it entirely, and this is the argument for writing tests 1 to 3 at the limits rather than near them.

S6 fails 51 checks, and that breadth is the point. An off-by-one in the measurement convention is not a local defect; it corrupts every number the block produces. The contrast with S1 is the useful part — a suite where every mutation kills with a similar count is a suite that is only testing one thing.

S5 is the inverse of the others. It widens the period floor so test 4 stops failing, which means the mutation is detected by a test that was asserting a failure. A suite that only ever asserts success cannot catch a limit being loosened, and four of this suite's fifteen cells exist to assert specific violations for that reason.

10. Verification Connection — Parameterising A Mode Rather Than Copying A Bench

A UVM environment for a multi-speed I²C VIP faces a choice that looks cosmetic and is not: is the speed mode a configuration field or a separate agent?

The budget identity settles it. Because the four numbers are one constraint, a mode is a single coherent object and splitting it across three agents means three chances for the numbers to drift apart.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_mode_cfg.sv — the mode as one object, with the identity asserted at build time
   // A speed mode is FOUR NUMBERS THAT MUST SUM TO A FIFTH. Holding them in one
   // config object and checking the identity in the constructor means a typo is a
   // build-time failure rather than a mysterious timing violation three thousand
   // transactions into a regression.
   class i2c_mode_cfg extends uvm_object;
      `uvm_object_utils(i2c_mode_cfg)

      typedef enum { SM, FM, FMP } mode_e;

      rand mode_e mode;
      int unsigned tlow_min_ns, thigh_min_ns, tr_max_ns, tf_max_ns, fscl_max_khz;

      function new(string name = "i2c_mode_cfg");
         super.new(name);
      endfunction

      // UM10204 Table 10, as a single table rather than three scattered constants.
      function void apply(mode_e m);
         mode = m;
         case (m)
            SM : begin tlow_min_ns=4700; thigh_min_ns=4000; tr_max_ns=1000; tf_max_ns=300; fscl_max_khz=100;  end
            FM : begin tlow_min_ns=1300; thigh_min_ns= 600; tr_max_ns= 300; tf_max_ns=300; fscl_max_khz=400;  end
            FMP: begin tlow_min_ns= 500; thigh_min_ns= 260; tr_max_ns= 120; tf_max_ns=120; fscl_max_khz=1000; end
         endcase
         check_identity();
      endfunction

      // THE IDENTITY, AS A BUILD-TIME ASSERTION. If someone edits one number in
      // the table above without editing the frequency, this fires immediately and
      // names the mode -- rather than producing a VIP that drives a subtly illegal
      // clock and blames the DUT.
      function void check_identity();
         int unsigned budget_ns = tlow_min_ns + thigh_min_ns + tr_max_ns + tf_max_ns;
         int unsigned period_ns = 1_000_000 / fscl_max_khz;
         if (budget_ns != period_ns)
            `uvm_fatal("I2C_MODE",
               $sformatf("Table 10 identity broken for %s: %0d+%0d+%0d+%0d = %0d ns but 1/fSCL = %0d ns",
                         mode.name(), tlow_min_ns, thigh_min_ns, tr_max_ns, tf_max_ns,
                         budget_ns, period_ns))
      endfunction

      // The bit period a generator may legally use. Note it is the MAXIMUM of the
      // frequency floor and the phase-plus-edge sum, not either one alone -- which
      // is the whole content of Chapter 11.9's budget.
      function int unsigned min_period_ns(int unsigned tr_actual, int unsigned tf_actual);
         int unsigned from_phases = tlow_min_ns + thigh_min_ns + tr_actual + tf_actual;
         int unsigned from_fscl   = 1_000_000 / fscl_max_khz;
         return (from_phases > from_fscl) ? from_phases : from_fscl;
      endfunction
   endclass

Two things that class does which a scattered set of parameters cannot.

It fails at build time. check_identity runs in apply, so a mis-edited table is a uvm_fatal naming the mode, not a regression failure five thousand transactions later pointing at the DUT. This is the cheapest assertion in the environment and it protects the assumption every timing check downstream depends on.

It returns the binding period, not the frequency period. min_period_ns takes the larger of the two constraints. A generator that used 1/fSCL alone would drive a legal-frequency clock with illegal phases the moment the actual edges were slower than the table's maxima — which is the ordinary situation on a real board, as §7.2.1's remark says outright.

11. FPGA and ASIC Implications

The three modes are one RTL and three constants. Because the protocol is identical, a controller supporting all three needs one datapath and a mode-selected divider — not three state machines. Anything else is a sign that speed has leaked into the protocol logic, and it will diverge under maintenance.

The edge rates are not yours to choose on an FPGA. tr is set by the pull-up and the capacitance, and tf by the pad's drive strength and slope control. A generic FPGA I/O is a fast, strong CMOS driver with no slope control at all, which makes tf too short rather than too long — the opposite of the problem most people expect. Fast-mode's 20 ns tr floor exists for the same physical reason, and Chapter 14.4 sizes the resistor that fixes it.

Slope control is a Fast-mode requirement, not an optimisation. §5.1 lists it among the mandatory additions: "The output buffers of Fast-mode devices incorporate slope control of the falling edges of the SDA and SCL signals." On an FPGA that means selecting a slew-rate-limited I/O standard, and on an ASIC it means an output cell with a controlled edge. A design that meets Table 10's numbers with an uncontrolled edge has met the timing and missed the requirement.

Fm+ changes the pad, not the logic. 20 mA at 0.4 V from an I²C pad is a real specification on the output cell, and it is the entire reason Fm+ can drive 550 pF. If the pads cannot sink 20 mA, the part is a Fast-mode part running at 1 MHz into a small load — which may well work on the bench and will not work on the customer's longer bus.

Derate with Equation 3, not with the headline number. Measure the actual tr and tf on the assembled board, put them into fmax = 1/(tLOW+tHIGH+tr+tf), and use the result. The headline rate assumes the table's edge maxima, and a board with slower edges than that is not a Fast-mode bus no matter what the controller is configured for.

12. Debugging — The Bus That Broke When The Pull-Up Was Improved

Symptom

A Fast-mode bus with a marginal rise time was fixed by dropping the pull-up from 4.7 kΩ to 1.5 kΩ. The rise time went from 340 ns to 110 ns and the scope showed a clean edge. Two slaves that had worked previously began NACKing intermittently, and one stopped responding entirely.

Root Cause

The slow rise time had been silently derating the bus below 400 kHz, and the four Table 10 limits are one budget. Removing 230 ns from the rising edge removed 230 ns from the period, pushing the clock to 437 kHz — over the Fast-mode maximum. Nothing about the edge was wrong; the period that the edge had been padding was never re-checked. The Standard-mode part had been out of specification all along and was only working because the real rate was near its own limit.

Fix
Re-derive the divider from Equation 3 using the measured edges: 1300 + 620 + 110 + 250 = 2280 ns is the phase-plus-edge floor, but 1/400 kHz = 2500 ns is the binding constraint, so the period must be lengthened to 2.5 µs. Then remove the Standard-mode part or drop the whole bus to 100 kbit/s, because §5.1 says it should not be on a Fast-mode bus at all.

The shape of this is worth keeping. An improvement to one parameter consumed the margin of another, because they share a budget. Nothing was measured wrongly and no device was faulty. The period was simply never a free variable, and improving the edge spent it.

It is also the mechanism behind the previous chapter's closing observation: a bus can be out of specification for years and work, because the violation is masked by a second violation in the opposite direction. Fix one and the other surfaces.

13. Common Misconceptions

"Fast-mode is a faster protocol." It is the same protocol with four different numbers. There is no Fast-mode condition, byte format, address, or state that Standard-mode lacks.

"A Fast-mode device runs at 400 kbit/s." The specification's requirement is that it can synchronize with a 400 kbit/s transfer and can stretch. Throughput is not the conformance criterion. §5.

"The four timing limits are independent constraints to check off." They sum exactly to the period in all three modes. Satisfying each individually is necessary and not sufficient. §4a.

"A faster edge is always an improvement." It shortens the period, and the period has a floor. §4a and §12.

"Standard-mode parts are fine on a Fast-mode bus if the master slows down." If the master genuinely runs the whole bus at 100 kbit/s then it is a Standard-mode bus and everything is fine. What §5.1 warns against is a Fast-mode bus with a Standard-mode part on it, where the part sees phases it cannot resolve.

"Fm+ is Fast-mode at 1 MHz." Fm+ specifies a 20 mA output stage against Fast-mode's 3 mA. That is a different pad, and it is what allows 550 pF instead of 400 pF. Chapter 14.4 §5.

"tr only has a maximum." Fast-mode gives it a 20 ns minimum. An edge can be too fast. §6.

"Rise time is a property of the driver." On an open-drain bus the rising edge is made by the resistor and the capacitance, not by any driver. Only the falling edge belongs to the device. Chapter 2.5.

14. Reason It Through

A bus measures tLOW = 4.8 µs, tHIGH = 4.1 µs, tr = 900 ns, tf = 280 ns. Which modes is it legal in?

Sum: 4800 + 4100 + 900 + 280 = 10 080 ns, so 99.2 kHz. Against Standard-mode: every phase is above its minimum, both edges are inside their maxima, and the period exceeds 10 000 ns. Legal. Against Fast-mode: tr of 900 ns is three times the 300 ns allowance. Illegal, on that parameter alone — exactly the shape of §6's first row.

Same bus, and someone asks whether it could be run at 400 kHz by just changing the divider. No. At a 2.5 µs period the 900 ns rise plus the 280 ns fall leave 1320 ns for both phases together, and Fast-mode needs 1300 + 600 = 1900 ns of phase. The bus is 580 ns short before the divider is even considered. The rise time has to come down first, which means the pull-up or the capacitance has to change — it is a board problem, not a configuration one.

A Fm+ controller drives a bus whose measured edges are tr = 300 ns and tf = 250 ns. What is the real maximum rate?

Equation 3 with Fm+'s phase minima: 500 + 260 + 300 + 250 = 1310 ns, so 763 kHz — not 1 MHz. The edges are legal for Standard-mode but far outside Fm+'s own 120 ns maxima, and the phase minima cannot absorb the difference. Configuring the controller for 1 MHz on this board produces phases below Fm+'s minima, which is §12's failure with the numbers changed.

Why can a cell sitting exactly on all five limits report only one binding constraint?

Because five margins of zero is a five-way tie, and the monitor's documented rule resolves ties in table order. That is a reporting convention, not a measurement: the honest reading of binding = tLOW with worst_margin = 0 is "the tightest margin is zero and at least one constraint is tLOW". Test 1 asserts the convention so it cannot drift.

A device datasheet claims Fast-mode compliance but specifies a maximum SCL frequency of 250 kHz. Is that contradictory?

No, and §5 is why. The compliance requirement is the ability to synchronize with a 400 kbit/s transfer and to stretch. A part that can track 400 kHz edges, holds SCL low when it needs time, and cannot process faster than 250 kHz of payload is conforming. What would be contradictory is a part claiming Fast-mode compliance while unable to stretch — because then a 400 kHz master has no way to accommodate it.

15. Understanding Check

16. Summary

The protocol does not change across the three mainstream modes. Four numbers in Table 10 change, and one drive current in Table 9. Everything else — conditions, frames, addresses, acknowledge, stretching, synchronization, arbitration — is identical, which is why thirteen modules could be written without mentioning speed.

The four numbers are one budget. tLOW(min) + tHIGH(min) + tr(max) + tf(max) = 1/fSCL(max), exactly, in all three modes. The table was constructed that way and §7.2.1 states the relation as Equation 3.

So a faster edge can make a clock illegal. Edge time is period, not an addition to it. Improve a rising edge without revisiting the divider and a frequency violation replaces the edge violation — §12 is that bus.

Fast-mode compliance is about synchronizing and stretching, not throughput. A 150 kbit/s device is conforming if it can track 400 kbit/s edges and hold the clock. Both capabilities come from the two preceding modules.

Compatibility is one-way, and one number wide. A legal Standard-mode cell fails Fast-mode on the rise time alone; a legal Fast-mode cell fails Standard-mode on three parameters at once. Going down is safe; going up produces phases a slow input cannot resolve, and nothing on the bus says so.

tr is bounded from below in Fast-mode. 20 ns. An edge can be too fast, and a monitor that ranks only the upper bound reports a violation with positive margin.

Fm+ is a different output stage. 20 mA against 3 mA, and that is what buys 550 pF rather than 400 pF. Its timing is the tightest of the three; its drive is what makes the timing reachable.

Derate from measurement, not from the headline. Equation 3 with the board's real edges is the bus's real maximum rate, and it is usually below the number on the controller's configuration page.

17. What Comes Next

The three modes in this chapter differ in magnitude. The next one differs in kind.

Chapter 14.2 takes High-speed mode, and it is the only speed category that changes the protocol rather than the numbers. Entering it requires a START, an eight-bit master code, and a not-acknowledge — all at Fast-mode speed — after which the winning master switches to a different electrical regime: an open-drain pull-down with a current-source pull-up on the clock, a bus that may be bridged into two segments, and arbitration that is over before the fast phase begins.

Three things from this chapter carry straight into it. The budget identity will be tested against Hs-mode's own table and will fail there — by exactly 2.00 % at both capacitance corners, which turns out to explain where the number 3.4 Mbit/s came from. The tr maximum acquires a second value, trCL1, that applies only after a repeated START and after every acknowledge — and the reason is a current source being deliberately switched off so that a slave can still stretch. And the arbitration of Module 13 decides whether the high-speed phase happens at all, on a single bit.

Continue learning