Skip to content
VLSI Mentor

I²C · Module 11

tr, tf and Cb — The I²C Electrical Envelope

The only parameters an RTL designer cannot control, and the only edge with a minimum as well as a maximum. Derives the pull-up sizing window and finds that Fast-mode at the maximum bus capacitance does not close with a resistor at all.

Six chapters have treated an edge as an instant — the moment at which a level is sampled or an interval begins. Every parameter so far has been a duration between edges: the clock's phases, the data window, the framing margins that Chapter 11.6 closed. That simplification has been doing real work, and this chapter drops it.

tr, tf and Cb are different from everything else in Table 10 in three ways at once:

No device can meet tr by itself — it is set by the pull-up and the bus capacitance together, so a board can violate it with entirely compliant silicon on it.

tr has a MINIMUM as well as a maximum in Fast-mode — the only parameter in the table where being too fast is a violation.

Cb is not a time at all. It is the one entry in a timing table that is a capacitance, because it is the term that converts a board layout into a timing number.

This is also where two earlier results close. Chapter 11.2 §4 found tr and tf sitting inside the clock's period budget; Chapter 11.4 §4 found a Fast-mode-Plus deficit of exactly tr(max) and concluded the specification expects the pull-up to be beaten. §4 here derives what beating it costs, and finds a window that does not always exist.

1. The Three Rows

Four things to extract, and each one shapes a section below.

tr(max) is 1000 ns in Standard-mode and 300 ns in Fast-mode — a factor of 3.3 — while tf(max) is 300 ns in BOTH. The rise slackens as the bus slows; the fall does not. §2 explains why, and the reason is that they are produced by completely different mechanisms.

Fast-mode imposes a tr minimum of 20 ns. Everywhere else in this module, faster is safer. Here it is not, and §5 is about why — a reason that connects directly to Chapter 11.8.

Cb rises from 400 pF to 550 pF at Fast-mode Plus, which is the opposite of what a faster mode should permit. §4 resolves that apparent contradiction, and the resolution is the key to the whole chapter.

Both tr and tf apply to "both SDA and SCL signals". One envelope for both lines — which matters because they usually have different capacitances (SCL fans out to every device's clock input; SDA to every device's data input and output) and are usually given identical pull-ups.

2. Rise Is Passive, Fall Is Active

I²C is open-drain (Chapter 2.3). That single architectural fact produces the whole asymmetry of §1's first observation.

A falling edge is driven. A device turns on a transistor that sinks current, and the line is pulled down actively. The fall time is set by how much current the transistor can sink against the bus capacitance — and the specification guarantees at least 3 mA, which is a lot into 400 pF. Falls are fast and roughly independent of the pull-up.

A rising edge is not driven by anything. Every device has released the line, and it rises only because the pull-up resistor charges the bus capacitance. Nothing is driving it; it is an RC relaxation.

A fall is a driver's specification. A rise is a board's.

That is why tr(max) relaxes by 3.3× between Fast-mode and Standard-mode while tf(max) stays at 300 ns: the fall is limited by silicon that does not change with the speed grade, and the rise is limited by an RC product the board designer chooses.

A vertical arrangement showing the supply at the top connected through a pull-up element to a single bus node. The bus node has a total capacitance to ground shown beside it. Below the bus node, three device pads are each connected upward to it, representing open-drain outputs that can pull the node low. Edges are labelled to show that the pull-up charges the node passively while any pad can discharge it actively.VDD1.8 V to 5 VPull-up Rpor a current sourceSDA or SCL nodeone wire, every deviceCb totalpads, traces, connectorsMaster padsinks 3 mA minSlave 1 padsinks 3 mA minSlave 2 padsinks 3 mA minchargespulls low12
The pull-up network. A rise happens only because the pull-up charges the total bus capacitance with no device driving, so tr is set by the Rp-Cb product. A fall is driven actively by whichever pad turns on its sink transistor, so tf is set by that transistor. Two edges, two entirely different mechanisms, two different limits.

Note what the diagram says about Cb: it is the total — every device's pad capacitance, every trace, every connector and every via on the net. It is not a property of any one component, which is why no device's datasheet can promise tr compliance.

3. Measuring an Edge That Is Not an Instant

An edge has a duration, so it has to be measured between two voltages. The specification's thresholds are 0.3 VDD and 0.7 VDD, so tr is the time to cross from 30 % to 70 % of the supply.

That has a direct consequence for how a monitor is built: RTL cannot see a voltage, so the honest digital model of an analog edge is two threshold comparators, and the edge duration is the interval between their outputs changing. That is exactly how a real bus monitor works, and it is what §6's design takes as input.

tr is the interval between the 0.3 VDD and 0.7 VDD crossings

10 cycles
Ten intervals showing two comparator outputs during a rising edge on one bus line. The lower-threshold comparator asserts first and the upper-threshold comparator asserts four intervals later. A region row marks the interval between the two crossings as the rise time, with the line settled before and after.tr: max 300 ns Fasttr: max 300 ns Fastcrosses 0.3 VDDcrosses 0.3 VDDcrosses 0.7 VDDcrosses 0.7 VDDabove 0.3above 0.7regionlowlowrisingrisingrisingrisinghighhighhighhight0t1t2t3t4t5t6t7t8t9
An edge measured between two thresholds. The interval during which the upper comparator has not yet asserted while the lower one already has is the rise time. A monitor sees two digital signals; the analog ramp between them is inferred, not observed.

Two consequences of the two-comparator model worth stating.

A monitor's own resolution bounds what it can report. At a 100 MHz sample clock, one tick is 10 ns, and Fast-mode's tr(min) is 20 ns — two ticks. So a monitor sampling at 100 MHz can barely distinguish a legal fast edge from an illegal one, and a monitor for the tr minimum needs a faster clock than one for the maximum. §10 gives the numbers.

An edge that never completes is a distinct failure. If the lower comparator asserts and the upper one never does, the line started rising and stalled — a pull-up that is too weak for the capacitance, or a device holding the line in a partial state. Like Chapter 11.4 §5's missing transition, this must be reported on expiry rather than waited for, because tr is a maximum.

4. Deriving the Pull-Up Window — and Finding It Empty

This is the section that turns the table into a board decision. Two constraints bound the pull-up from opposite sides.

The lower bound comes from the driver. When a device pulls the line low it must reach VOL(max) = 0.4 V while sinking no more than the guaranteed IOL = 3 mA. The pull-up carries that current, so:

Rp(min) = (VDD − VOL(max)) / IOL

The upper bound comes from tr. The rise is an RC relaxation from 0.3 VDD to 0.7 VDD, so it takes Rp × Cb × ln(0.7/0.3). And ln(7/3) = 0.8473, which is where the specification's own coefficient comes from:

Rp(max) = tr(max) / (0.8473 × Cb)

Now put numbers in. Rp(min) at three common supplies:

VDDRp(min) = (VDD − 0.4) / 3 mA
5.0 V1533 Ω
3.3 V967 Ω
1.8 V467 Ω

And Rp(max) at each mode's maximum Cb:

modetr(max)Cb(max)Rp(max)
Standard1000 ns400 pF2951 Ω
Fast300 ns400 pF885 Ω
Fm+120 ns550 pF258 Ω

Cross them, and the result is not what the table's headline numbers suggest:

mode, at Cb(max)5.0 V3.3 V1.8 V
Standard (1533…2951)closes967…2951 closes467…2951 closes
Fast (…885)1533 > 885 — EMPTY967 > 885 — EMPTY467…885 closes
Fm+ (…258)EMPTYEMPTY467 > 258 — EMPTY

Fast-mode at the maximum 400 pF has no valid resistor at 3.3 V or 5 V. Fast-mode Plus at 550 pF has none at any of the three supplies.

The practical form for Fast-mode with a resistor is to work backwards to the capacitance a resistor can serve:

Cb(max, resistive) = tr(max) / (0.8473 × Rp(min))

mode5.0 V3.3 V1.8 V
Standard770 pF — Cb limit binds first1221 pF — Cb binds2528 pF — Cb binds
Fast231 pF366 pF758 pF — Cb binds
Fm+92 pF146 pF303 pF

So a 5 V Fast-mode bus with a resistive pull-up is limited to about 230 pF, not 400 pF — a little over half the table's figure. That is the number to design to, and it is nowhere in Table 10 because it is a consequence of the table rather than an entry in it.

And note the inversion: in Standard-mode the Cb limit of 400 pF binds before the resistor does at every supply, so the table's number is the real constraint. In Fast-mode and Fm+ the resistor binds first, and the table's number is unreachable. Which constraint dominates flips with the speed grade.

5. The Only Minimum on an Edge

Fast-mode's tr(min) of 20 ns is the sole place in Table 10 where being faster is a violation, and the reason is worth understanding because it connects two chapters.

A fast edge on a long wire rings. A bus line with connectors and stubs is not a lumped capacitance; it has inductance, and a sufficiently fast edge excites it. The ringing overshoots and undershoots, and an undershoot that crosses back below a receiver's threshold looks like a second edge.

So a too-fast rise can produce what a receiver sees as a transition, a reversal, and another transition — three events where the transmitter produced one. On SCL that is a spurious clock pulse; on SDA a spurious data change.

That is precisely what Chapter 11.8's tSP exists to absorb. tSP is 0 to 50 ns in Fast-mode and Fm+, and a device must suppress pulses shorter than it. The two parameters are two halves of one defence:

the parameterthe obligation
the transmitting sidetr(min) = 20 nsdo not produce edges fast enough to ring badly
the receiving sidetSP = 0…50 nssuppress the short pulses that ringing produces anyway

Neither alone is sufficient, and Standard-mode has neither — no tr minimum and no tSP requirement — because a 1000 ns rise cannot ring. The two constraints appear together at Fast-mode for the same underlying reason.

§6's monitor checks the two maxima and deliberately not this minimum, for the resolution reason §6a gives: 20 ns is two ticks of a 100 MHz sample clock, and a verdict with that little resolution is not a verdict.

6. The Edge Rate Monitor in Three Languages

The design takes the two threshold comparators for one line, measures each edge between them, and reports three things: the edge duration with its direction, a verdict per direction, and the effective high time measured between the VIH crossings.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_rate_monitor.sv — an analog edge measured through two threshold comparators
   // THE ELECTRICAL ENVELOPE: tr, tf and what they steal from the clock.
   //
   // Table 10:
   //     tr  rise time of both SDA and SCL   max 1000 / 300 / 120 ns
   //     tf  fall time of both SDA and SCL   max  300 / 300 / 120 ns
   //     Cb  capacitive load per bus line    max  400 / 400 / 550 pF
   //
   // THE FIRST THING TO SAY IS THAT A SINGLE DIGITAL SAMPLE CANNOT MEASURE AN EDGE RATE.
   // Every other block in this module works from `sda_in` and `scl_in` -- one bit each, the
   // output of an input buffer that has already decided high or low. An edge rate is the TIME
   // BETWEEN TWO VOLTAGE THRESHOLDS, and a one-bit sample has thrown that away before this
   // logic ever sees it. So this block takes TWO comparator outputs per line:
   //
   //     *_above_vil   the line is above the LOW threshold   (0.3 VDD)
   //     *_above_vih   the line is above the HIGH threshold  (0.7 VDD)
   //
   // and the rise time is the interval between those two crossings. That extra hardware is
   // the price of measuring an edge on-chip, and it is why compliance measurement is normally
   // done with a scope: a normal I2C input stage physically cannot report tr.
   //
   // WHY THE EDGES MATTER TO THE CLOCK. Table 10 contains an identity that is exact in all
   // three speed modes:
   //
   //     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   //
   //     Standard   4.7 + 4.0  + 1.000 + 0.300 = 10.0 us  = 1/100 kHz
   //     Fast       1.3 + 0.6  + 0.300 + 0.300 =  2.5 us  = 1/400 kHz
   //     Fm+        0.5 + 0.26 + 0.120 + 0.120 =  1.0 us  = 1/1000 kHz
   //
   // So the period is fully accounted for and the edges are part of the budget, not an
   // afterthought. Every nanosecond an edge takes beyond the assumed value has to come out of
   // a phase -- and since the phases have minima, a slow edge does not merely soften the
   // waveform, it eventually makes the clock illegal. This block therefore reports both the
   // measured edges AND the high time that survives them, which is the number a budget cares
   // about and the one Chapter 11.9 closes.
   //
   // Cb is not measurable here at all: capacitance is what CAUSES a slow edge, and this block
   // sees the consequence. Chapter 11.7 works the relationship the other way, from Cb and the
   // pull-up to the edge it produces.
   //
   // PASSIVE: observes comparator outputs and drives nothing.
   module i2c_edge_rate_monitor #(
       parameter int TICK_W = 16,
       // Fast-mode maxima, in ticks of a 100 MHz sample clock: 300 ns = 30 ticks each.
       parameter int T_R_MAX = 30,
       parameter int T_F_MAX = 30
   )(
       input  logic clk,
       input  logic rst_n,

       // Two thresholds per line. A conventional input stage provides only one of these;
       // measuring an edge requires the second.
       input  logic scl_above_vil,
       input  logic scl_above_vih,

       // ---- measured, on SCL ----
       output logic              edge_valid,      // pulse: an edge has been measured
       output logic              edge_was_rise,
       output logic [TICK_W-1:0] t_edge,

       // ---- verdicts ----
       output logic viol_tr,
       output logic viol_tf,

       // ---- the consequence for the clock ----
       // The high phase measured between the VIH crossings -- which is the time the line is
       // ACTUALLY above the high threshold, and therefore the tHIGH a receiver really gets.
       // A design that measured between the VIL crossings instead would report a longer high
       // phase than exists and would hide exactly the erosion this block is for.
       output logic              high_valid,
       output logic [TICK_W-1:0] t_high_effective,

       // ---- totals and worst cases: these are MAXIMA, so the worst case is the LARGEST ----
       output logic [TICK_W-1:0] n_rise,
       output logic [TICK_W-1:0] n_fall,
       output logic [TICK_W-1:0] n_viol,
       output logic [TICK_W-1:0] max_tr_seen,
       output logic [TICK_W-1:0] max_tf_seen
   );
       logic vil_q, vih_q;
       logic vil_rise, vil_fall, vih_rise, vih_fall;
       assign vil_rise = !vil_q &&  scl_above_vil;
       assign vil_fall =  vil_q && !scl_above_vil;
       assign vih_rise = !vih_q &&  scl_above_vih;
       assign vih_fall =  vih_q && !scl_above_vih;

       // A rising edge is timed from the VIL crossing to the VIH crossing; a falling edge from
       // the VIH crossing back down to the VIL crossing. Two timers, because a rise and a fall
       // can never be in flight at the same time but the arming conditions differ.
       logic              r_arm, f_arm;
       logic [TICK_W-1:0] e_ticks;
       logic [TICK_W-1:0] e_now;
       assign e_now = e_ticks + 1'b1;

       // The effective high time, measured VIH crossing to VIH crossing.
       logic              h_arm;
       logic [TICK_W-1:0] h_ticks;
       logic [TICK_W-1:0] h_now;
       assign h_now = h_ticks + 1'b1;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               vil_q            <= 1'b1;    // an idle bus is high, so above both thresholds
               vih_q            <= 1'b1;
               r_arm            <= 1'b0;
               f_arm            <= 1'b0;
               e_ticks          <= '0;
               h_arm            <= 1'b0;
               h_ticks          <= '0;
               edge_valid       <= 1'b0;
               edge_was_rise    <= 1'b0;
               t_edge           <= '0;
               viol_tr          <= 1'b0;
               viol_tf          <= 1'b0;
               high_valid       <= 1'b0;
               t_high_effective <= '0;
               n_rise           <= '0;
               n_fall           <= '0;
               n_viol           <= '0;
               max_tr_seen      <= '0;
               max_tf_seen      <= '0;
           end else begin
               vil_q <= scl_above_vil;
               vih_q <= scl_above_vih;

               edge_valid <= 1'b0;
               high_valid <= 1'b0;

               // ---- arm a RISE measurement when the line crosses the low threshold upward ----
               if (vil_rise) begin
                   r_arm   <= 1'b1;
                   f_arm   <= 1'b0;
                   e_ticks <= '0;
               // ---- arm a FALL measurement when it crosses the high threshold downward ----
               end else if (vih_fall) begin
                   f_arm   <= 1'b1;
                   r_arm   <= 1'b0;
                   e_ticks <= '0;
               end else if (r_arm && vih_rise) begin
                   // The rise completed: VIL to VIH.
                   r_arm         <= 1'b0;
                   edge_valid    <= 1'b1;
                   edge_was_rise <= 1'b1;
                   t_edge        <= e_now;
                   viol_tr       <= (e_now > T_R_MAX[TICK_W-1:0]);
                   viol_tf       <= 1'b0;
                   n_rise        <= n_rise + 1'b1;
                   if (e_now > T_R_MAX[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
                   if (e_now > max_tr_seen) max_tr_seen <= e_now;
               end else if (f_arm && vil_fall) begin
                   // The fall completed: VIH down to VIL.
                   f_arm         <= 1'b0;
                   edge_valid    <= 1'b1;
                   edge_was_rise <= 1'b0;
                   t_edge        <= e_now;
                   viol_tf       <= (e_now > T_F_MAX[TICK_W-1:0]);
                   viol_tr       <= 1'b0;
                   n_fall        <= n_fall + 1'b1;
                   if (e_now > T_F_MAX[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
                   if (e_now > max_tf_seen) max_tf_seen <= e_now;
               end else if (r_arm || f_arm) begin
                   e_ticks <= e_now;
               end

               // ---- the effective high time, VIH to VIH ----
               if (vih_rise) begin
                   h_arm   <= 1'b1;
                   h_ticks <= '0;
               end else if (h_arm && vih_fall) begin
                   h_arm            <= 1'b0;
                   high_valid       <= 1'b1;
                   t_high_effective <= h_now;
               end else if (h_arm) begin
                   h_ticks <= h_now;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_rate_monitor_tb.sv — eleven scenarios, the two limits deliberately different
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tr(max) = tf(max) = 300 ns = 30 ticks.
   //
   // The stimulus drives the TWO THRESHOLD comparators directly, because that is the only way
   // an edge rate can be presented to logic. A slow edge is modelled as the two crossings
   // being far apart in time; a fast edge as them being close together. That is not an
   // approximation -- it is exactly what the comparators see.
   module i2c_edge_rate_monitor_tb;
       localparam int TICK_W  = 16;
       localparam int T_R_MAX = 30;
       // Deliberately DIFFERENT from T_R_MAX. Standard-mode's tr(max) and tf(max) differ by
       // more than 3x (1000 ns against 300 ns), so unequal limits are the realistic case -- and
       // with them equal, a design judging both against one limit is unobservable.
       localparam int T_F_MAX = 20;

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

       logic rst_n = 1'b0;
       logic scl_above_vil = 1'b1, scl_above_vih = 1'b1;   // idle bus: above both thresholds

       logic edge_valid, edge_was_rise, viol_tr, viol_tf, high_valid;
       logic [TICK_W-1:0] t_edge, t_high_effective, n_rise, n_fall, n_viol;
       logic [TICK_W-1:0] max_tr_seen, max_tf_seen;

       int errors = 0;
       int base;
       logic [TICK_W-1:0] viol_before, nrise_before;
       logic [TICK_W-1:0] h_fast, h_slow;
       int hf, hs;

       i2c_edge_rate_monitor #(.TICK_W(TICK_W), .T_R_MAX(T_R_MAX), .T_F_MAX(T_F_MAX))
         dut (.*);

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

       logic [TICK_W-1:0] e_log [0:31];
       logic r_log [0:31];
       logic vr_log [0:31];
       logic vf_log [0:31];
       int n_log;
       always @(posedge clk) if (rst_n && edge_valid && n_log < 32) begin
           e_log[n_log] = t_edge;  r_log[n_log]  = edge_was_rise;
           vr_log[n_log] = viol_tr; vf_log[n_log] = viol_tf;
           n_log++;
       end

       logic [TICK_W-1:0] h_log [0:31];
       int n_hlog;
       always @(posedge clk) if (rst_n && high_valid && n_hlog < 32) begin
           h_log[n_hlog] = t_high_effective; n_hlog++;
       end

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

       // A FALLING edge taking `tf` ticks: the line drops below VIH, then `tf` ticks later it
       // drops below VIL. Between the two it is in the undefined region.
       task automatic fall_edge(input int tf);
           begin
               scl_above_vih = 1'b0; tick(tf);
               scl_above_vil = 1'b0;
           end
       endtask

       // A RISING edge taking `tr` ticks: above VIL first, then above VIH `tr` ticks later.
       task automatic rise_edge(input int tr);
           begin
               scl_above_vil = 1'b1; tick(tr);
               scl_above_vih = 1'b1;
           end
       endtask

       // One SCL period with settable edge rates and phase widths. `hi` is the time spent
       // fully above VIH; the effective high time the block reports is that plus the sample
       // in which the crossing is observed.
       task automatic scl_period(input int tf, input int lo, input int tr, input int hi);
           begin
               fall_edge(tf);
               tick(lo);
               rise_edge(tr);
               tick(hi);
           end
       endtask

       initial begin
           tick(3);
           // These are MAXIMA, so the worst-case trackers start at zero.
           if (max_tr_seen !== '0 || max_tf_seen !== '0) begin
               $display("FAIL: the maximum trackers did not start at zero"); errors++; end
           rst_n = 1'b1; tick(2);
           scl_above_vil = 1'b1; scl_above_vih = 1'b1; tick(20);

           // ---- 1: FAST edges, well inside the limits. 10 ticks where 30 is the maximum.
           //      Nothing may be flagged -- for a maximum, smaller is safer.
           repeat (4) scl_period(10, 100, 10, 80);
           tick(20);
           if (n_viol !== '0) begin
               $display("FAIL: %0d fast edges flagged", n_viol); errors++; end
           if (n_rise < 16'd4 || n_fall < 16'd4) begin
               $display("FAIL: %0d rises and %0d falls measured from four periods",
                        n_rise, n_fall); errors++; end
           // The first edge measured is a FALL, because the stimulus starts from an idle high
           // bus. Getting the polarity right matters: a block that labelled it a rise would
           // then judge every edge against the wrong parameter.
           if (r_log[0] !== 1'b0) begin
               $display("FAIL: the first edge from an idle-high bus was labelled a RISE");
               errors++; end
           if (e_log[0] < 16'd8 || e_log[0] > 16'd12) begin
               $display("FAIL: a 10-tick fall measured %0d", e_log[0]); errors++; end

           // ---- 2: a SLOW RISE. 50 ticks where tr(max) is 30. Only the rise verdict fires.
           begin
               base = n_log;
               scl_period(10, 100, 50, 80);
               tick(20);
               // index base is the fall, base+1 the rise
               if (r_log[base + 1] !== 1'b1) begin
                   $display("FAIL: the second edge of a period should be the RISE"); errors++; end
               if (vr_log[base + 1] !== 1'b1) begin
                   $display("FAIL: a 50-tick rise was not flagged (tr max %0d)", T_R_MAX);
                   errors++; end
               if (vf_log[base + 1] !== 1'b0) begin
                   $display("FAIL: a slow RISE also flagged the FALL parameter"); errors++; end
               if (max_tr_seen < 16'd45) begin
                   $display("FAIL: max_tr_seen = %0d after a 50-tick rise", max_tr_seen);
                   errors++; end
           end

           // ---- 3: a SLOW FALL. The mirror -- tr and tf are separate parameters with
           //      separate limits, and in Standard-mode they differ by more than 3x, so a
           //      block that judged both against one limit would be wrong in Standard-mode
           //      even if it looked right in Fast-mode where the two happen to be equal.
           begin
               base = n_log;
               scl_period(60, 100, 10, 80);
               tick(20);
               if (vf_log[base] !== 1'b1) begin
                   $display("FAIL: a 60-tick fall was not flagged (tf max %0d)", T_F_MAX);
                   errors++; end
               if (vr_log[base] !== 1'b0) begin
                   $display("FAIL: a slow FALL also flagged the RISE parameter"); errors++; end
               if (max_tf_seen < 16'd55) begin
                   $display("FAIL: max_tf_seen = %0d after a 60-tick fall", max_tf_seen);
                   errors++; end
           end

           // ---- 3b: THE SEPARATION TEST. A 25-tick edge is LEGAL as a rise (max 30) and
           //      ILLEGAL as a fall (max 20). One duration, two verdicts, decided only by which
           //      parameter the edge is judged against.
           begin
               base = n_log;
               scl_period(25, 100, 25, 80);
               tick(20);
               if (vf_log[base] !== 1'b1) begin
                   $display("FAIL: a 25-tick FALL was not flagged against its own 20-tick limit -- both edges are being judged by one limit");
                   errors++; end
               if (vr_log[base + 1] !== 1'b0) begin
                   $display("FAIL: a 25-tick RISE was flagged against a 30-tick limit"); errors++; end
           end

           // ---- 4: BOUNDARIES. Exactly AT the maximum is legal; one tick above is not.
           begin
               base = n_log;
               scl_period(T_F_MAX, 100, T_R_MAX, 80);
               tick(20);
               if (vf_log[base] !== 1'b0) begin
                   $display("FAIL: a fall of EXACTLY tf(max) was rejected"); errors++; end
               if (vr_log[base + 1] !== 1'b0) begin
                   $display("FAIL: a rise of EXACTLY tr(max) was rejected"); errors++; end
           end
           begin
               base = n_log;
               scl_period(T_F_MAX + 1, 100, T_R_MAX + 1, 80);
               tick(20);
               if (vf_log[base] !== 1'b1) begin
                   $display("FAIL: a fall one tick above the maximum was accepted"); errors++; end
               if (vr_log[base + 1] !== 1'b1) begin
                   $display("FAIL: a rise one tick above the maximum was accepted"); errors++; end
           end

           // ---- 5: THE POINT OF THE BLOCK. The effective high time is measured between the
           //      VIH crossings, so it EXCLUDES the transition. Two periods asking for the
           //      same 80 ticks fully above VIH, one reached by a fast rise and one by a slow
           //      one, must report the SAME effective high time -- a VIL-based measurement
           //      would have included the rise and reported a much longer phase for the slow
           //      edge, hiding exactly the erosion this block exists to expose.
           //
           //      Each high phase is closed with an explicit falling edge before it is read.
           //      A high-phase measurement is reported by the fall that ENDS it, so reading
           //      the log straight after the period that opened it returns the PREVIOUS
           //      period's value -- which it did, before these tests were rewritten.
           begin
               scl_period(10, 100, 10, 80);
               fall_edge(10);                 // close the high phase so it is reported
               tick(20);
               h_fast = h_log[n_hlog - 1];

               scl_above_vil = 1'b1; tick(20);
               scl_period(10, 100, 60, 80);
               fall_edge(10);
               tick(20);
               h_slow = h_log[n_hlog - 1];

               if (h_slow > h_fast + 16'd10) begin
                   $display("FAIL: a 60-tick rise reported effective high %0d against %0d for a 10-tick rise -- the measurement is VIL-based, not VIH-based",
                            h_slow, h_fast); errors++; end
               if (h_fast < 16'd75 || h_fast > 16'd90) begin
                   $display("FAIL: an 80-tick fully-high interval measured %0d", h_fast);
                   errors++; end
           end

           // ---- 6: stated directly: an 80-tick high phase reached by a 40-tick rise must
           //      measure about 80, not 120.
           begin
               scl_above_vil = 1'b1; tick(20);
               scl_period(10, 100, 40, 80);
               fall_edge(10);
               tick(20);
               if (h_log[n_hlog - 1] > 16'd95) begin
                   $display("FAIL: the effective high time measured %0d with a 40-tick rise -- the transition was counted",
                            h_log[n_hlog - 1]); errors++; end
           end

           // ---- 7: an edge that never completes produces NO measurement. The line dips below
           //      VIH and comes back up without ever crossing VIL: that is noise on the high
           //      phase, not a falling edge, and timing it as one would report a fall that did
           //      not happen.
           begin
               nrise_before = n_fall;
               scl_above_vih = 1'b0; tick(20);
               scl_above_vih = 1'b1; tick(60);      // back up without crossing VIL
               if (n_fall !== nrise_before) begin
                   $display("FAIL: an incomplete edge produced %0d fall measurements",
                            n_fall - nrise_before); errors++; end
           end

           // ---- 8: a STATIC bus measures nothing. No threshold crossings, no edges.
           begin
               viol_before = n_viol; nrise_before = n_rise;
               scl_above_vil = 1'b1; scl_above_vih = 1'b1; tick(400);
               if (n_rise !== nrise_before || n_viol !== viol_before) begin
                   $display("FAIL: a static bus produced edges or violations"); errors++; end
           end

           if (errors == 0)
               $display("PASS: an edge rate needs two thresholds, tr and tf are separate maxima, the effective high time is VIH-based and excludes the transition, an incomplete edge is not an edge");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_rate_monitor.v — the same monitor in Verilog-2001
   // THE ELECTRICAL ENVELOPE: tr, tf and what they steal from the clock.
   //
   // Table 10:
   //     tr  rise time of both SDA and SCL   max 1000 / 300 / 120 ns
   //     tf  fall time of both SDA and SCL   max  300 / 300 / 120 ns
   //     Cb  capacitive load per bus line    max  400 / 400 / 550 pF
   //
   // THE FIRST THING TO SAY IS THAT A SINGLE DIGITAL SAMPLE CANNOT MEASURE AN EDGE RATE.
   // Every other block in this module works from `sda_in` and `scl_in` -- one bit each, the
   // output of an input buffer that has already decided high or low. An edge rate is the TIME
   // BETWEEN TWO VOLTAGE THRESHOLDS, and a one-bit sample has thrown that away before this
   // logic ever sees it. So this block takes TWO comparator outputs per line:
   //
   //     *_above_vil   the line is above the LOW threshold   (0.3 VDD)
   //     *_above_vih   the line is above the HIGH threshold  (0.7 VDD)
   //
   // and the rise time is the interval between those two crossings. That extra hardware is
   // the price of measuring an edge on-chip, and it is why compliance measurement is normally
   // done with a scope: a normal I2C input stage physically cannot report tr.
   //
   // WHY THE EDGES MATTER TO THE CLOCK. Table 10 contains an identity that is exact in all
   // three speed modes:
   //
   //     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   //
   //     Standard   4.7 + 4.0  + 1.000 + 0.300 = 10.0 us  = 1/100 kHz
   //     Fast       1.3 + 0.6  + 0.300 + 0.300 =  2.5 us  = 1/400 kHz
   //     Fm+        0.5 + 0.26 + 0.120 + 0.120 =  1.0 us  = 1/1000 kHz
   //
   // So the period is fully accounted for and the edges are part of the budget, not an
   // afterthought. Every nanosecond an edge takes beyond the assumed value has to come out of
   // a phase -- and since the phases have minima, a slow edge does not merely soften the
   // waveform, it eventually makes the clock illegal. This block therefore reports both the
   // measured edges AND the high time that survives them, which is the number a budget cares
   // about and the one Chapter 11.9 closes.
   //
   // Cb is not measurable here at all: capacitance is what CAUSES a slow edge, and this block
   // sees the consequence. Chapter 11.7 works the relationship the other way, from Cb and the
   // pull-up to the edge it produces.
   //
   // PASSIVE: observes comparator outputs and drives nothing.
   // (Verilog-2001)
   module i2c_edge_rate_monitor #(
       parameter TICK_W = 16,
       // Fast-mode maxima, in ticks of a 100 MHz sample clock: 300 ns = 30 ticks each.
       parameter T_R_MAX = 30,
       parameter T_F_MAX = 30
   )(
       input  wire  clk,
       input  wire  rst_n,

       // Two thresholds per line. A conventional input stage provides only one of these;
       // measuring an edge requires the second.
       input  wire  scl_above_vil,
       input  wire  scl_above_vih,

       // ---- measured, on SCL ----
       output reg                edge_valid,      // pulse: an edge has been measured
       output reg                edge_was_rise,
       output reg   [TICK_W-1:0] t_edge,

       // ---- verdicts ----
       output reg   viol_tr,
       output reg   viol_tf,

       // ---- the consequence for the clock ----
       // The high phase measured between the VIH crossings -- which is the time the line is
       // ACTUALLY above the high threshold, and therefore the tHIGH a receiver really gets.
       // A design that measured between the VIL crossings instead would report a longer high
       // phase than exists and would hide exactly the erosion this block is for.
       output reg                high_valid,
       output reg   [TICK_W-1:0] t_high_effective,

       // ---- totals and worst cases: these are MAXIMA, so the worst case is the LARGEST ----
       output reg   [TICK_W-1:0] n_rise,
       output reg   [TICK_W-1:0] n_fall,
       output reg   [TICK_W-1:0] n_viol,
       output reg   [TICK_W-1:0] max_tr_seen,
       output reg   [TICK_W-1:0] max_tf_seen
   );
       reg vil_q, vih_q;
       wire vil_rise, vil_fall, vih_rise, vih_fall;
       assign vil_rise = !vil_q &&  scl_above_vil;
       assign vil_fall =  vil_q && !scl_above_vil;
       assign vih_rise = !vih_q &&  scl_above_vih;
       assign vih_fall =  vih_q && !scl_above_vih;

       // A rising edge is timed from the VIL crossing to the VIH crossing; a falling edge from
       // the VIH crossing back down to the VIL crossing. Two timers, because a rise and a fall
       // can never be in flight at the same time but the arming conditions differ.
       reg              r_arm, f_arm;
       reg [TICK_W-1:0] e_ticks;
       wire [TICK_W-1:0] e_now;
       assign e_now = e_ticks + 1'b1;

       // The effective high time, measured VIH crossing to VIH crossing.
       reg              h_arm;
       reg [TICK_W-1:0] h_ticks;
       wire [TICK_W-1:0] h_now;
       assign h_now = h_ticks + 1'b1;

       always @(posedge clk) begin
           if (!rst_n) begin
               vil_q            <= 1'b1;    // an idle bus is high, so above both thresholds
               vih_q            <= 1'b1;
               r_arm            <= 1'b0;
               f_arm            <= 1'b0;
               e_ticks          <= {TICK_W{1'b0}};
               h_arm            <= 1'b0;
               h_ticks          <= {TICK_W{1'b0}};
               edge_valid       <= 1'b0;
               edge_was_rise    <= 1'b0;
               t_edge           <= {TICK_W{1'b0}};
               viol_tr          <= 1'b0;
               viol_tf          <= 1'b0;
               high_valid       <= 1'b0;
               t_high_effective <= {TICK_W{1'b0}};
               n_rise           <= {TICK_W{1'b0}};
               n_fall           <= {TICK_W{1'b0}};
               n_viol           <= {TICK_W{1'b0}};
               max_tr_seen      <= {TICK_W{1'b0}};
               max_tf_seen      <= {TICK_W{1'b0}};
           end else begin
               vil_q <= scl_above_vil;
               vih_q <= scl_above_vih;

               edge_valid <= 1'b0;
               high_valid <= 1'b0;

               // ---- arm a RISE measurement when the line crosses the low threshold upward ----
               if (vil_rise) begin
                   r_arm   <= 1'b1;
                   f_arm   <= 1'b0;
                   e_ticks <= {TICK_W{1'b0}};
               // ---- arm a FALL measurement when it crosses the high threshold downward ----
               end else if (vih_fall) begin
                   f_arm   <= 1'b1;
                   r_arm   <= 1'b0;
                   e_ticks <= {TICK_W{1'b0}};
               end else if (r_arm && vih_rise) begin
                   // The rise completed: VIL to VIH.
                   r_arm         <= 1'b0;
                   edge_valid    <= 1'b1;
                   edge_was_rise <= 1'b1;
                   t_edge        <= e_now;
                   viol_tr       <= (e_now > T_R_MAX);
                   viol_tf       <= 1'b0;
                   n_rise        <= n_rise + 1'b1;
                   if (e_now > T_R_MAX) n_viol <= n_viol + 1'b1;
                   if (e_now > max_tr_seen) max_tr_seen <= e_now;
               end else if (f_arm && vil_fall) begin
                   // The fall completed: VIH down to VIL.
                   f_arm         <= 1'b0;
                   edge_valid    <= 1'b1;
                   edge_was_rise <= 1'b0;
                   t_edge        <= e_now;
                   viol_tf       <= (e_now > T_F_MAX);
                   viol_tr       <= 1'b0;
                   n_fall        <= n_fall + 1'b1;
                   if (e_now > T_F_MAX) n_viol <= n_viol + 1'b1;
                   if (e_now > max_tf_seen) max_tf_seen <= e_now;
               end else if (r_arm || f_arm) begin
                   e_ticks <= e_now;
               end

               // ---- the effective high time, VIH to VIH ----
               if (vih_rise) begin
                   h_arm   <= 1'b1;
                   h_ticks <= {TICK_W{1'b0}};
               end else if (h_arm && vih_fall) begin
                   h_arm            <= 1'b0;
                   high_valid       <= 1'b1;
                   t_high_effective <= h_now;
               end else if (h_arm) begin
                   h_ticks <= h_now;
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_rate_monitor_tb.v — the Verilog testbench, structurally identical
   `timescale 1ns/1ps
   // 100 MHz sample clock. Fast-mode tr(max) = tf(max) = 300 ns = 30 ticks.
   //
   // The stimulus drives the TWO THRESHOLD comparators directly, because that is the only way
   // an edge rate can be presented to logic. A slow edge is modelled as the two crossings
   // being far apart in time; a fast edge as them being close together. That is not an
   // approximation -- it is exactly what the comparators see.
   module i2c_edge_rate_monitor_tb;   // Verilog-2001
       localparam TICK_W  = 16;
       localparam T_R_MAX = 30;
       // Deliberately DIFFERENT from T_R_MAX. Standard-mode's tr(max) and tf(max) differ by
       // more than 3x (1000 ns against 300 ns), so unequal limits are the realistic case -- and
       // with them equal, a design judging both against one limit is unobservable.
       localparam T_F_MAX = 20;

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

       reg rst_n = 1'b0;
       reg scl_above_vil = 1'b1, scl_above_vih = 1'b1;   // idle bus: above both thresholds

       wire edge_valid, edge_was_rise, viol_tr, viol_tf, high_valid;
       wire [TICK_W-1:0] t_edge, t_high_effective, n_rise, n_fall, n_viol;
       wire [TICK_W-1:0] max_tr_seen, max_tf_seen;

       integer errors = 0;
       integer base = 0;
       reg [TICK_W-1:0] viol_before, nrise_before;
       reg [TICK_W-1:0] h_fast, h_slow;
       integer hf = 0, hs = 0;

       i2c_edge_rate_monitor #(.TICK_W(TICK_W), .T_R_MAX(T_R_MAX), .T_F_MAX(T_F_MAX))
         dut (
           .clk(clk), .rst_n(rst_n), .scl_above_vil(scl_above_vil),
           .scl_above_vih(scl_above_vih), .edge_valid(edge_valid),
           .edge_was_rise(edge_was_rise), .t_edge(t_edge), .viol_tr(viol_tr), .viol_tf(viol_tf),
           .high_valid(high_valid), .t_high_effective(t_high_effective), .n_rise(n_rise),
           .n_fall(n_fall), .n_viol(n_viol), .max_tr_seen(max_tr_seen),
           .max_tf_seen(max_tf_seen));

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

       reg [TICK_W-1:0] e_log [0:31];
       reg r_log [0:31];
       reg vr_log [0:31];
       reg vf_log [0:31];
       integer n_log = 0;
       always @(posedge clk) if (rst_n && edge_valid && n_log < 32) begin
           e_log[n_log] = t_edge;  r_log[n_log]  = edge_was_rise;
           vr_log[n_log] = viol_tr; vf_log[n_log] = viol_tf;
           n_log = n_log + 1;
       end

       reg [TICK_W-1:0] h_log [0:31];
       integer n_hlog = 0;
       always @(posedge clk) if (rst_n && high_valid && n_hlog < 32) begin
           h_log[n_hlog] = t_high_effective; n_hlog = n_hlog + 1;
       end

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

       // A FALLING edge taking `tf` ticks: the line drops below VIH, then `tf` ticks later it
       // drops below VIL. Between the two it is in the undefined region.
       task fall_edge;
           input integer tf;
       begin
               scl_above_vih = 1'b0; tick(tf);
               scl_above_vil = 1'b0;
               end
       endtask

       // A RISING edge taking `tr` ticks: above VIL first, then above VIH `tr` ticks later.
       task rise_edge;
           input integer tr;
       begin
               scl_above_vil = 1'b1; tick(tr);
               scl_above_vih = 1'b1;
               end
       endtask

       // One SCL period with settable edge rates and phase widths. `hi` is the time spent
       // fully above VIH; the effective high time the block reports is that plus the sample
       // in which the crossing is observed.
       task scl_period;
           input integer tf;
           input integer lo;
           input integer tr;
           input integer hi;
       begin
               fall_edge(tf);
               tick(lo);
               rise_edge(tr);
               tick(hi);
               end
       endtask

       initial begin
           tick(3);
           // These are MAXIMA, so the worst-case trackers start at zero.
           if (max_tr_seen !== {TICK_W{1'b0}} || max_tf_seen !== {TICK_W{1'b0}}) begin
               $display("FAIL: the maximum trackers did not start at zero"); errors = errors + 1; end
           rst_n = 1'b1; tick(2);
           scl_above_vil = 1'b1; scl_above_vih = 1'b1; tick(20);

           // ---- 1: FAST edges, well inside the limits. 10 ticks where 30 is the maximum.
           //      Nothing may be flagged -- for a maximum, smaller is safer.
           repeat (4) scl_period(10, 100, 10, 80);
           tick(20);
           if (n_viol !== {TICK_W{1'b0}}) begin
               $display("FAIL: %0d fast edges flagged", n_viol); errors = errors + 1; end
           if (n_rise < 16'd4 || n_fall < 16'd4) begin
               $display("FAIL: %0d rises and %0d falls measured from four periods",
                        n_rise, n_fall); errors = errors + 1; end
           // The first edge measured is a FALL, because the stimulus starts from an idle high
           // bus. Getting the polarity right matters: a block that labelled it a rise would
           // then judge every edge against the wrong parameter.
           if (r_log[0] !== 1'b0) begin
               $display("FAIL: the first edge from an idle-high bus was labelled a RISE");
               errors = errors + 1; end
           if (e_log[0] < 16'd8 || e_log[0] > 16'd12) begin
               $display("FAIL: a 10-tick fall measured %0d", e_log[0]); errors = errors + 1; end

           // ---- 2: a SLOW RISE. 50 ticks where tr(max) is 30. Only the rise verdict fires.
           begin
               base = n_log;
               scl_period(10, 100, 50, 80);
               tick(20);
               // index base is the fall, base+1 the rise
               if (r_log[base + 1] !== 1'b1) begin
                   $display("FAIL: the second edge of a period should be the RISE"); errors = errors + 1; end
               if (vr_log[base + 1] !== 1'b1) begin
                   $display("FAIL: a 50-tick rise was not flagged (tr max %0d)", T_R_MAX);
                   errors = errors + 1; end
               if (vf_log[base + 1] !== 1'b0) begin
                   $display("FAIL: a slow RISE also flagged the FALL parameter"); errors = errors + 1; end
               if (max_tr_seen < 16'd45) begin
                   $display("FAIL: max_tr_seen = %0d after a 50-tick rise", max_tr_seen);
                   errors = errors + 1; end
           end

           // ---- 3: a SLOW FALL. The mirror -- tr and tf are separate parameters with
           //      separate limits, and in Standard-mode they differ by more than 3x, so a
           //      block that judged both against one limit would be wrong in Standard-mode
           //      even if it looked right in Fast-mode where the two happen to be equal.
           begin
               base = n_log;
               scl_period(60, 100, 10, 80);
               tick(20);
               if (vf_log[base] !== 1'b1) begin
                   $display("FAIL: a 60-tick fall was not flagged (tf max %0d)", T_F_MAX);
                   errors = errors + 1; end
               if (vr_log[base] !== 1'b0) begin
                   $display("FAIL: a slow FALL also flagged the RISE parameter"); errors = errors + 1; end
               if (max_tf_seen < 16'd55) begin
                   $display("FAIL: max_tf_seen = %0d after a 60-tick fall", max_tf_seen);
                   errors = errors + 1; end
           end

           // ---- 3b: THE SEPARATION TEST. A 25-tick edge is LEGAL as a rise (max 30) and
           //      ILLEGAL as a fall (max 20). One duration, two verdicts, decided only by which
           //      parameter the edge is judged against.
           begin
               base = n_log;
               scl_period(25, 100, 25, 80);
               tick(20);
               if (vf_log[base] !== 1'b1) begin
                   $display("FAIL: a 25-tick FALL was not flagged against its own 20-tick limit -- both edges are being judged by one limit");
                   errors = errors + 1; end
               if (vr_log[base + 1] !== 1'b0) begin
                   $display("FAIL: a 25-tick RISE was flagged against a 30-tick limit"); errors = errors + 1; end
           end

           // ---- 4: BOUNDARIES. Exactly AT the maximum is legal; one tick above is not.
           begin
               base = n_log;
               scl_period(T_F_MAX, 100, T_R_MAX, 80);
               tick(20);
               if (vf_log[base] !== 1'b0) begin
                   $display("FAIL: a fall of EXACTLY tf(max) was rejected"); errors = errors + 1; end
               if (vr_log[base + 1] !== 1'b0) begin
                   $display("FAIL: a rise of EXACTLY tr(max) was rejected"); errors = errors + 1; end
           end
           begin
               base = n_log;
               scl_period(T_F_MAX + 1, 100, T_R_MAX + 1, 80);
               tick(20);
               if (vf_log[base] !== 1'b1) begin
                   $display("FAIL: a fall one tick above the maximum was accepted"); errors = errors + 1; end
               if (vr_log[base + 1] !== 1'b1) begin
                   $display("FAIL: a rise one tick above the maximum was accepted"); errors = errors + 1; end
           end

           // ---- 5: THE POINT OF THE BLOCK. The effective high time is measured between the
           //      VIH crossings, so it EXCLUDES the transition. Two periods asking for the
           //      same 80 ticks fully above VIH, one reached by a fast rise and one by a slow
           //      one, must report the SAME effective high time -- a VIL-based measurement
           //      would have included the rise and reported a much longer phase for the slow
           //      edge, hiding exactly the erosion this block exists to expose.
           //
           //      Each high phase is closed with an explicit falling edge before it is read.
           //      A high-phase measurement is reported by the fall that ENDS it, so reading
           //      the log straight after the period that opened it returns the PREVIOUS
           //      period's value -- which it did, before these tests were rewritten.
           begin
               scl_period(10, 100, 10, 80);
               fall_edge(10);                 // close the high phase so it is reported
               tick(20);
               h_fast = h_log[n_hlog - 1];

               scl_above_vil = 1'b1; tick(20);
               scl_period(10, 100, 60, 80);
               fall_edge(10);
               tick(20);
               h_slow = h_log[n_hlog - 1];

               if (h_slow > h_fast + 16'd10) begin
                   $display("FAIL: a 60-tick rise reported effective high %0d against %0d for a 10-tick rise -- the measurement is VIL-based, not VIH-based",
                            h_slow, h_fast); errors = errors + 1; end
               if (h_fast < 16'd75 || h_fast > 16'd90) begin
                   $display("FAIL: an 80-tick fully-high interval measured %0d", h_fast);
                   errors = errors + 1; end
           end

           // ---- 6: stated directly: an 80-tick high phase reached by a 40-tick rise must
           //      measure about 80, not 120.
           begin
               scl_above_vil = 1'b1; tick(20);
               scl_period(10, 100, 40, 80);
               fall_edge(10);
               tick(20);
               if (h_log[n_hlog - 1] > 16'd95) begin
                   $display("FAIL: the effective high time measured %0d with a 40-tick rise -- the transition was counted",
                            h_log[n_hlog - 1]); errors = errors + 1; end
           end

           // ---- 7: an edge that never completes produces NO measurement. The line dips below
           //      VIH and comes back up without ever crossing VIL: that is noise on the high
           //      phase, not a falling edge, and timing it as one would report a fall that did
           //      not happen.
           begin
               nrise_before = n_fall;
               scl_above_vih = 1'b0; tick(20);
               scl_above_vih = 1'b1; tick(60);      // back up without crossing VIL
               if (n_fall !== nrise_before) begin
                   $display("FAIL: an incomplete edge produced %0d fall measurements",
                            n_fall - nrise_before); errors = errors + 1; end
           end

           // ---- 8: a STATIC bus measures nothing. No threshold crossings, no edges.
           begin
               viol_before = n_viol; nrise_before = n_rise;
               scl_above_vil = 1'b1; scl_above_vih = 1'b1; tick(400);
               if (n_rise !== nrise_before || n_viol !== viol_before) begin
                   $display("FAIL: a static bus produced edges or violations"); errors = errors + 1; end
           end

           if (errors == 0)
               $display("PASS: an edge rate needs two thresholds, tr and tf are separate maxima, the effective high time is VIH-based and excludes the transition, an incomplete edge is not an edge");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_rate_monitor.vhd — the same monitor in VHDL
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- THE ELECTRICAL ENVELOPE: tr, tf and what they steal from the clock.
   --
   -- Table 10:
   --     tr  rise time of both SDA and SCL   max 1000 / 300 / 120 ns
   --     tf  fall time of both SDA and SCL   max  300 / 300 / 120 ns
   --     Cb  capacitive load per bus line    max  400 / 400 / 550 pF
   --
   -- A SINGLE DIGITAL SAMPLE CANNOT MEASURE AN EDGE RATE. Every other block in this module works
   -- from sda_in and scl_in -- one bit each, the output of an input buffer that has already decided
   -- high or low. An edge rate is the TIME BETWEEN TWO VOLTAGE THRESHOLDS, and a one-bit sample has
   -- thrown that away before this logic ever sees it. So this block takes TWO comparator outputs:
   --
   --     *_above_vil   the line is above the LOW threshold   (0.3 VDD)
   --     *_above_vih   the line is above the HIGH threshold  (0.7 VDD)
   --
   -- and the rise time is the interval between those two crossings. That extra hardware is the
   -- price of measuring an edge on-chip, and it is why compliance measurement is normally done with
   -- a scope: a normal I2C input stage physically cannot report tr.
   --
   -- WHY THE EDGES MATTER TO THE CLOCK. Table 10 contains an identity exact in all three modes:
   --
   --     tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
   --
   --     Standard   4.7 + 4.0  + 1.000 + 0.300 = 10.0 us = 1/100 kHz
   --     Fast       1.3 + 0.6  + 0.300 + 0.300 =  2.5 us = 1/400 kHz
   --     Fm+        0.5 + 0.26 + 0.120 + 0.120 =  1.0 us = 1/1000 kHz
   --
   -- So the period is fully accounted for and the edges are part of the budget, not an afterthought.
   -- Every nanosecond an edge takes beyond the assumed value comes out of a phase -- and since the
   -- phases have minima, a slow edge does not merely soften the waveform, it eventually makes the
   -- clock illegal. This block therefore reports both the measured edges AND the high time that
   -- survives them, which is the number Chapter 11.9 closes a budget with.
   --
   -- Cb is not measurable here at all: capacitance CAUSES a slow edge, and this block sees the
   -- consequence. Chapter 11.7 works the relationship the other way, from Cb and the pull-up.
   entity i2c_edge_rate_monitor is
       generic (
           TICK_W : positive := 16;
           -- Fast-mode maxima, in ticks of a 100 MHz sample clock: 300 ns = 30 ticks each.
           T_R_MAX : natural := 30;
           T_F_MAX : natural := 30
       );
       port (
           clk   : in std_logic;
           rst_n : in std_logic;

           -- Two thresholds. A conventional input stage provides only one of these.
           scl_above_vil : in std_logic;
           scl_above_vih : in std_logic;

           edge_valid    : out std_logic;
           edge_was_rise : out std_logic;
           t_edge        : out unsigned(TICK_W - 1 downto 0);

           viol_tr : out std_logic;
           viol_tf : out std_logic;

           -- The high phase measured between the VIH crossings -- the time the line is ACTUALLY
           -- above the high threshold, and therefore the tHIGH a receiver really gets. Measuring
           -- between the VIL crossings instead would report a longer high phase than exists and
           -- would hide exactly the erosion this block is for.
           high_valid       : out std_logic;
           t_high_effective : out unsigned(TICK_W - 1 downto 0);

           -- These are MAXIMA, so the worst case is the LARGEST value seen.
           n_rise : out unsigned(TICK_W - 1 downto 0);
           n_fall : out unsigned(TICK_W - 1 downto 0);
           n_viol : out unsigned(TICK_W - 1 downto 0);
           max_tr_seen : out unsigned(TICK_W - 1 downto 0);
           max_tf_seen : out unsigned(TICK_W - 1 downto 0)
       );
   end entity;

   architecture rtl of i2c_edge_rate_monitor is
       signal vil_q, vih_q : std_logic := '1';   -- an idle bus is high, so above both thresholds
       signal vil_rise, vil_fall, vih_rise, vih_fall : std_logic;

       -- A rising edge is timed from the VIL crossing to the VIH crossing; a falling edge from the
       -- VIH crossing back down to the VIL crossing. Two arming conditions, one timer, because a
       -- rise and a fall can never be in flight at the same time.
       signal r_arm, f_arm : std_logic := '0';
       signal e_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal e_now   : unsigned(TICK_W - 1 downto 0);

       -- The effective high time, measured VIH crossing to VIH crossing.
       signal h_arm   : std_logic := '0';
       signal h_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
       signal h_now   : unsigned(TICK_W - 1 downto 0);
   begin
       vil_rise <= (not vil_q) and scl_above_vil;
       vil_fall <= vil_q and (not scl_above_vil);
       vih_rise <= (not vih_q) and scl_above_vih;
       vih_fall <= vih_q and (not scl_above_vih);

       e_now <= e_ticks + 1;
       h_now <= h_ticks + 1;

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   vil_q            <= '1';
                   vih_q            <= '1';
                   r_arm            <= '0';
                   f_arm            <= '0';
                   e_ticks          <= (others => '0');
                   h_arm            <= '0';
                   h_ticks          <= (others => '0');
                   edge_valid       <= '0';
                   edge_was_rise    <= '0';
                   t_edge           <= (others => '0');
                   viol_tr          <= '0';
                   viol_tf          <= '0';
                   high_valid       <= '0';
                   t_high_effective <= (others => '0');
                   n_rise           <= (others => '0');
                   n_fall           <= (others => '0');
                   n_viol           <= (others => '0');
                   max_tr_seen      <= (others => '0');
                   max_tf_seen      <= (others => '0');
               else
                   vil_q <= scl_above_vil;
                   vih_q <= scl_above_vih;

                   edge_valid <= '0';
                   high_valid <= '0';

                   -- arm a RISE measurement when the line crosses the low threshold upward
                   if vil_rise = '1' then
                       r_arm   <= '1';
                       f_arm   <= '0';
                       e_ticks <= (others => '0');
                   -- arm a FALL measurement when it crosses the high threshold downward
                   elsif vih_fall = '1' then
                       f_arm   <= '1';
                       r_arm   <= '0';
                       e_ticks <= (others => '0');
                   elsif r_arm = '1' and vih_rise = '1' then
                       -- The rise completed: VIL to VIH.
                       r_arm         <= '0';
                       edge_valid    <= '1';
                       edge_was_rise <= '1';
                       t_edge        <= e_now;
                       viol_tf       <= '0';
                       if e_now > to_unsigned(T_R_MAX, TICK_W) then
                           viol_tr <= '1';
                           n_viol  <= n_viol + 1;
                       else
                           viol_tr <= '0';
                       end if;
                       n_rise <= n_rise + 1;
                       if e_now > max_tr_seen then max_tr_seen <= e_now; end if;
                   elsif f_arm = '1' and vil_fall = '1' then
                       -- The fall completed: VIH down to VIL.
                       f_arm         <= '0';
                       edge_valid    <= '1';
                       edge_was_rise <= '0';
                       t_edge        <= e_now;
                       viol_tr       <= '0';
                       if e_now > to_unsigned(T_F_MAX, TICK_W) then
                           viol_tf <= '1';
                           n_viol  <= n_viol + 1;
                       else
                           viol_tf <= '0';
                       end if;
                       n_fall <= n_fall + 1;
                       if e_now > max_tf_seen then max_tf_seen <= e_now; end if;
                   elsif r_arm = '1' or f_arm = '1' then
                       e_ticks <= e_now;
                   end if;

                   -- the effective high time, VIH to VIH
                   if vih_rise = '1' then
                       h_arm   <= '1';
                       h_ticks <= (others => '0');
                   elsif h_arm = '1' and vih_fall = '1' then
                       h_arm            <= '0';
                       high_valid       <= '1';
                       t_high_effective <= h_now;
                   elsif h_arm = '1' then
                       h_ticks <= h_now;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_rate_monitor_tb.vhd — the VHDL testbench, single-writer throughout
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   -- 100 MHz sample clock. Fast-mode tr(max) = tf(max) = 300 ns = 30 ticks.
   --
   -- The stimulus drives the TWO THRESHOLD comparators directly, because that is the only way an
   -- edge rate can be presented to logic. A slow edge is the two crossings far apart in time; a
   -- fast edge is them close together. Not an approximation -- exactly what the comparators see.
   entity i2c_edge_rate_monitor_tb is
   end entity;

   architecture sim of i2c_edge_rate_monitor_tb is
       constant TICK_W  : positive := 16;
       constant T_R_MAX : natural  := 30;
       -- Deliberately DIFFERENT from T_R_MAX. Standard-mode's tr(max) and tf(max) differ by more
       -- than 3x (1000 ns against 300 ns), so unequal limits are the realistic case -- and with them
       -- equal, a design judging both against one limit is unobservable.
       constant T_F_MAX : natural  := 20;

       signal clk   : std_logic := '0';
       signal rst_n : std_logic := '0';
       signal scl_above_vil, scl_above_vih : std_logic := '1';   -- idle: above both thresholds

       signal edge_valid, edge_was_rise, viol_tr, viol_tf, high_valid : std_logic;
       signal t_edge, t_high_effective : unsigned(TICK_W - 1 downto 0);
       signal n_rise, n_fall, n_viol : unsigned(TICK_W - 1 downto 0);
       signal max_tr_seen, max_tf_seen : unsigned(TICK_W - 1 downto 0);

       type tick_arr is array (0 to 31) of unsigned(TICK_W - 1 downto 0);
       type bit_arr  is array (0 to 31) of std_logic;
       signal e_log, h_log : tick_arr := (others => (others => '0'));
       signal r_log, vr_log, vf_log : bit_arr := (others => '0');
       signal n_log, n_hlog : natural := 0;

       signal test_done : std_logic := '0';
   begin
       dut : entity work.i2c_edge_rate_monitor
           generic map (TICK_W => TICK_W, T_R_MAX => T_R_MAX, T_F_MAX => T_F_MAX)
           port map (clk => clk, rst_n => rst_n, scl_above_vil => scl_above_vil,
                     scl_above_vih => scl_above_vih, edge_valid => edge_valid,
                     edge_was_rise => edge_was_rise, t_edge => t_edge, viol_tr => viol_tr,
                     viol_tf => viol_tf, high_valid => high_valid,
                     t_high_effective => t_high_effective, n_rise => n_rise, n_fall => n_fall,
                     n_viol => n_viol, max_tr_seen => max_tr_seen, max_tf_seen => max_tf_seen);

       clk <= not clk after 5 ns;

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

       obs_e : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and edge_valid = '1' and n_log < 32 then
               e_log(n_log) <= t_edge;  r_log(n_log)  <= edge_was_rise;
               vr_log(n_log) <= viol_tr; vf_log(n_log) <= viol_tf;
               n_log <= n_log + 1;
           end if;
       end process;

       obs_h : process (clk)
       begin
           if rising_edge(clk) and rst_n = '1' and high_valid = '1' and n_hlog < 32 then
               h_log(n_hlog) <= t_high_effective; n_hlog <= n_hlog + 1;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable base : natural;
           variable h_fast, h_slow : unsigned(TICK_W - 1 downto 0);
           variable viol_before, nfall_before, nrise_before : unsigned(TICK_W - 1 downto 0);

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

           -- A FALLING edge taking `tf` ticks: below VIH, then `tf` ticks later below VIL.
           procedure fall_edge (tf : in positive) is
           begin
               scl_above_vih <= '0'; tick(tf);
               scl_above_vil <= '0';
           end procedure;

           -- A RISING edge taking `tr` ticks: above VIL first, then above VIH `tr` ticks later.
           procedure rise_edge (tr : in positive) is
           begin
               scl_above_vil <= '1'; tick(tr);
               scl_above_vih <= '1';
           end procedure;

           procedure scl_period (tf, lo, tr, hi : in positive) is
           begin
               fall_edge(tf);
               tick(lo);
               rise_edge(tr);
               tick(hi);
           end procedure;
       begin
           tick(3);
           -- These are MAXIMA, so the worst-case trackers start at zero.
           if max_tr_seen /= to_unsigned(0, TICK_W) or max_tf_seen /= to_unsigned(0, TICK_W) then
               report "the maximum trackers did not start at zero" severity error;
               errs := errs + 1; end if;
           rst_n <= '1'; tick(2);
           scl_above_vil <= '1'; scl_above_vih <= '1'; tick(20);

           -- 1: FAST edges, well inside the limits. 10 ticks where 30 is the maximum. Nothing may
           -- be flagged -- for a maximum, smaller is safer.
           for i in 1 to 4 loop scl_period(10, 100, 10, 80); end loop;
           tick(20);
           if n_viol /= to_unsigned(0, TICK_W) then
               report "fast edges were flagged" severity error; errs := errs + 1; end if;
           if n_rise < to_unsigned(4, TICK_W) or n_fall < to_unsigned(4, TICK_W) then
               report "too few edges measured from four periods" severity error;
               errs := errs + 1; end if;
           -- The first edge is a FALL, because the stimulus starts from an idle high bus. Getting
           -- the polarity right matters: a block labelling it a rise would judge every edge against
           -- the wrong parameter.
           if r_log(0) /= '0' then
               report "the first edge from an idle-high bus was labelled a RISE" severity error;
               errs := errs + 1; end if;
           if e_log(0) < to_unsigned(8, TICK_W) or e_log(0) > to_unsigned(12, TICK_W) then
               report "a 10-tick fall measured out of range" severity error; errs := errs + 1; end if;

           -- 2: a SLOW RISE. 50 ticks where tr(max) is 30. Only the rise verdict fires.
           base := n_log;
           scl_period(10, 100, 50, 80);
           tick(20);
           if r_log(base + 1) /= '1' then
               report "the second edge of a period should be the RISE" severity error;
               errs := errs + 1; end if;
           if vr_log(base + 1) /= '1' then
               report "a 50-tick rise was not flagged" severity error; errs := errs + 1; end if;
           if vf_log(base + 1) /= '0' then
               report "a slow RISE also flagged the FALL parameter" severity error;
               errs := errs + 1; end if;
           if max_tr_seen < to_unsigned(45, TICK_W) then
               report "the rise worst case did not move" severity error; errs := errs + 1; end if;

           -- 3: a SLOW FALL. The mirror -- tr and tf are separate parameters with separate limits,
           -- and in Standard-mode they differ by more than 3x, so a block judging both against one
           -- limit would be wrong in Standard-mode even if it looked right in Fast-mode where the
           -- two happen to be equal.
           base := n_log;
           scl_period(60, 100, 10, 80);
           tick(20);
           if vf_log(base) /= '1' then
               report "a 60-tick fall was not flagged" severity error; errs := errs + 1; end if;
           if vr_log(base) /= '0' then
               report "a slow FALL also flagged the RISE parameter" severity error;
               errs := errs + 1; end if;
           if max_tf_seen < to_unsigned(55, TICK_W) then
               report "the fall worst case did not move" severity error; errs := errs + 1; end if;

           -- 3b: THE SEPARATION TEST. A 25-tick edge is LEGAL as a rise (max 30) and ILLEGAL as a
           -- fall (max 20). One duration, two verdicts, decided only by which parameter it is
           -- judged against.
           base := n_log;
           scl_period(25, 100, 25, 80);
           tick(20);
           if vf_log(base) /= '1' then
               report "a 25-tick FALL was not flagged against its own 20-tick limit -- both edges "
                    & "are being judged by one limit" severity error; errs := errs + 1; end if;
           if vr_log(base + 1) /= '0' then
               report "a 25-tick RISE was flagged against a 30-tick limit" severity error;
               errs := errs + 1; end if;

           -- 4: BOUNDARIES. Exactly AT the maximum is legal; one tick above is not.
           base := n_log;
           scl_period(T_F_MAX, 100, T_R_MAX, 80);
           tick(20);
           if vf_log(base) /= '0' then
               report "a fall of EXACTLY tf(max) was rejected" severity error; errs := errs + 1; end if;
           if vr_log(base + 1) /= '0' then
               report "a rise of EXACTLY tr(max) was rejected" severity error; errs := errs + 1; end if;

           base := n_log;
           scl_period(T_F_MAX + 1, 100, T_R_MAX + 1, 80);
           tick(20);
           if vf_log(base) /= '1' then
               report "a fall one tick above the maximum was accepted" severity error;
               errs := errs + 1; end if;
           if vr_log(base + 1) /= '1' then
               report "a rise one tick above the maximum was accepted" severity error;
               errs := errs + 1; end if;

           -- 5: THE POINT OF THE BLOCK. The effective high time is measured between the VIH
           -- crossings, so it EXCLUDES the transition. Two periods asking for the same 80 ticks
           -- fully above VIH, one reached by a fast rise and one by a slow one, must report the
           -- SAME effective high time -- a VIL-based measurement would have included the rise and
           -- reported a much longer phase for the slow edge, hiding the erosion this block exposes.
           --
           -- Each high phase is closed with an explicit falling edge before it is read: a
           -- high-phase measurement is reported by the fall that ENDS it, so reading the log
           -- straight after the period that opened it returns the PREVIOUS period's value.
           scl_period(10, 100, 10, 80);
           fall_edge(10);
           tick(20);
           h_fast := h_log(n_hlog - 1);

           scl_above_vil <= '1'; tick(20);
           scl_period(10, 100, 60, 80);
           fall_edge(10);
           tick(20);
           h_slow := h_log(n_hlog - 1);

           if h_slow > h_fast + 10 then
               report "the slow-edge period reported a longer effective high time -- the measurement "
                    & "is VIL-based, not VIH-based" severity error; errs := errs + 1; end if;
           if h_fast < to_unsigned(75, TICK_W) or h_fast > to_unsigned(90, TICK_W) then
               report "an 80-tick fully-high interval measured out of range" severity error;
               errs := errs + 1; end if;

           -- 6: stated directly: an 80-tick high phase reached by a 40-tick rise must measure about
           -- 80, not 120.
           scl_above_vil <= '1'; tick(20);
           scl_period(10, 100, 40, 80);
           fall_edge(10);
           tick(20);
           if h_log(n_hlog - 1) > to_unsigned(95, TICK_W) then
               report "the effective high time counted the transition" severity error;
               errs := errs + 1; end if;

           -- 7: an edge that never completes produces NO measurement. The line dips below VIH and
           -- comes back up without crossing VIL: that is noise on the high phase, not a falling
           -- edge, and timing it as one would report a fall that did not happen.
           nfall_before := n_fall;
           scl_above_vih <= '0'; tick(20);
           scl_above_vih <= '1'; tick(60);      -- back up without crossing VIL
           if n_fall /= nfall_before then
               report "an incomplete edge produced a fall measurement" severity error;
               errs := errs + 1; end if;

           -- 8: a STATIC bus measures nothing. No threshold crossings, no edges.
           viol_before := n_viol; nrise_before := n_rise;
           scl_above_vil <= '1'; scl_above_vih <= '1'; tick(400);
           if n_rise /= nrise_before or n_viol /= viol_before then
               report "a static bus produced edges or violations" severity error;
               errs := errs + 1; end if;

           if errs = 0 then
               report "i2c_edge_rate_monitor self-check complete: an edge rate needs two thresholds, "
                    & "tr and tf are separate maxima, the effective high time is VIH-based and "
                    & "excludes the transition, an incomplete edge is not an edge" severity note;
           else
               report "i2c_edge_rate_monitor self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

6a. Five Decisions Worth Defending

The input is two comparators, not a logic level. §3's argument. A monitor taking a single digital input cannot measure an edge at all — the edge is exactly the interval during which a single-threshold view is undefined. Making the interface two signals forces the measurement to be honest about what it needs.

The monitor checks the two maxima and deliberately does not check tr(min). §5's 20 ns minimum needs a sample clock several times faster than this block's to resolve at all — at 100 MHz it is two ticks — so a verdict against it would be a number dressed up as a judgement. §10 gives the clock rate a real minimum check needs, and §9 shows the assertion written so that it is disabled rather than vacuous when no minimum applies. A check you cannot resolve is worse than no check, because a passing result implies a measurement that was never possible.

A stalled edge is reported on expiry. §3's second consequence, and the same structure as Chapter 11.4 §5. An edge that starts and never completes is the most severe failure available and produces no completion event to compare at.

The rise and fall limits are separate parameters, configured differently in the testbench. T_R_MAX = 30, T_F_MAX = 20 — deliberately unequal, because Table 10 gives tr(max) and tf(max) the same 300 ns in Fast-mode. That is Chapter 11.4 §8's degeneracy trap again, and §8 records that mutation G2 survived until the values were separated. The direction of each edge is therefore a reported output, not an inference — mutation G4 swaps the labels and would otherwise apply each limit to the wrong edge.

The block monitors ONE line, and a real system instantiates it twice. Cb differs between SDA and SCL in practice — SCL fans out to clock inputs, SDA to inputs and outputs — so the two lines have genuinely different edge rates on the same board with the same pull-ups. Keeping the block single-line means the two reports cannot be conflated; a shared instance would report the worse of the two without saying which, which is the one thing a board-level report must not do.

6b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ iverilog -g2012 -o d7 i2c_edge_rate_monitor.sv i2c_edge_rate_monitor_tb.sv && ./d7
   PASS: an edge rate needs two thresholds, tr and tf are separate maxima, the effective high
   time is VIH-based and excludes the transition, an incomplete edge is not an edge
   i2c_edge_rate_monitor_tb.sv:250: $finish called at 34170000 (1ps)

   $ iverilog -g2005 -o v7 i2c_edge_rate_monitor.v i2c_edge_rate_monitor_tb.v && ./v7
   PASS: an edge rate needs two thresholds, tr and tf are separate maxima, the effective high
   time is VIH-based and excludes the transition, an incomplete edge is not an edge
   i2c_edge_rate_monitor_tb.v:263: $finish called at 34170000 (1ps)

   $ nvc -a i2c_edge_rate_monitor.vhd i2c_edge_rate_monitor_tb.vhd
   $ nvc -e i2c_edge_rate_monitor_tb && nvc -r i2c_edge_rate_monitor_tb --stop-time=700us
   ** Note: 34170ns+0: i2c_edge_rate_monitor self-check complete: an edge rate needs two
      thresholds, tr and tf are separate maxima, the effective high time is VIH-based and excludes
      the transition, an incomplete edge is not an edge

All three at 34170 ns.

7. What the Testbench Proves

The monitor is configured with T_R_MAX = 30 (Fast-mode's 300 ns at 100 MHz) and — deliberately — T_F_MAX = 20, so the two maxima differ even though Table 10 gives them the same 300 ns in Fast-mode. §8's mutation G2 is why.

#stimuluswhat it establishes
1resetthe maximum-trackers read zero
2four clean periodsfour rises and four falls measured; nothing flagged
3the first edge from an idle-high buslabelled a FALL, not a rise
4a 10-tick fallmeasured as 10; the second edge of the period is the rise
5a 50-tick riseviolates tr (30), does not flag tf; max_tr_seen records it
6a 60-tick fallviolates tf (20), does not flag tr; max_tf_seen records it
7a 25-tick edgelegal as a rise (30), illegal as a fall (20)
8exactly at each maximumaccepted
9one tick above each maximumrejected — both boundaries pinned
10a 60-tick rise vs a 10-tick risethe effective high time shrinks — the measurement is VIH-based
11an 80-tick fully-high intervalmeasured as 80
12a 40-tick risethe transition is excluded from the effective high time
13an incomplete edgeproduces no measurement
14a static busproduces no edges and no violations

Test 7 is the separability test, and it exists because Table 10 gives tr(max) and tf(max) the same value in Fast-mode. A 25-tick edge is inside the 30-tick rise limit and outside the 20-tick fall limit, so it is the single stimulus distinguishing a monitor that applies each limit to its own edge from one that applies a single limit to both. With the specification's own Fast-mode numbers that stimulus cannot exist — the third occurrence of this trap in the module, after Chapter 11.4 §8 and Chapter 11.5 §6.

Tests 10 to 12 are about an output this chapter has not yet mentioned, and it is the one that closes the loop with Chapter 11.2.

The monitor reports t_high_effective: the high phase measured between the VIH crossings — the time the line is actually above the high threshold, and therefore the tHIGH a receiver really gets. A design that measured between the VIL crossings instead would report a longer high phase than exists, and would hide exactly the erosion this block is for.

That makes the connection to §4's identity concrete rather than rhetorical. A slow rise does not merely soften an edge; it takes time out of the high phase, and the effective high time is that theft as a number. Test 10 shows it directly: the same nominal period with a 60-tick rise instead of a 10-tick one yields a measurably shorter high phase. Test 12 pins the definition — the transition itself must not be counted.

Test 3 is smaller than it looks. An idle I²C bus sits high, so the first edge a monitor ever sees is a fall. A monitor that assumed edges alternate starting with a rise would mislabel every measurement on every capture, and then apply the wrong limit to each. Mutation G4 is that swap.

Test 13 is the incomplete edge. The lower threshold is crossed and the upper one never is — a line that started to rise and stalled, which is a pull-up too weak for the capacitance. There is no completed edge, so there is no duration to report, and mutation G3 is the version that reports one anyway.

8. Mutation Testing

Five defects injected into the SystemVerilog monitor.

#injected defectoutcome
G1the effective high time is measured VIL-to-VIL, including the transitionkilled — test 10
G2tr and tf are judged against one limitkilled — test 7, after the limits were separated
G3an incomplete edge is reported as a completed onekilled — test 13
G4the rise and fall labels are swappedkilled — tests 3 and 4
G5the maximum tracker starts at all-oneskilled — test 1

Five injected, five killed. Two notes.

G1 is the mutation that justifies the effective-high-time output existing at all. Measuring VIL-to-VIL includes the whole transition in the high phase, so it reports a longer high time than the receiver gets — and it is wrong in the dangerous direction: a bus whose high phase has been eroded below tHIGH(min) by a slow rise would be reported as compliant. It is caught by test 10, which compares the effective high time under a fast rise against the same period with a slow one and requires the second to be shorter.

That is a comparison between two stimuli rather than a check against a constant, and it is the right shape here: the absolute number depends on the period, but the direction of the change is the property being asserted.

G2 survived the first run for the third time in this module. tr(max) and tf(max) are both 300 ns in Fast-mode, so one limit for both edges is behaviourally identical to two under the specification's own numbers. Setting T_F_MAX = 20 against T_R_MAX = 30 — deliberately unrealistic — makes the mutant observable.

The three instances now form a pattern worth stating as a rule. Table 10 gives equal values to tVD;DAT and tVD;ACK in all three modes, to tSU;STA and tHD;STA in two of three, and to tr(max) and tf(max) in Fast-mode. Each time, a testbench configured with the specification's own numbers made a real defect unobservable.

Before running a mutation suite, list every pair of parameters whose configured values are equal, and separate them. Equal values are not a simplification; they are a coverage hole with a plausible excuse.

And the three differ instructively in what the fix is. For tVD;DAT/tVD;ACK no legal configuration separates them, so the testbench must use deliberately unrealistic numbers. For the two START margins a real configuration separates them — Standard-mode — and Chapter 11.5 §6 names it as a gap rather than closing it. For tr/tf a real configuration also separates them, Standard-mode again at 1000 against 300, and here the suite chose an artificial split instead so the rest of the chapter's numbers stay Fast-mode.

9. Verification Connection — Asserting on Something RTL Cannot See

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_props.sv — the assertion's honesty depends on its inputs
   // An edge is an interval, so the property needs both thresholds. An assertion written against
   // a single digital `sda` cannot express anything about tr at all -- the edge is precisely the
   // region where a single-threshold view has no value. This is the one place in the module
   // where the INTERFACE, not the property, is what makes the check possible.
   //
   //    sda_above_30  -- comparator at 0.3 VDD
   //    sda_above_70  -- comparator at 0.7 VDD

   int edge_ticks;
   always_ff @(posedge clk)
      if ($rose(sda_above_30) && !sda_above_70) edge_ticks <= 0;
      else                                      edge_ticks <= edge_ticks + 1;

   // tr as a MAXIMUM: bounded liveness, the shape Chapter 11.4 section 9 establishes. The
   // interesting failure is an edge that never completes, which no window-shaped property sees.
   property p_tr_max;
      @(posedge clk) ($rose(sda_above_30) && !sda_above_70) |-> ##[1:T_R_MAX] sda_above_70;
   endproperty
   assert property (p_tr_max)
      else $error("tr exceeded: 0.7 VDD not reached within %0d ticks of 0.3 VDD", T_R_MAX);

   // tr as a MINIMUM -- which section 6's monitor deliberately does NOT check, because it cannot
   // resolve 20 ns. In an environment whose sample clock CAN, this is the shape: a generate guard
   // so that a Standard-mode build, which has no rise-time minimum at all, does not carry a
   // vacuous assertion that a coverage report would score as passing.
   if (T_R_MIN > 0) begin : g_tr_min
      property p_tr_min;
         @(posedge clk) $rose(sda_above_70) |-> (edge_ticks >= T_R_MIN);
      endproperty
      assert property (p_tr_min)
         else $error("tr too FAST: %0d ticks, minimum %0d -- ringing risk", edge_ticks, T_R_MIN);
   end

   // The NEGATIVE property, and for this chapter it is about scope rather than polarity. tr and
   // tf are BOARD properties, so an assertion firing here does NOT indict the RTL -- and a
   // suite that treats it as an RTL failure will chase the wrong logic. The message says so.
   property p_edge_is_a_board_property;
      @(posedge clk) (viol_tr || viol_tf) |-> !rtl_fault_expected;
   endproperty
   assert property (p_edge_is_a_board_property)
      else $error("edge-rate violation: check Rp and Cb, NOT the state machine");
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_cov.sv — coverage over a design space the RTL does not choose
   // The unusual thing about this covergroup is that its axes are BOARD parameters. The design
   // space being covered is not the RTL's state space -- it is the set of boards the RTL might
   // be soldered onto, which is the space section 4's table lives in.
   covergroup i2c_edge_cg with function sample(int tr, int tf, int r_max, int r_min,
                                               int rp_ohms, int cb_pf, int vdd_mv, int mode);
      tr_margin: coverpoint (r_max - tr) {
         bins over       = {[$:-1]};
         bins exact      = {0};
         bins within_10  = {[1:10]};
         bins comfortable = {[11:$]};
      }

      // The too-FAST bin, which exists in no other covergroup in this module because no other
      // parameter has a minimum. A suite that only ever slows edges down has not tested it,
      // and section 5 says the failure it guards against is ringing misread as extra edges.
      tr_fast: coverpoint tr {
         bins too_fast = {[0:1]};
         bins near_min = {[2:5]};
         bins normal   = {[6:$]};
      }

      // Pull-up and capacitance as explicit axes, binned at section 4's boundaries. The
      // interesting bins are the ones the table above says are EMPTY -- a build that lands
      // there is a board error, and covering it proves the monitor reports it.
      pullup: coverpoint rp_ohms {
         bins too_strong = {[0:400]};      // below Rp(min) at most supplies: VOL not met
         bins typical    = {[401:2200]};
         bins too_weak   = {[2201:10000]}; // tr exceeded at any real Cb
      }
      cap: coverpoint cb_pf {
         bins light    = {[0:100]};
         bins moderate = {[101:250]};
         bins heavy    = {[251:400]};      // the resistive Fast-mode wall, section 4
         bins over_spec = {[401:$]};
      }

      // The cross that reproduces section 4's table. Cells corresponding to an EMPTY window
      // must show a violation, and a run that reports none of them has not exercised the
      // region where the specification's own numbers do not close.
      rp_x_cap: cross pullup, cap;

      // And speed mode, because which constraint BINDS flips with it -- Cb in Standard-mode,
      // the resistor in Fast-mode and Fm+. Same reasoning as Chapter 11.5's speed cross.
      speed: coverpoint mode { bins standard = {0}; bins fast = {1}; bins fm_plus = {2}; }
      tr_x_speed: cross tr_margin, speed;
   endgroup

10. FPGA and ASIC Implications

The monitor is two counters and four compares per line, around 70 flops at TICK_W = 12. Small, but it has a sample-rate requirement nothing else in the module has.

The sample clock must be fast enough to resolve tr(min). Fast-mode's 20 ns minimum is two ticks at 100 MHz, which means a 100 MHz monitor can distinguish "1 tick" from "2 ticks" and nothing finer. To measure the minimum with any confidence, 400 MHz or more is needed — 20 ns becomes 8 ticks. The maximum is easy at any rate (300 ns is 30 ticks at 100 MHz); the minimum is what sets the clock. A monitor built to check both at 100 MHz should report the minimum as a warning with its resolution stated, not as a verdict.

Cb is a layout deliverable, and §4's table is the number to hand the layout engineer. Not "keep it under 400 pF" — which is Table 10's number and is unreachable in Fast-mode with a resistor — but the derived figure: about 230 pF at 5 V or 366 pF at 3.3 V. Estimating it means counting pad capacitances from datasheets (typically 5–10 pF each), adding roughly 1 pF per centimetre of trace, and adding connectors generously.

A repeater or buffer resets the budget and adds a segment. When Cb cannot be met, the standard answer is a bus buffer, which splits the net into two independently budgeted segments. It also adds its own propagation delay to every transfer, which comes out of the low-phase budget of Chapter 11.9 — so it trades an electrical problem for a timing one.

On the driver side, the only lever is IOL. A stronger sink transistor lowers Rp(min) and widens §4's window from below. This is why Fast-mode-Plus pads are specified at 20 mA rather than 3 mA: at 20 mA and 3.3 V, Rp(min) falls to 145 Ω, which is below the 258 Ω Rp(max) — the window that was empty at 3 mA closes at 20 mA. That is the cleanest possible illustration of §4's mechanism, and it is the actual reason Fm+ exists as a separate speed grade rather than as Fast-mode with a faster clock.

11. Debugging — The Bus That Worked Until a Connector Was Added

Pitfall — designing to Table 10's Cb limit rather than to the derived resistive limit
Buggy Code
// A 5 V Fast-mode bus. Six devices on one board, 4.7 kohm pull-ups -- the value everybody
// uses -- and an estimated bus capacitance of about 180 pF.
//
//     Rp   = 4700 ohm
//     Cb   = 180 pF  (estimated: 6 pads at ~8 pF, plus traces)
//     tr   = 0.8473 * 4700 * 180e-12 = 717 ns
//     tr(max) for Fast-mode = 300 ns
//
// So the bus was ALREADY out of specification by a factor of 2.4 before anything was added,
// and it worked -- because every device on it happened to have generous input hysteresis and
// the master's tSU;DAT margin was large enough to absorb a slow rise.
//
// The design review had checked Cb against Table 10's 400 pF limit and found 180 pF
// comfortable. It had not checked tr, because Cb was "well within spec".
Symptom

A mezzanine connector was added for a daughterboard, bringing the estimated capacitance to about 300 pF. Still under Table 10's 400 pF. The review passed on the same grounds as before.

The bus stopped working reliably. Reads returned corrupted bytes at a rate that depended on which devices were installed, and the daughterboard did not need to be POPULATED for the failures to appear -- an empty connector was enough, which made no sense to anybody looking for a device-level cause.

The first theory was the connector's signal integrity, and a great deal of time went into its grounding. The second was crosstalk from an adjacent switching regulator, and a shield helped slightly -- which, as in Chapter 11.6's case, read as confirmation of an electrical cause that was real but not the cause.

A scope on SCL settled it in a minute, once someone measured the edge rather than looking at the eye. The rise took about 1.2 us. Fast-mode allows 300 ns.

The empty connector mattered because Cb is the TOTAL net capacitance -- pads, traces, connectors and vias. An unpopulated connector still contributes its own capacitance and the stub length to reach it. Nothing needed to be plugged in.

Working backwards: 0.8473 * 4700 * 300e-12 = 1195 ns, which matches the scope almost exactly. The arithmetic had been available from the start.

Root Cause

The design was checked against Cb(max) = 400 pF and never against tr(max) = 300 ns, on the implicit assumption that meeting the capacitance limit implies meeting the rise time. It does not: the two are linked by the pull-up, and Table 10's Cb figure is only reachable with a pull-up strong enough to charge it in time.

At 5 V, Rp(min) is 1533 ohm from the VOL/IOL requirement, so the largest capacitance a RESISTIVE Fast-mode pull-up can serve is tr(max)/(0.8473 * Rp(min)) = 231 pF. The board was at 300 pF with a 4700 ohm pull-up, which is three times weaker than even that bound allows.

The 4.7 kohm value was the deeper problem. It is the right answer for a Standard-mode bus, where Rp(max) at 400 pF is 2951 ohm... which 4.7 kohm also exceeds. It is a habit, not a calculation, and it had been carried into a Fast-mode design where the window is three times narrower.

12. Common Misconceptions

"A device's datasheet can promise tr compliance." It cannot. tr is set by the pull-up and the total bus capacitance, so it is a board property. No silicon meets it alone.

"Meeting Cb(max) means meeting tr(max)." Only if the pull-up is strong enough, and in Fast-mode at 5 V a resistive pull-up caps out near 230 pF against Table 10's 400 pF. §11 is that assumption costing a redesign.

"4.7 kΩ is a safe default." It exceeds Rp(max) for Standard-mode at 400 pF (2951 Ω) and is roughly four times too weak for Fast-mode at 300 pF. It is a habit, not a calculation.

"Faster edges are always safer." Fast-mode sets tr(min) = 20 ns because a too-fast edge rings, and an undershoot that crosses back through a threshold reads as an extra transition. This is the one parameter where speed is a violation.

"Cb(max) rising to 550 pF at Fm+ means Fm+ tolerates more capacitance with the same pull-up." It means Fm+ assumes a different kind of pull-up. With a 3 mA resistive drive the Fm+ window is empty at every supply; Fm+ specifies 20 mA pads, and that is what makes 550 pF reachable.

"Rise and fall are symmetric." A fall is driven by a transistor; a rise is an RC relaxation with nothing driving it. That is why tr(max) relaxes by 3.3× from Fast to Standard while tf(max) stays at 300 ns.

"An edge can be measured from a digital signal." The edge is exactly the interval in which a single-threshold digital view has no defined value. Measuring it needs two thresholds.

"A slow rise is only a signal-integrity concern." It shortens the high phase the receiver actually gets. §7's effective-high-time output measures that theft directly, and it is why §4's identity puts the edges inside the clock budget.

"An edge-rate violation indicts the RTL." It indicts the board. A monitor's message should say so, or the failure gets triaged to the wrong team.

13. Reason It Through

Why does tr(max) relax from 300 ns to 1000 ns between Fast-mode and Standard-mode while tf(max) stays at 300 ns in both?

Because a fall is actively driven by a transistor whose sink current does not change with the speed grade, while a rise is a passive RC relaxation whose duration the board designer chooses through Rp and Cb. The specification relaxes the parameter it expects the board to trade against and leaves the silicon-limited one alone.

A 5 V Fast-mode board has 300 pF and a 1 kΩ pull-up. Is it compliant?

No, and not for the rise-time reason. tr = 0.8473 × 1000 × 300 pF = 254 ns, inside the 300 ns limit. But Rp(min) at 5 V is 1533 Ω, so a 1 kΩ pull-up carries more than the guaranteed 3 mA sink and a device may not reach VOL(max) = 0.4 V. The window at 5 V and 300 pF is empty — 1533 Ω required from below, 1180 Ω from above — and the fix is a lower supply or a stronger driver, not a different resistor.

Why is Cb(max) larger at Fast-mode Plus than at Fast-mode, when every other Fm+ parameter is tighter?

Because it is not a resistor's budget. With a 3 mA driver the Fm+ window is empty at every common supply, so 550 pF is only reachable with a current-source pull-up or a 20 mA sink — which is what Fm+ pads specify. Reading it as a resistive limit inverts the design.

Fast-mode's tr(min) is 20 ns. What does a monitor sampling at 100 MHz actually know about it, and what should it report?

That the edge took one tick or two — 10 ns of resolution against a 20 ns limit. It can distinguish nothing finer, so a verdict is not justified. It should report the measurement with its resolution stated, as a warning; a confident minimum check needs 400 MHz or more.

A bus fails after an unpopulated connector is added. Why is that consistent with a rise-time cause and not with a device-level one?

Because Cb is the total net capacitance — pads, traces, vias and the connector itself, plus the stub reaching it. Nothing has to be plugged in for the capacitance to rise, and a higher Cb lengthens every rise through the same pull-up. A device-level cause would require a device.

§4 found two of three modes' windows empty at Table 10's own Cb(max). Is that an error in the table?

No. It says the three numbers in a row are individual limits, not a jointly achievable operating point — the same structure Chapter 11.4 §4 found in the low-phase budget and Chapter 11.2 §4 in the clock period. A specification's maxima are a boundary of the legal region, not a corner you may sit in.

14. Understanding Check

15. Summary

Rise is passive and fall is active, and that single consequence of open-drain signalling explains why tr(max) relaxes by 3.3× between Fast-mode and Standard-mode while tf(max) stays at 300 ns in both.

tr is a board property, not a device property. It is set by the pull-up and the total bus capacitance, so compliant silicon on a non-compliant board violates it — and a monitor's message should name the board, or the failure gets triaged to the wrong team.

The pull-up has two bounds that can cross. Rp(min) from the driver's VOL/IOL obligation, Rp(max) from tr — and at Table 10's own Cb(max), the window is empty for Fast-mode at 3.3 V and 5 V, and for Fast-mode Plus at every common supply with a 3 mA driver.

So the usable capacitance is well below the table's figure: about 231 pF at 5 V Fast-mode and 366 pF at 3.3 V, against a stated 400 pF. Which constraint binds also flips with the speed grade — Cb in Standard-mode, the resistor in Fast-mode and Fm+.

Cb(max) = 550 pF at Fm+ is a statement about current-source pull-ups, which is why Fm+ specifies 20 mA pads. That closes Chapter 11.4 §4's finding: the term the specification expects a design to beat is the rise time, and beating it means changing the pull-up's nature rather than its value.

tr(min) is the only place faster is worse, because a fast edge rings and an undershoot reads as an extra transition. It and tSP are two halves of one defence, and both appear only where edges are fast enough to need them.

An edge cannot be measured from a digital signal, because the edge is exactly where a single-threshold view is undefined. Two comparators, and a sample clock fast enough for the minimum — which is what sets the rate, not the maximum.

A specification's maxima bound the legal region; they are not a corner you may sit in. Three chapters have now found a worst-case sum that does not close, and each time the deficit named the term to beat.

16. What Comes Next

Chapter 11.8 takes tSP, the parameter §5 identified as the receiving half of the ringing defence — and it is the only block in this module that is not a checker. tSP is not something a monitor observes; it is something a device must do, so the design is a datapath: a filter in the signal path with real latency.

That latency is the chapter's substance. A filter that suppresses pulses shorter than tSP necessarily delays every legitimate transition by at least that long, and the delay lands inside the budgets of the six preceding chapters. There is no way to have the filter without paying for it, and the payment is what makes Chapter 11.9's final accounting come out the way it does.

The chapter also settles a detail that sounds trivial and is not: whether a pulse of exactly tSP must be rejected or accepted, and what tSP = 0 — legal in Standard-mode — has to mean for a filter that cannot be removed from the path.

Continue learning

Related tutorials