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
trby 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.
trhas a MINIMUM as well as a maximum in Fast-mode — the only parameter in the table where being too fast is a violation.
Cbis 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.
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 cyclesTwo 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:
| VDD | Rp(min) = (VDD − 0.4) / 3 mA |
|---|---|
| 5.0 V | 1533 Ω |
| 3.3 V | 967 Ω |
| 1.8 V | 467 Ω |
And Rp(max) at each mode's maximum Cb:
| mode | tr(max) | Cb(max) | Rp(max) |
|---|---|---|---|
| Standard | 1000 ns | 400 pF | 2951 Ω |
| Fast | 300 ns | 400 pF | 885 Ω |
| Fm+ | 120 ns | 550 pF | 258 Ω |
Cross them, and the result is not what the table's headline numbers suggest:
| mode, at Cb(max) | 5.0 V | 3.3 V | 1.8 V |
|---|---|---|---|
| Standard (1533…2951) | closes | 967…2951 closes | 467…2951 closes |
| Fast (…885) | 1533 > 885 — EMPTY | 967 > 885 — EMPTY | 467…885 closes |
| Fm+ (…258) | EMPTY | EMPTY | 467 > 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))
| mode | 5.0 V | 3.3 V | 1.8 V |
|---|---|---|---|
| Standard | 770 pF — Cb limit binds first | 1221 pF — Cb binds | 2528 pF — Cb binds |
| Fast | 231 pF | 366 pF | 758 pF — Cb binds |
| Fm+ | 92 pF | 146 pF | 303 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 parameter | the obligation | |
|---|---|---|
| the transmitting side | tr(min) = 20 ns | do not produce edges fast enough to ring badly |
| the receiving side | tSP = 0…50 ns | suppress 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.
// 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 `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 // 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 `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 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; 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
$ 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 edgeAll 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.
| # | stimulus | what it establishes |
|---|---|---|
| 1 | reset | the maximum-trackers read zero |
| 2 | four clean periods | four rises and four falls measured; nothing flagged |
| 3 | the first edge from an idle-high bus | labelled a FALL, not a rise |
| 4 | a 10-tick fall | measured as 10; the second edge of the period is the rise |
| 5 | a 50-tick rise | violates tr (30), does not flag tf; max_tr_seen records it |
| 6 | a 60-tick fall | violates tf (20), does not flag tr; max_tf_seen records it |
| 7 | a 25-tick edge | legal as a rise (30), illegal as a fall (20) |
| 8 | exactly at each maximum | accepted |
| 9 | one tick above each maximum | rejected — both boundaries pinned |
| 10 | a 60-tick rise vs a 10-tick rise | the effective high time shrinks — the measurement is VIH-based |
| 11 | an 80-tick fully-high interval | measured as 80 |
| 12 | a 40-tick rise | the transition is excluded from the effective high time |
| 13 | an incomplete edge | produces no measurement |
| 14 | a static bus | produces 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 defect | outcome |
|---|---|---|
| G1 | the effective high time is measured VIL-to-VIL, including the transition | killed — test 10 |
| G2 | tr and tf are judged against one limit | killed — test 7, after the limits were separated |
| G3 | an incomplete edge is reported as a completed one | killed — test 13 |
| G4 | the rise and fall labels are swapped | killed — tests 3 and 4 |
| G5 | the maximum tracker starts at all-ones | killed — 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
// 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"); // 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;
endgroup10. 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
// 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".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.
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
- Related topic
Pull-Up Resistors — Sizing, Rise Time and Bus Capacitance
The element that restores a released line, and the price it charges. Because HIGH is recovered passively through a resistor into real bus capacitance, the rising edge takes time — so the resistor sets timing as well as the idle level, and its correct value is a bounded range rather than a number to memorise.
- Related topic
Real Bus Electrical Behavior — Edges, Levels and Level Shifting
Take the idealised node onto a real board. Edges acquire shape and become asymmetric, levels turn out to be thresholds with a region between them, capacitance comes from identifiable places a designer controls, and two devices on one bus may not share a supply — which makes a bidirectional level shifter structural.
- Related topic
Choosing a Speed Mode — Pull-Ups, Capacitance and Design Cost
The pull-up resistor has one floor and two independent ceilings, and the interval between them is sometimes empty. Reproduces UM10204's own worked example, shows a Fast-mode bus at its own capacitance limit has no legal resistor, and explains why Fast-mode Plus is a 20 mA output stage rather than a faster one.
- Related topic
External Pull-Ups vs Internal Weak Pull-Ups
Every FPGA pin can enable an internal pull-up from a constraints file, and it is the wrong device for a bus. Works the arithmetic both ways: what resistance each speed mode allows at a given bus capacitance, what rise time a weak internal pull-up actually produces, and why a value that costs nothing is five times too large for Fast mode.
