I²C · Module 11
f(SCL), tLOW and tHIGH — The I²C Clock Envelope
A legal I²C clock is three constraints, not one frequency — and a perfectly compliant 400 kHz clock with a symmetric duty cycle is illegal in Fast-mode. Derives the envelope and an exact identity hidden in Table 10.
Ask an engineer what makes an I²C clock legal and the answer is almost always a frequency: 100 kHz, 400 kHz, 1 MHz. That answer is a third of the truth, and the missing two thirds are where real bring-up problems live.
A legal clock satisfies three constraints:
tLOW ≥ tLOW(min)·tHIGH ≥ tHIGH(min)·period ≥ 1/fSCL(max)
None of the three implies the others, which means a clock can satisfy any two and fail the third. The case worth internalising immediately: a clock running at exactly 400 kHz can be illegal, because 400 kHz says nothing about how that period is divided.
This chapter also uncovers something exact in Table 10 that turns out to run through the whole module.
1. The Three Rows
Three observations before any arithmetic.
tLOW and tHIGH have no maximum. That is not an omission — it is what makes clock stretching legal (Module 12). A slave may hold SCL low for as long as it needs and the clock remains compliant; it is simply slower. Any checker for these parameters is therefore a one-sided comparison, and §7's test 7 confirms a 30 µs low phase raises nothing.
tLOW(min) > tHIGH(min) in every mode. 4.7 against 4.0, 1.3 against 0.6, 0.5 against 0.26. A legal I²C clock is asymmetric, and the asymmetry is not a convention — §3 derives it.
fSCL is expressed as a frequency and checked as a period. A frequency is a rate over time and a compliance check is about one cycle, so the useful form is period ≥ 1/fSCL(max): 10 µs, 2.5 µs, 1 µs. That reformulation is also what makes §4's identity visible.
2. A Legal Frequency Can Be an Illegal Clock
This is the section to remember. Take Fast-mode, whose period floor is 2.5 µs, and run at exactly 400 kHz. Three ways to divide that period:
| duty | tHIGH | tLOW | tHIGH ≥ 0.6? | tLOW ≥ 1.3? | verdict |
|---|---|---|---|---|---|
| 50 % | 1.25 µs | 1.25 µs | yes | no | illegal |
| 25 % | 0.625 µs | 1.875 µs | yes | yes | legal |
| 75 % | 1.875 µs | 0.625 µs | yes | no | illegal |
| 60 % | 1.5 µs | 1.0 µs | yes | no | illegal |
A symmetric 400 kHz clock is not a legal Fast-mode clock. Read that again, because the instinct to divide a period in half is very strong and a 50 % duty is what a naive divider produces. At Fast-mode, tLOW(min) is 1.3 µs out of a 2.5 µs period — 52 % of the period must be low, so the duty cycle cannot exceed 48 %.
3. Why the Low Phase Carries the Longer Obligation
The asymmetry is derivable rather than arbitrary, and the derivation is short.
The low phase is when SDA is allowed to move (Chapter 11.1, question 2). So everything that has to happen between one bit and the next happens inside it:
- the transmitter must hold the previous bit long enough to bridge SCL's falling edge —
tHD;DAT, Chapter 11.3; - it must then produce the new bit —
tVD;DAT, Chapter 11.4; - the line must physically settle —
tr, Chapter 11.7; - and the new value must be stable before the next rising edge —
tSU;DAT.
The high phase has only one job: be long enough for a receiver to sample. Nothing has to change during it; nothing is allowed to. One obligation against four.
And the four are not notional. Adding the Fast-mode figures for steps 2, 3 and 4 gives 0.9 + 0.3 + 0.1 = 1.3 µs, which is tLOW(min) exactly. The low phase minimum is not a round number somebody chose — it is the sum of what has to fit inside it. Chapter 11.9 makes that a hardware check; here it is the reason the clock is asymmetric.
4. The Identity Hiding in Table 10
Now the arithmetic that runs through the rest of the module. Add the two phase minima and the two edge maxima, and compare against the period floor:
| mode | tLOW(min) | tHIGH(min) | tr(max) | tf(max) | sum | 1/fSCL(max) |
|---|---|---|---|---|---|---|
| Standard | 4.7 | 4.0 | 1.000 | 0.300 | 10.000 µs | 10.000 µs |
| Fast | 1.3 | 0.6 | 0.300 | 0.300 | 2.500 µs | 2.500 µs |
| Fm+ | 0.5 | 0.26 | 0.120 | 0.120 | 1.000 µs | 1.000 µs |
tLOW(min) + tHIGH(min) + tr(max) + tf(max) = 1/fSCL(max)— exactly, in all three modes.
Three separate columns of the table, three different speed grades, and the identity is exact every time. That is not coincidence; it is the table being internally consistent, and it has three consequences worth stating plainly.
At the limits there is ZERO margin. A design that takes the full tLOW(min), the full tHIGH(min), the maximum rise and the maximum fall has exactly used its period and has nothing left. So every real design must beat at least one of the four terms — and which one it beats is a board-level decision: a smaller pull-up buys rise time, a stronger driver buys fall time, a slower clock buys everything.
The edges are part of the clock budget, not an afterthought. A slow rise does not merely soften the waveform; it eats a phase, and the phases have minima. Chapter 11.7 is about that transfer.
It explains the duty cycle numerically. At the limits, Fast-mode's high phase is 0.6 of 2.5 µs — 24 % — and the rest is low phase plus edges. The asymmetry §3 derived from obligations falls out of the identity as a number.
5. The Envelope, Drawn
One Fast-mode period at the limits: 1.3 low + 0.6 high + 0.3 rise + 0.3 fall = 2.5 µs
10 cyclesThe figure is drawn with kind: "signal" rather than kind: "clock" for exactly the reason Chapter 11.1 §3 gives: the claim is about the proportions of one particular period, and a clock row renders a generic square wave that would make the proportions meaningless.
What the budget row shows is that the period is fully allocated. There is no unlabelled interval — no slack — which is the visual form of §4's identity.
6. The Envelope Checker in Three Languages
The design measures a period falling-edge to falling-edge and reports three verdicts plus the worst case seen.
| output | meaning |
|---|---|
t_low, t_high, t_period | the measurement, reported once per period |
viol_low, viol_high, viol_period | the three verdicts, independently |
min_low_seen, min_high_seen | the worst case, which is what a report should quote |
// The SCL CLOCK ENVELOPE. A legal I2C clock is not one constraint but THREE, and the
// three are not redundant:
//
// tLOW >= tLOW(min) the low phase carries the longer obligation
// tHIGH >= tHIGH(min)
// fSCL <= fSCL(max) equivalently, period >= 1/fSCL(max)
//
// A clock can satisfy any two and fail the third, which is why this block reports three
// separate verdicts. The case that surprises people is a clock at a perfectly legal
// FREQUENCY whose duty cycle is illegal: 400 kHz at 75 % duty is 1.875 us high and
// 0.625 us LOW, and Fast-mode's tLOW(min) is 1.3 us. Legal frequency, illegal clock.
//
// The third constraint is stated as a PERIOD rather than a frequency, because that is how
// it is checked and because of an identity in Table 10 worth knowing:
//
// tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
//
// exactly, in all three speed modes. The period budget is therefore fully accounted for:
// the phase minima and the edge maxima leave nothing over. Chapter 11.7 spends the edges
// and Chapter 11.9 closes the whole budget.
//
// PASSIVE: observes SCL and drives nothing.
module i2c_clock_envelope_checker #(
parameter int TICK_W = 16,
// Fast-mode, in ticks of a 100 MHz sample clock: 1.3 us, 0.6 us, 2.5 us.
parameter int T_LOW_MIN = 130,
parameter int T_HIGH_MIN = 60,
parameter int T_PERIOD_MIN = 250
)(
input logic clk,
input logic rst_n,
input logic scl_in,
// ---- measured, reported once per period ----
output logic period_valid, // pulse: a full period has been measured
output logic [TICK_W-1:0] t_low,
output logic [TICK_W-1:0] t_high,
output logic [TICK_W-1:0] t_period,
// ---- the three verdicts ----
output logic viol_low,
output logic viol_high,
output logic viol_period, // equivalently: fSCL too fast
// ---- running totals ----
output logic [TICK_W-1:0] n_periods,
output logic [TICK_W-1:0] n_bad,
// The WORST case seen, which is what a bring-up report should quote. An average hides
// a single violating period, and a single violating period is a violation.
output logic [TICK_W-1:0] min_low_seen,
output logic [TICK_W-1:0] min_high_seen
);
logic scl_q;
logic scl_rise, scl_fall;
assign scl_rise = !scl_q && scl_in;
assign scl_fall = scl_q && !scl_in;
// A period is measured FALLING EDGE to FALLING EDGE, so that one period contains
// exactly one low phase followed by one high phase and the two sum to the period.
// Measuring rise-to-rise would work equally well; what would NOT work is measuring
// the low and high phases independently and adding them, because they would then come
// from different periods whenever the clock is not perfectly regular -- and an
// irregular clock is exactly what this block exists to find.
logic [TICK_W-1:0] phase_ticks;
logic [TICK_W-1:0] low_ticks;
logic have_low; // a low phase has been captured this period
// There is deliberately NO "have we seen a falling edge yet" guard here, and its absence
// was established by mutation testing rather than assumed. Such a guard looks necessary --
// the low phase in progress when reset is released began before this block was watching,
// so measuring it would report an artefact. But `scl_q` resets to 1, matching an idle bus,
// so a line that is LOW at reset release registers a falling edge on the very first clock:
// `have_low` can therefore only ever be set after a fall this block itself observed. The
// guard was unreachable, no stimulus could distinguish its presence from its absence, and
// the honest response to an equivalent mutant is to delete the dead code rather than to
// contrive a test that appears to cover it.
// Every measurement includes the sample period in which it is taken; reading the
// register directly would under-report each interval by one tick and shift every
// comparison against a spec minimum. Chapter 11.1 records how that is found.
logic [TICK_W-1:0] phase_now;
assign phase_now = phase_ticks + 1'b1;
always_ff @(posedge clk) begin
if (!rst_n) begin
scl_q <= 1'b1; // an idle bus has SCL released, therefore high
phase_ticks <= '0;
low_ticks <= '0;
have_low <= 1'b0;
period_valid <= 1'b0;
t_low <= '0;
t_high <= '0;
t_period <= '0;
viol_low <= 1'b0;
viol_high <= 1'b0;
viol_period <= 1'b0;
n_periods <= '0;
n_bad <= '0;
// The running minima start at their maximum so the first real measurement
// replaces them. Starting at zero would make every report read "0", which is
// the classic way a worst-case tracker silently reports nothing.
min_low_seen <= {TICK_W{1'b1}};
min_high_seen <= {TICK_W{1'b1}};
end else begin
scl_q <= scl_in;
period_valid <= 1'b0;
if (scl_fall) begin
// The end of a high phase, and the end of a period if a low phase has
// already been captured.
if (have_low) begin
t_low <= low_ticks;
t_high <= phase_now;
t_period <= low_ticks + phase_now;
period_valid <= 1'b1;
// Three independent comparisons. Each is STRICTLY LESS THAN, so a
// measurement exactly equal to a specified minimum passes -- the
// table's values are minima, and rejecting the boundary would reject
// compliant traffic.
viol_low <= (low_ticks < T_LOW_MIN[TICK_W-1:0]);
viol_high <= (phase_now < T_HIGH_MIN[TICK_W-1:0]);
viol_period <= ((low_ticks + phase_now) < T_PERIOD_MIN[TICK_W-1:0]);
n_periods <= n_periods + 1'b1;
if ((low_ticks < T_LOW_MIN[TICK_W-1:0])
|| (phase_now < T_HIGH_MIN[TICK_W-1:0])
|| ((low_ticks + phase_now) < T_PERIOD_MIN[TICK_W-1:0]))
n_bad <= n_bad + 1'b1;
if (low_ticks < min_low_seen) min_low_seen <= low_ticks;
if (phase_now < min_high_seen) min_high_seen <= phase_now;
end
have_low <= 1'b0;
phase_ticks <= '0;
end else if (scl_rise) begin
// The end of a low phase.
low_ticks <= phase_now;
have_low <= 1'b1;
phase_ticks <= '0;
end else begin
phase_ticks <= phase_now;
end
end
end
endmodule `timescale 1ns/1ps
// 100 MHz sample clock: one tick is 10 ns, so Fast-mode's envelope is tLOW 130 ticks,
// tHIGH 60 ticks, period 250 ticks. Expected verdicts are derived from the parameters
// rather than written as constants, so the tests check the comparisons.
module i2c_clock_envelope_checker_tb;
localparam int TICK_W = 16;
localparam int T_LOW_MIN = 130; // Fast-mode tLOW = 1.3 us
localparam int T_HIGH_MIN = 60; // Fast-mode tHIGH = 0.6 us
localparam int T_PERIOD_MIN = 250; // Fast-mode 1/fSCL = 2.5 us
logic clk = 1'b0;
always #5 clk = ~clk;
logic rst_n = 1'b0;
logic scl_in = 1'b1;
logic period_valid, viol_low, viol_high, viol_period;
logic [TICK_W-1:0] t_low, t_high, t_period, n_periods, n_bad;
logic [TICK_W-1:0] min_low_seen, min_high_seen;
int errors = 0;
int base;
logic [TICK_W-1:0] bad_before, per_before;
i2c_clock_envelope_checker #(.TICK_W(TICK_W), .T_LOW_MIN(T_LOW_MIN),
.T_HIGH_MIN(T_HIGH_MIN), .T_PERIOD_MIN(T_PERIOD_MIN)) dut (.*);
initial begin #2000000; $display("FAIL: watchdog expired"); $finish; end
logic [TICK_W-1:0] lo_log [0:31];
logic [TICK_W-1:0] hi_log [0:31];
logic [TICK_W-1:0] pe_log [0:31];
logic vl_log [0:31];
logic vh_log [0:31];
logic vp_log [0:31];
int n_log;
always @(posedge clk) if (rst_n && period_valid && n_log < 32) begin
lo_log[n_log] = t_low; hi_log[n_log] = t_high; pe_log[n_log] = t_period;
vl_log[n_log] = viol_low; vh_log[n_log] = viol_high; vp_log[n_log] = viol_period;
n_log++;
end
task automatic tick(input int n);
begin repeat (n) @(negedge clk); end
endtask
// One SCL period: low for `lo` ticks then high for `hi` ticks. The block measures
// falling edge to falling edge, so this task must be called back to back for the
// measurement to line up with the intent.
task automatic scl_period(input int lo, input int hi);
begin
scl_in = 1'b0; tick(lo);
scl_in = 1'b1; tick(hi);
end
endtask
initial begin
tick(3);
// The worst-case trackers must start at their MAXIMUM, not at zero -- a
// worst-case tracker initialised to zero reports zero forever.
if (min_low_seen !== {TICK_W{1'b1}} || min_high_seen !== {TICK_W{1'b1}}) begin
$display("FAIL: the worst-case trackers did not start at their maximum");
errors++; end
// Reset is released with SCL HIGH, matching an idle bus and the block's own reset
// value for its SCL history register -- so no spurious edge is manufactured by the
// release itself. Mutation testing established that this makes a "have we seen a fall
// yet" guard unreachable, and the guard was deleted rather than left as dead code; see
// the design header.
rst_n = 1'b1; tick(2);
repeat (2) scl_period(160, 90);
scl_in = 1'b1; tick(20);
bad_before = n_bad;
// ---- 1: a LEGAL Fast-mode clock with room to spare. 160 low, 90 high, period
// 250 -- exactly at the period minimum, and both phases above their minima.
scl_in = 1'b1; tick(20);
repeat (5) scl_period(160, 90);
scl_in = 1'b1; tick(20);
if (n_log < 4) begin
$display("FAIL: %0d periods reported from five, expected at least 4", n_log);
errors++; end
if (n_bad !== bad_before) begin
$display("FAIL: %0d legal periods flagged", n_bad - bad_before); errors++; end
if (lo_log[n_log-1] < 16'd155 || lo_log[n_log-1] > 16'd165) begin
$display("FAIL: a 160-tick low phase measured %0d", lo_log[n_log-1]); errors++; end
if (hi_log[n_log-1] < 16'd85 || hi_log[n_log-1] > 16'd95) begin
$display("FAIL: a 90-tick high phase measured %0d", hi_log[n_log-1]); errors++; end
// The period must be the SUM of the two phases that were actually measured --
// not a separately timed interval that could drift from them.
if (pe_log[n_log-1] !== lo_log[n_log-1] + hi_log[n_log-1]) begin
$display("FAIL: period %0d is not low %0d + high %0d",
pe_log[n_log-1], lo_log[n_log-1], hi_log[n_log-1]); errors++; end
// ---- 2: THE INTERESTING CASE. A legal PERIOD and a legal tHIGH, with an
// ILLEGAL tLOW. 250 ticks total, 100 low and 150 high -- a 60 % duty at
// exactly 400 kHz. The frequency is compliant and the clock is not.
begin
base = n_log;
repeat (3) scl_period(100, 150);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b1) begin
$display("FAIL: a 100-tick low phase was not flagged (tLOW min = %0d)",
T_LOW_MIN); errors++; end
if (vh_log[base + 1] !== 1'b0) begin
$display("FAIL: a 150-tick high phase was flagged (tHIGH min = %0d)",
T_HIGH_MIN); errors++; end
if (vp_log[base + 1] !== 1'b0) begin
$display("FAIL: a 250-tick period was flagged (period min = %0d) -- the frequency is LEGAL",
T_PERIOD_MIN); errors++; end
end
// ---- 3: the mirror -- a legal period and tLOW, with an ILLEGAL tHIGH.
// 250 ticks total, 210 low and 40 high.
begin
base = n_log;
repeat (3) scl_period(210, 40);
scl_in = 1'b1; tick(20);
if (vh_log[base + 1] !== 1'b1) begin
$display("FAIL: a 40-tick high phase was not flagged"); errors++; end
if (vl_log[base + 1] !== 1'b0 || vp_log[base + 1] !== 1'b0) begin
$display("FAIL: an illegal tHIGH also flagged another constraint"); errors++; end
end
// ---- 4: both phases LEGAL and the PERIOD too short -- which Table 10 makes
// impossible for the minima themselves, but is reachable with a period
// minimum raised above tLOW(min)+tHIGH(min). Here 130 + 60 = 190 < 250, so
// a clock at exactly both phase minima violates fSCL. This is the case that
// proves the three constraints are independent rather than two of them
// implying the third.
begin
base = n_log;
repeat (3) scl_period(T_LOW_MIN, T_HIGH_MIN);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b0 || vh_log[base + 1] !== 1'b0) begin
$display("FAIL: phases exactly at their minima were flagged"); errors++; end
if (vp_log[base + 1] !== 1'b1) begin
$display("FAIL: a %0d-tick period was not flagged (period min = %0d)",
T_LOW_MIN + T_HIGH_MIN, T_PERIOD_MIN); errors++; end
end
// ---- 5: BOUNDARIES. Exactly at each minimum is legal; one tick below is not.
begin
base = n_log;
repeat (3) scl_period(T_LOW_MIN, T_PERIOD_MIN - T_LOW_MIN);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b0) begin
$display("FAIL: tLOW exactly at the minimum was rejected"); errors++; end
if (vp_log[base + 1] !== 1'b0) begin
$display("FAIL: a period exactly at the minimum was rejected"); errors++; end
end
begin
base = n_log;
repeat (3) scl_period(T_LOW_MIN - 1, T_PERIOD_MIN - T_LOW_MIN + 1);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b1) begin
$display("FAIL: tLOW one tick below the minimum was accepted"); errors++; end
end
// ---- 6: the WORST CASE is tracked, not the last value. After a short phase has
// been seen, a long clean run must NOT erase it -- a report that quotes the
// most recent period hides every violation that is not the final one.
begin
repeat (6) scl_period(200, 120);
scl_in = 1'b1; tick(20);
if (min_low_seen > 16'd131) begin
$display("FAIL: min_low_seen = %0d after a short phase was seen earlier",
min_low_seen); errors++; end
end
// ---- 7: a STRETCHED low phase is not a violation. A slave holding SCL low makes
// tLOW enormous, and tLOW has only a MINIMUM -- there is no maximum, which
// is exactly what makes clock stretching legal.
begin
bad_before = n_bad;
scl_period(3000, 90);
scl_period(160, 90);
scl_in = 1'b1; tick(20);
if (n_bad !== bad_before) begin
$display("FAIL: a 3000-tick stretched low phase was flagged as a violation");
errors++; end
end
// ---- 8: an IDLE bus produces no periods at all. SCL parked high is not a
// zero-frequency clock; it is no clock, and reporting periods for it would
// manufacture measurements out of nothing.
begin
per_before = n_periods;
scl_in = 1'b1; tick(500);
if (n_periods !== per_before) begin
$display("FAIL: an idle bus produced %0d periods", n_periods - per_before);
errors++; end
end
// ---- 9: the FIRST low phase after reset must not be measured, because it began
// before this block was watching. Measuring it would report a value that is
// an artefact of when reset was released rather than a property of the bus.
// Checked by construction: test 1's first reported period is index 0 and its
// low phase is a full 160 ticks, not the ragged interval since reset.
// Checked directly by the guard test in the preamble: a rise with no preceding fall
// produced no period at all, so no pre-reset interval was ever measured.
if (lo_log[0] === 16'hFFFF) begin
$display("FAIL: the first reported low phase is an uninitialised artefact"); errors++; end
if (errors == 0)
$display("PASS: three independent constraints, a legal frequency can still be an illegal clock, stretching is not a violation, worst case tracked");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule // The SCL CLOCK ENVELOPE. A legal I2C clock is not one constraint but THREE, and the
// three are not redundant:
//
// tLOW >= tLOW(min) the low phase carries the longer obligation
// tHIGH >= tHIGH(min)
// fSCL <= fSCL(max) equivalently, period >= 1/fSCL(max)
//
// A clock can satisfy any two and fail the third, which is why this block reports three
// separate verdicts. The case that surprises people is a clock at a perfectly legal
// FREQUENCY whose duty cycle is illegal: 400 kHz at 75 % duty is 1.875 us high and
// 0.625 us LOW, and Fast-mode's tLOW(min) is 1.3 us. Legal frequency, illegal clock.
//
// The third constraint is stated as a PERIOD rather than a frequency, because that is how
// it is checked and because of an identity in Table 10 worth knowing:
//
// tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
//
// exactly, in all three speed modes. The period budget is therefore fully accounted for:
// the phase minima and the edge maxima leave nothing over. Chapter 11.7 spends the edges
// and Chapter 11.9 closes the whole budget.
//
// PASSIVE: observes SCL and drives nothing.
// (Verilog-2001)
module i2c_clock_envelope_checker #(
parameter TICK_W = 16,
// Fast-mode, in ticks of a 100 MHz sample clock: 1.3 us, 0.6 us, 2.5 us.
parameter T_LOW_MIN = 130,
parameter T_HIGH_MIN = 60,
parameter T_PERIOD_MIN = 250
)(
input wire clk,
input wire rst_n,
input wire scl_in,
// ---- measured, reported once per period ----
output reg period_valid, // pulse: a full period has been measured
output reg [TICK_W-1:0] t_low,
output reg [TICK_W-1:0] t_high,
output reg [TICK_W-1:0] t_period,
// ---- the three verdicts ----
output reg viol_low,
output reg viol_high,
output reg viol_period, // equivalently: fSCL too fast
// ---- running totals ----
output reg [TICK_W-1:0] n_periods,
output reg [TICK_W-1:0] n_bad,
// The WORST case seen, which is what a bring-up report should quote. An average hides
// a single violating period, and a single violating period is a violation.
output reg [TICK_W-1:0] min_low_seen,
output reg [TICK_W-1:0] min_high_seen
);
reg scl_q;
wire scl_rise, scl_fall;
assign scl_rise = !scl_q && scl_in;
assign scl_fall = scl_q && !scl_in;
// A period is measured FALLING EDGE to FALLING EDGE, so that one period contains
// exactly one low phase followed by one high phase and the two sum to the period.
// Measuring rise-to-rise would work equally well; what would NOT work is measuring
// the low and high phases independently and adding them, because they would then come
// from different periods whenever the clock is not perfectly regular -- and an
// irregular clock is exactly what this block exists to find.
reg [TICK_W-1:0] phase_ticks;
reg [TICK_W-1:0] low_ticks;
reg have_low; // a low phase has been captured this period
// There is deliberately NO "have we seen a falling edge yet" guard here, and its absence
// was established by mutation testing rather than assumed. Such a guard looks necessary --
// the low phase in progress when reset is released began before this block was watching,
// so measuring it would report an artefact. But `scl_q` resets to 1, matching an idle bus,
// so a line that is LOW at reset release registers a falling edge on the very first clock:
// `have_low` can therefore only ever be set after a fall this block itself observed. The
// guard was unreachable, no stimulus could distinguish its presence from its absence, and
// the honest response to an equivalent mutant is to delete the dead code rather than to
// contrive a test that appears to cover it.
// Every measurement includes the sample period in which it is taken; reading the
// register directly would under-report each interval by one tick and shift every
// comparison against a spec minimum. Chapter 11.1 records how that is found.
wire [TICK_W-1:0] phase_now;
assign phase_now = phase_ticks + 1'b1;
always @(posedge clk) begin
if (!rst_n) begin
scl_q <= 1'b1; // an idle bus has SCL released, therefore high
phase_ticks <= {TICK_W{1'b0}};
low_ticks <= {TICK_W{1'b0}};
have_low <= 1'b0;
period_valid <= 1'b0;
t_low <= {TICK_W{1'b0}};
t_high <= {TICK_W{1'b0}};
t_period <= {TICK_W{1'b0}};
viol_low <= 1'b0;
viol_high <= 1'b0;
viol_period <= 1'b0;
n_periods <= {TICK_W{1'b0}};
n_bad <= {TICK_W{1'b0}};
// The running minima start at their maximum so the first real measurement
// replaces them. Starting at zero would make every report read "0", which is
// the classic way a worst-case tracker silently reports nothing.
min_low_seen <= {TICK_W{1'b1}};
min_high_seen <= {TICK_W{1'b1}};
end else begin
scl_q <= scl_in;
period_valid <= 1'b0;
if (scl_fall) begin
// The end of a high phase, and the end of a period if a low phase has
// already been captured.
if (have_low) begin
t_low <= low_ticks;
t_high <= phase_now;
t_period <= low_ticks + phase_now;
period_valid <= 1'b1;
// Three independent comparisons. Each is STRICTLY LESS THAN, so a
// measurement exactly equal to a specified minimum passes -- the
// table's values are minima, and rejecting the boundary would reject
// compliant traffic.
viol_low <= (low_ticks < T_LOW_MIN);
viol_high <= (phase_now < T_HIGH_MIN);
viol_period <= ((low_ticks + phase_now) < T_PERIOD_MIN);
n_periods <= n_periods + 1'b1;
if ((low_ticks < T_LOW_MIN)
|| (phase_now < T_HIGH_MIN)
|| ((low_ticks + phase_now) < T_PERIOD_MIN))
n_bad <= n_bad + 1'b1;
if (low_ticks < min_low_seen) min_low_seen <= low_ticks;
if (phase_now < min_high_seen) min_high_seen <= phase_now;
end
have_low <= 1'b0;
phase_ticks <= {TICK_W{1'b0}};
end else if (scl_rise) begin
// The end of a low phase.
low_ticks <= phase_now;
have_low <= 1'b1;
phase_ticks <= {TICK_W{1'b0}};
end else begin
phase_ticks <= phase_now;
end
end
end
endmodule `timescale 1ns/1ps
// 100 MHz sample clock: one tick is 10 ns, so Fast-mode's envelope is tLOW 130 ticks,
// tHIGH 60 ticks, period 250 ticks. Expected verdicts are derived from the parameters
// rather than written as constants, so the tests check the comparisons.
module i2c_clock_envelope_checker_tb; // Verilog-2001
localparam TICK_W = 16;
localparam T_LOW_MIN = 130; // Fast-mode tLOW = 1.3 us
localparam T_HIGH_MIN = 60; // Fast-mode tHIGH = 0.6 us
localparam T_PERIOD_MIN = 250; // Fast-mode 1/fSCL = 2.5 us
reg clk = 1'b0;
always #5 clk = ~clk;
reg rst_n = 1'b0;
reg scl_in = 1'b1;
wire period_valid, viol_low, viol_high, viol_period;
wire [TICK_W-1:0] t_low, t_high, t_period, n_periods, n_bad;
wire [TICK_W-1:0] min_low_seen, min_high_seen;
integer errors = 0;
integer base = 0;
reg [TICK_W-1:0] bad_before, per_before;
i2c_clock_envelope_checker #(.TICK_W(TICK_W), .T_LOW_MIN(T_LOW_MIN),
.T_HIGH_MIN(T_HIGH_MIN), .T_PERIOD_MIN(T_PERIOD_MIN)) dut (
.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .period_valid(period_valid),
.t_low(t_low), .t_high(t_high), .t_period(t_period), .viol_low(viol_low),
.viol_high(viol_high), .viol_period(viol_period), .n_periods(n_periods),
.n_bad(n_bad), .min_low_seen(min_low_seen), .min_high_seen(min_high_seen));
initial begin #2000000; $display("FAIL: watchdog expired"); $finish; end
reg [TICK_W-1:0] lo_log [0:31];
reg [TICK_W-1:0] hi_log [0:31];
reg [TICK_W-1:0] pe_log [0:31];
reg vl_log [0:31];
reg vh_log [0:31];
reg vp_log [0:31];
integer n_log = 0;
always @(posedge clk) if (rst_n && period_valid && n_log < 32) begin
lo_log[n_log] = t_low; hi_log[n_log] = t_high; pe_log[n_log] = t_period;
vl_log[n_log] = viol_low; vh_log[n_log] = viol_high; vp_log[n_log] = viol_period;
n_log = n_log + 1;
end
task tick;
input integer n;
begin repeat (n) @(negedge clk); end
endtask
// One SCL period: low for `lo` ticks then high for `hi` ticks. The block measures
// falling edge to falling edge, so this task must be called back to back for the
// measurement to line up with the intent.
task scl_period;
input integer lo;
input integer hi;
begin
scl_in = 1'b0; tick(lo);
scl_in = 1'b1; tick(hi);
end
endtask
initial begin
tick(3);
// The worst-case trackers must start at their MAXIMUM, not at zero -- a
// worst-case tracker initialised to zero reports zero forever.
if (min_low_seen !== {TICK_W{1'b1}} || min_high_seen !== {TICK_W{1'b1}}) begin
$display("FAIL: the worst-case trackers did not start at their maximum");
errors = errors + 1; end
// Reset is released with SCL HIGH, matching an idle bus and the block's own reset
// value for its SCL history register -- so no spurious edge is manufactured by the
// release itself. Mutation testing established that this makes a "have we seen a fall
// yet" guard unreachable, and the guard was deleted rather than left as dead code; see
// the design header.
rst_n = 1'b1; tick(2);
repeat (2) scl_period(160, 90);
scl_in = 1'b1; tick(20);
bad_before = n_bad;
// ---- 1: a LEGAL Fast-mode clock with room to spare. 160 low, 90 high, period
// 250 -- exactly at the period minimum, and both phases above their minima.
scl_in = 1'b1; tick(20);
repeat (5) scl_period(160, 90);
scl_in = 1'b1; tick(20);
if (n_log < 4) begin
$display("FAIL: %0d periods reported from five, expected at least 4", n_log);
errors = errors + 1; end
if (n_bad !== bad_before) begin
$display("FAIL: %0d legal periods flagged", n_bad - bad_before); errors = errors + 1; end
if (lo_log[n_log-1] < 16'd155 || lo_log[n_log-1] > 16'd165) begin
$display("FAIL: a 160-tick low phase measured %0d", lo_log[n_log-1]); errors = errors + 1; end
if (hi_log[n_log-1] < 16'd85 || hi_log[n_log-1] > 16'd95) begin
$display("FAIL: a 90-tick high phase measured %0d", hi_log[n_log-1]); errors = errors + 1; end
// The period must be the SUM of the two phases that were actually measured --
// not a separately timed interval that could drift from them.
if (pe_log[n_log-1] !== lo_log[n_log-1] + hi_log[n_log-1]) begin
$display("FAIL: period %0d is not low %0d + high %0d",
pe_log[n_log-1], lo_log[n_log-1], hi_log[n_log-1]); errors = errors + 1; end
// ---- 2: THE INTERESTING CASE. A legal PERIOD and a legal tHIGH, with an
// ILLEGAL tLOW. 250 ticks total, 100 low and 150 high -- a 60 % duty at
// exactly 400 kHz. The frequency is compliant and the clock is not.
begin
base = n_log;
repeat (3) scl_period(100, 150);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b1) begin
$display("FAIL: a 100-tick low phase was not flagged (tLOW min = %0d)",
T_LOW_MIN); errors = errors + 1; end
if (vh_log[base + 1] !== 1'b0) begin
$display("FAIL: a 150-tick high phase was flagged (tHIGH min = %0d)",
T_HIGH_MIN); errors = errors + 1; end
if (vp_log[base + 1] !== 1'b0) begin
$display("FAIL: a 250-tick period was flagged (period min = %0d) -- the frequency is LEGAL",
T_PERIOD_MIN); errors = errors + 1; end
end
// ---- 3: the mirror -- a legal period and tLOW, with an ILLEGAL tHIGH.
// 250 ticks total, 210 low and 40 high.
begin
base = n_log;
repeat (3) scl_period(210, 40);
scl_in = 1'b1; tick(20);
if (vh_log[base + 1] !== 1'b1) begin
$display("FAIL: a 40-tick high phase was not flagged"); errors = errors + 1; end
if (vl_log[base + 1] !== 1'b0 || vp_log[base + 1] !== 1'b0) begin
$display("FAIL: an illegal tHIGH also flagged another constraint"); errors = errors + 1; end
end
// ---- 4: both phases LEGAL and the PERIOD too short -- which Table 10 makes
// impossible for the minima themselves, but is reachable with a period
// minimum raised above tLOW(min)+tHIGH(min). Here 130 + 60 = 190 < 250, so
// a clock at exactly both phase minima violates fSCL. This is the case that
// proves the three constraints are independent rather than two of them
// implying the third.
begin
base = n_log;
repeat (3) scl_period(T_LOW_MIN, T_HIGH_MIN);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b0 || vh_log[base + 1] !== 1'b0) begin
$display("FAIL: phases exactly at their minima were flagged"); errors = errors + 1; end
if (vp_log[base + 1] !== 1'b1) begin
$display("FAIL: a %0d-tick period was not flagged (period min = %0d)",
T_LOW_MIN + T_HIGH_MIN, T_PERIOD_MIN); errors = errors + 1; end
end
// ---- 5: BOUNDARIES. Exactly at each minimum is legal; one tick below is not.
begin
base = n_log;
repeat (3) scl_period(T_LOW_MIN, T_PERIOD_MIN - T_LOW_MIN);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b0) begin
$display("FAIL: tLOW exactly at the minimum was rejected"); errors = errors + 1; end
if (vp_log[base + 1] !== 1'b0) begin
$display("FAIL: a period exactly at the minimum was rejected"); errors = errors + 1; end
end
begin
base = n_log;
repeat (3) scl_period(T_LOW_MIN - 1, T_PERIOD_MIN - T_LOW_MIN + 1);
scl_in = 1'b1; tick(20);
if (vl_log[base + 1] !== 1'b1) begin
$display("FAIL: tLOW one tick below the minimum was accepted"); errors = errors + 1; end
end
// ---- 6: the WORST CASE is tracked, not the last value. After a short phase has
// been seen, a long clean run must NOT erase it -- a report that quotes the
// most recent period hides every violation that is not the final one.
begin
repeat (6) scl_period(200, 120);
scl_in = 1'b1; tick(20);
if (min_low_seen > 16'd131) begin
$display("FAIL: min_low_seen = %0d after a short phase was seen earlier",
min_low_seen); errors = errors + 1; end
end
// ---- 7: a STRETCHED low phase is not a violation. A slave holding SCL low makes
// tLOW enormous, and tLOW has only a MINIMUM -- there is no maximum, which
// is exactly what makes clock stretching legal.
begin
bad_before = n_bad;
scl_period(3000, 90);
scl_period(160, 90);
scl_in = 1'b1; tick(20);
if (n_bad !== bad_before) begin
$display("FAIL: a 3000-tick stretched low phase was flagged as a violation");
errors = errors + 1; end
end
// ---- 8: an IDLE bus produces no periods at all. SCL parked high is not a
// zero-frequency clock; it is no clock, and reporting periods for it would
// manufacture measurements out of nothing.
begin
per_before = n_periods;
scl_in = 1'b1; tick(500);
if (n_periods !== per_before) begin
$display("FAIL: an idle bus produced %0d periods", n_periods - per_before);
errors = errors + 1; end
end
// ---- 9: the FIRST low phase after reset must not be measured, because it began
// before this block was watching. Measuring it would report a value that is
// an artefact of when reset was released rather than a property of the bus.
// Checked by construction: test 1's first reported period is index 0 and its
// low phase is a full 160 ticks, not the ragged interval since reset.
// Checked directly by the guard test in the preamble: a rise with no preceding fall
// produced no period at all, so no pre-reset interval was ever measured.
if (lo_log[0] === 16'hFFFF) begin
$display("FAIL: the first reported low phase is an uninitialised artefact"); errors = errors + 1; end
if (errors == 0)
$display("PASS: three independent constraints, a legal frequency can still be an illegal clock, stretching is not a violation, worst case tracked");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- The SCL CLOCK ENVELOPE. A legal I2C clock is not one constraint but THREE, and the three
-- are not redundant:
--
-- tLOW >= tLOW(min) the low phase carries the longer obligation
-- tHIGH >= tHIGH(min)
-- fSCL <= fSCL(max) equivalently, period >= 1/fSCL(max)
--
-- A clock can satisfy any two and fail the third, which is why this block reports three
-- separate verdicts. The case that surprises people is a clock at a perfectly legal FREQUENCY
-- whose duty cycle is illegal: 400 kHz at 75 % duty is 1.875 us high and 0.625 us LOW, and
-- Fast-mode's tLOW(min) is 1.3 us. Legal frequency, illegal clock.
--
-- The third constraint is stated as a PERIOD rather than a frequency, because that is how it
-- is checked and because of an identity in Table 10 worth knowing:
--
-- tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
--
-- exactly, in all three speed modes. Chapter 11.7 spends the edges and Chapter 11.9 closes
-- the whole budget.
--
-- PASSIVE: observes SCL and drives nothing.
entity i2c_clock_envelope_checker is
generic (
TICK_W : positive := 16;
-- Fast-mode, in ticks of a 100 MHz sample clock: 1.3 us, 0.6 us, 2.5 us.
T_LOW_MIN : natural := 130;
T_HIGH_MIN : natural := 60;
T_PERIOD_MIN : natural := 250
);
port (
clk : in std_logic;
rst_n : in std_logic;
scl_in : in std_logic;
period_valid : out std_logic;
t_low : out unsigned(TICK_W - 1 downto 0);
t_high : out unsigned(TICK_W - 1 downto 0);
t_period : out unsigned(TICK_W - 1 downto 0);
viol_low : out std_logic;
viol_high : out std_logic;
viol_period : out std_logic; -- equivalently: fSCL too fast
n_periods : out unsigned(TICK_W - 1 downto 0);
n_bad : out unsigned(TICK_W - 1 downto 0);
-- The WORST case seen, which is what a bring-up report should quote. An average hides
-- a single violating period, and a single violating period is a violation.
min_low_seen : out unsigned(TICK_W - 1 downto 0);
min_high_seen : out unsigned(TICK_W - 1 downto 0)
);
end entity;
architecture rtl of i2c_clock_envelope_checker is
signal scl_q : std_logic := '1';
signal scl_rise, scl_fall : std_logic;
-- A period is measured FALLING EDGE to FALLING EDGE, so one period contains exactly one
-- low phase followed by one high phase and the two sum to the period. Measuring the two
-- phases independently and adding them would take them from different periods whenever
-- the clock is irregular -- and an irregular clock is what this block exists to find.
signal phase_ticks, low_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
signal have_low : std_logic := '0';
-- There is deliberately NO "have we seen a falling edge yet" guard here, and its absence
-- was established by mutation testing rather than assumed. Such a guard looks necessary --
-- the low phase in progress when reset is released began before this block was watching --
-- but scl_q resets to '1', matching an idle bus, so a line that is LOW at reset release
-- registers a falling edge on the very first clock. have_low can therefore only ever be set
-- after a fall this block itself observed, no stimulus could distinguish the guard's
-- presence from its absence, and the honest response to an equivalent mutant is to delete
-- the dead code rather than contrive a test that appears to cover it.
-- Every measurement includes the sample period in which it is taken; reading the register
-- directly would under-report each interval by one tick and shift every comparison.
signal phase_now : unsigned(TICK_W - 1 downto 0);
begin
scl_rise <= (not scl_q) and scl_in;
scl_fall <= scl_q and (not scl_in);
phase_now <= phase_ticks + 1;
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
scl_q <= '1'; -- an idle bus has SCL released, therefore high
phase_ticks <= (others => '0');
low_ticks <= (others => '0');
have_low <= '0';
period_valid <= '0';
t_low <= (others => '0');
t_high <= (others => '0');
t_period <= (others => '0');
viol_low <= '0';
viol_high <= '0';
viol_period <= '0';
n_periods <= (others => '0');
n_bad <= (others => '0');
-- The running minima start at their MAXIMUM so the first real measurement
-- replaces them. Starting at zero would make every report read "0", which is
-- the classic way a worst-case tracker silently reports nothing.
min_low_seen <= (others => '1');
min_high_seen <= (others => '1');
else
scl_q <= scl_in;
period_valid <= '0';
if scl_fall = '1' then
-- The end of a high phase, and the end of a period if a low phase has
-- already been captured.
if have_low = '1' then
t_low <= low_ticks;
t_high <= phase_now;
t_period <= low_ticks + phase_now;
period_valid <= '1';
-- Three independent comparisons. Each is STRICTLY LESS THAN, so a
-- measurement exactly equal to a specified minimum passes.
if low_ticks < to_unsigned(T_LOW_MIN, TICK_W) then
viol_low <= '1'; else viol_low <= '0'; end if;
if phase_now < to_unsigned(T_HIGH_MIN, TICK_W) then
viol_high <= '1'; else viol_high <= '0'; end if;
if (low_ticks + phase_now) < to_unsigned(T_PERIOD_MIN, TICK_W) then
viol_period <= '1'; else viol_period <= '0'; end if;
n_periods <= n_periods + 1;
if low_ticks < to_unsigned(T_LOW_MIN, TICK_W)
or phase_now < to_unsigned(T_HIGH_MIN, TICK_W)
or (low_ticks + phase_now) < to_unsigned(T_PERIOD_MIN, TICK_W) then
n_bad <= n_bad + 1;
end if;
if low_ticks < min_low_seen then min_low_seen <= low_ticks; end if;
if phase_now < min_high_seen then min_high_seen <= phase_now; end if;
end if;
have_low <= '0';
phase_ticks <= (others => '0');
elsif scl_rise = '1' then
-- The end of a low phase.
low_ticks <= phase_now;
have_low <= '1';
phase_ticks <= (others => '0');
else
phase_ticks <= phase_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: one tick is 10 ns, so Fast-mode's envelope is tLOW 130 ticks, tHIGH
-- 60 ticks, period 250 ticks. Expected verdicts are derived from the generics rather than
-- written as constants, so the tests check the comparisons.
entity i2c_clock_envelope_checker_tb is
end entity;
architecture sim of i2c_clock_envelope_checker_tb is
constant TICK_W : positive := 16;
constant T_LOW_MIN : natural := 130;
constant T_HIGH_MIN : natural := 60;
constant T_PERIOD_MIN : natural := 250;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal scl_in : std_logic := '1';
signal period_valid, viol_low, viol_high, viol_period : std_logic;
signal t_low, t_high, t_period : unsigned(TICK_W - 1 downto 0);
signal n_periods, n_bad, min_low_seen, min_high_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 lo_log, hi_log, pe_log : tick_arr := (others => (others => '0'));
signal vl_log, vh_log, vp_log : bit_arr := (others => '0');
signal n_log : natural := 0;
signal test_done : std_logic := '0';
begin
dut : entity work.i2c_clock_envelope_checker
generic map (TICK_W => TICK_W, T_LOW_MIN => T_LOW_MIN,
T_HIGH_MIN => T_HIGH_MIN, T_PERIOD_MIN => T_PERIOD_MIN)
port map (clk => clk, rst_n => rst_n, scl_in => scl_in,
period_valid => period_valid, t_low => t_low, t_high => t_high,
t_period => t_period, viol_low => viol_low, viol_high => viol_high,
viol_period => viol_period, n_periods => n_periods, n_bad => n_bad,
min_low_seen => min_low_seen, min_high_seen => min_high_seen);
clk <= not clk after 5 ns;
watchdog : process
begin
wait for 2 ms;
if test_done = '0' then report "watchdog expired" severity failure; end if;
wait;
end process;
observe : process (clk)
begin
if rising_edge(clk) and rst_n = '1' and period_valid = '1' and n_log < 32 then
lo_log(n_log) <= t_low; hi_log(n_log) <= t_high; pe_log(n_log) <= t_period;
vl_log(n_log) <= viol_low; vh_log(n_log) <= viol_high; vp_log(n_log) <= viol_period;
n_log <= n_log + 1;
end if;
end process;
stim : process
variable errs : natural := 0;
variable base : natural;
variable bad_before, per_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;
-- One SCL period: low for `lo` ticks then high for `hi` ticks. The block measures
-- falling edge to falling edge, so this must be called back to back for the
-- measurement to line up with the intent.
procedure scl_period (lo, hi : in positive) is
begin
scl_in <= '0'; tick(lo);
scl_in <= '1'; tick(hi);
end procedure;
begin
tick(3);
-- The worst-case trackers must start at their MAXIMUM, not at zero.
if min_low_seen /= (min_low_seen'range => '1')
or min_high_seen /= (min_high_seen'range => '1') then
report "the worst-case trackers did not start at their maximum" severity error;
errs := errs + 1; end if;
-- Reset is released with SCL HIGH, matching an idle bus and the block's own reset value
-- for its SCL history register -- so no spurious edge is manufactured by the release
-- itself. Mutation testing established that this makes a "have we seen a fall yet" guard
-- unreachable, and the guard was deleted rather than left as dead code.
rst_n <= '1'; tick(2);
for i in 1 to 2 loop scl_period(160, 90); end loop;
scl_in <= '1'; tick(20);
bad_before := n_bad;
-- 1: a LEGAL Fast-mode clock with room to spare: 160 low, 90 high, period 250.
scl_in <= '1'; tick(20);
for i in 1 to 5 loop scl_period(160, 90); end loop;
scl_in <= '1'; tick(20);
if n_log < 4 then
report "too few periods reported from five" severity error; errs := errs + 1; end if;
if n_bad /= bad_before then
report "legal periods were flagged" severity error; errs := errs + 1; end if;
if lo_log(n_log-1) < to_unsigned(155, TICK_W)
or lo_log(n_log-1) > to_unsigned(165, TICK_W) then
report "a 160-tick low phase measured out of range" severity error;
errs := errs + 1; end if;
if hi_log(n_log-1) < to_unsigned(85, TICK_W)
or hi_log(n_log-1) > to_unsigned(95, TICK_W) then
report "a 90-tick high phase measured out of range" severity error;
errs := errs + 1; end if;
-- The period must be the SUM of the two phases actually measured, not a separately
-- timed interval that could drift from them.
if pe_log(n_log-1) /= lo_log(n_log-1) + hi_log(n_log-1) then
report "the period is not the sum of the measured low and high phases"
severity error; errs := errs + 1; end if;
-- 2: THE INTERESTING CASE. A legal PERIOD and a legal tHIGH with an ILLEGAL tLOW:
-- 250 ticks total, 100 low and 150 high -- a 60 % duty at exactly 400 kHz. The
-- frequency is compliant and the clock is not.
base := n_log;
for i in 1 to 3 loop scl_period(100, 150); end loop;
scl_in <= '1'; tick(20);
if vl_log(base + 1) /= '1' then
report "a 100-tick low phase was not flagged" severity error; errs := errs + 1; end if;
if vh_log(base + 1) /= '0' then
report "a 150-tick high phase was flagged" severity error; errs := errs + 1; end if;
if vp_log(base + 1) /= '0' then
report "a 250-tick period was flagged -- the frequency is LEGAL" severity error;
errs := errs + 1; end if;
-- 3: the mirror -- a legal period and tLOW with an ILLEGAL tHIGH.
base := n_log;
for i in 1 to 3 loop scl_period(210, 40); end loop;
scl_in <= '1'; tick(20);
if vh_log(base + 1) /= '1' then
report "a 40-tick high phase was not flagged" severity error; errs := errs + 1; end if;
if vl_log(base + 1) /= '0' or vp_log(base + 1) /= '0' then
report "an illegal tHIGH also flagged another constraint" severity error;
errs := errs + 1; end if;
-- 4: both phases LEGAL and the PERIOD too short. 130 + 60 = 190 < 250, so a clock at
-- exactly both phase minima violates fSCL. This proves the three constraints are
-- independent rather than two of them implying the third.
base := n_log;
for i in 1 to 3 loop scl_period(T_LOW_MIN, T_HIGH_MIN); end loop;
scl_in <= '1'; tick(20);
if vl_log(base + 1) /= '0' or vh_log(base + 1) /= '0' then
report "phases exactly at their minima were flagged" severity error;
errs := errs + 1; end if;
if vp_log(base + 1) /= '1' then
report "a 190-tick period was not flagged" severity error; errs := errs + 1; end if;
-- 5: BOUNDARIES. Exactly at each minimum is legal; one tick below is not.
base := n_log;
for i in 1 to 3 loop scl_period(T_LOW_MIN, T_PERIOD_MIN - T_LOW_MIN); end loop;
scl_in <= '1'; tick(20);
if vl_log(base + 1) /= '0' then
report "tLOW exactly at the minimum was rejected" severity error;
errs := errs + 1; end if;
if vp_log(base + 1) /= '0' then
report "a period exactly at the minimum was rejected" severity error;
errs := errs + 1; end if;
base := n_log;
for i in 1 to 3 loop scl_period(T_LOW_MIN - 1, T_PERIOD_MIN - T_LOW_MIN + 1); end loop;
scl_in <= '1'; tick(20);
if vl_log(base + 1) /= '1' then
report "tLOW one tick below the minimum was accepted" severity error;
errs := errs + 1; end if;
-- 6: the WORST CASE is tracked, not the last value. A long clean run must not erase a
-- short phase seen earlier -- a report quoting the most recent period hides every
-- violation that is not the final one.
for i in 1 to 6 loop scl_period(200, 120); end loop;
scl_in <= '1'; tick(20);
if min_low_seen > to_unsigned(131, TICK_W) then
report "min_low_seen was erased by a later clean run" severity error;
errs := errs + 1; end if;
-- 7: a STRETCHED low phase is not a violation. tLOW has only a MINIMUM -- there is no
-- maximum, which is exactly what makes clock stretching legal.
bad_before := n_bad;
scl_period(3000, 90);
scl_period(160, 90);
scl_in <= '1'; tick(20);
if n_bad /= bad_before then
report "a 3000-tick stretched low phase was flagged" severity error;
errs := errs + 1; end if;
-- 8: an IDLE bus produces no periods. SCL parked high is not a zero-frequency clock;
-- it is no clock, and reporting periods for it would manufacture measurements.
per_before := n_periods;
scl_in <= '1'; tick(500);
if n_periods /= per_before then
report "an idle bus produced periods" severity error; errs := errs + 1; end if;
-- 9: no pre-reset interval is ever measured. Guaranteed by construction rather than by
-- a guard: scl_q resets to '1' and reset is released with SCL high, so the first edge
-- this block can see is a fall.
if lo_log(0) = (lo_log(0)'range => '1') then
report "the first reported low phase is an uninitialised artefact" severity error;
errs := errs + 1; end if;
if errs = 0 then
report "i2c_clock_envelope_checker self-check complete: three independent "
& "constraints, a legal frequency can still be an illegal clock, stretching "
& "is not a violation, worst case tracked" severity note;
else
report "i2c_clock_envelope_checker self-check FAILED" severity error;
end if;
test_done <= '1';
wait;
end process;
end architecture;6a. Five Decisions Worth Defending
A period is measured falling-edge to falling-edge, so one period contains exactly one low phase followed by one high phase. The alternative — measuring the two phases independently and adding them — looks equivalent and is not: the two measurements would come from different periods whenever the clock is irregular, and an irregular clock is precisely what this block exists to find. §7's test 1 asserts t_period == t_low + t_high on the measured values, which is a check on the structure of the measurement rather than on the arithmetic.
Three verdicts, not one. §2 is the whole argument: a clock can fail any one of the three while passing the others, so collapsing them loses the diagnosis. Mutations B1 and B2 in §8 each disable one verdict and are killed by the case the other two cannot see.
The worst case is tracked, and it is initialised to its MAXIMUM. A minimum-tracker initialised to zero reports zero forever — it is the single most common way a worst-case register silently reports nothing, and §7 asserts the reset value explicitly. Mutation B3 injects it.
The worst case is a minimum, not the most recent value. A report that quotes the last period hides every violation that is not the final one, which on a bus that is intermittently marginal is all of them. Mutation B4 replaces the comparison with an assignment.
There is deliberately no "have we seen a falling edge yet" guard, and its absence was established rather than assumed. Such a guard looks necessary: the low phase in progress when reset is released began before the block was watching, so measuring it would report an artefact of when reset happened. But scl_q resets to 1, matching an idle bus, so a line that is low at reset release registers a falling edge on the very first clock — have_low can only ever be set after a fall this block itself observed.
The guard was unreachable. Mutation testing found it as an equivalent mutant, no stimulus could distinguish its presence from its absence, and the honest response to an equivalent mutant is to delete the dead code, not to contrive a test that appears to cover it. §8 records that as the module's one removed mutation.
6b. Verified Execution
$ iverilog -g2012 -o d2 i2c_clock_envelope_checker.sv i2c_clock_envelope_checker_tb.sv && ./d2
PASS: three independent constraints, a legal frequency can still be an illegal clock,
stretching is not a violation, worst case tracked
i2c_clock_envelope_checker_tb.sv:207: $finish called at 112850000 (1ps)
$ iverilog -g2005 -o v2 i2c_clock_envelope_checker.v i2c_clock_envelope_checker_tb.v && ./v2
PASS: three independent constraints, a legal frequency can still be an illegal clock,
stretching is not a violation, worst case tracked
i2c_clock_envelope_checker_tb.v:214: $finish called at 112850000 (1ps)
$ nvc -a i2c_clock_envelope_checker.vhd i2c_clock_envelope_checker_tb.vhd
$ nvc -e i2c_clock_envelope_checker_tb && nvc -r i2c_clock_envelope_checker_tb --stop-time=2500us
** Note: 112850ns+0: i2c_clock_envelope_checker self-check complete: three independent
constraints, a legal frequency can still be an illegal clock, stretching is not a violation,
worst case trackedAll three at 112850 ns.
7. What the Testbench Proves
| # | stimulus | what it establishes |
|---|---|---|
| 1 | 160 low, 90 high, period 250 | nothing flagged; and t_period is t_low + t_high |
| 2 | 100 low, 150 high, period 250 | the chapter's case: legal frequency, legal tHIGH, illegal tLOW |
| 3 | 210 low, 40 high, period 250 | the mirror — legal period and tLOW, illegal tHIGH |
| 4 | both phases exactly at their minima | both phase verdicts clean, period verdict fires |
| 5 | exactly at each minimum, then one tick below | both boundaries pinned |
| 6 | a clean run after a violation | the worst case is not erased |
| 7 | a 3000-tick stretched low phase | not a violation — tLOW has no maximum |
| 8 | 500 idle ticks | no periods reported at all |
| 9 | the first reported low phase | not a reset artefact |
Test 4 is the one that proves the three constraints are genuinely independent rather than two implying the third. With T_LOW_MIN + T_HIGH_MIN at 190 ticks and the period floor at 250, a clock sitting exactly on both phase minima still violates fSCL — so no pair of the three constraints entails the remaining one. Note this configuration is deliberately not a real speed mode: §4's identity means the table's own minima always sum to the period floor once the edges are included, so reaching this case requires a period floor set above the two phase minima. That is a legitimate configuration — it is what a design targeting a slower clock than its phase minima allow looks like — and it is the only way to exercise the constraint separately.
Test 7 checks the comparison has the right sense. tLOW is a minimum, so a 30 µs low phase must pass; a design that had accidentally written a maximum would flag every stretched transfer, which on a bus with an EEPROM is most of them.
Test 8 matters more than it looks. SCL parked high is not a zero-frequency clock — it is no clock, and a block that reported periods for it would manufacture measurements out of an idle bus. That is where the bus spends most of its life.
8. Mutation Testing
Six defects injected into the SystemVerilog checker, and one removed as provably equivalent.
| # | injected defect | outcome |
|---|---|---|
| B1 | tLOW is not checked | killed — the legal-frequency case of §2 |
| B2 | the period is checked against tLOW+tHIGH rather than its own minimum | killed — test 4 |
| B3 | the worst-case trackers start at zero | killed — the reset assertion in §7 |
| B4 | the worst case tracks the last value | killed — test 6 |
| B6 | the period is timed separately instead of being the sum of the two phases | killed — test 1's structural check |
| — | B5 removed: "the first low phase after reset is measured" | equivalent mutant — see below |
B1 is the mutation this chapter is about. Disabling the tLOW check leaves a checker that watches frequency and high time — exactly the naive checker of §2's callout — and it passes a 400 kHz clock with a 60 % duty. What kills it is test 2, which is the one stimulus where the two surviving checks are both satisfied and the clock is still illegal. Without §2's insight the test would not exist, and the mutation would survive.
B5 is the module's one removed mutation, and the reason is worth recording honestly. It disabled the started guard described in §6a, and it survived — because the guard was unreachable. scl_q resets to 1, so a low line at reset release produces a falling edge immediately; have_low can never be set by a rise that had no preceding fall. My first response was to write a test for it, which failed on the baseline: driving SCL low before releasing reset manufactures the very falling edge the test was trying to avoid.
That failure was the answer. The guard could not be exercised because it could not be reached, so it was dead code, and it was deleted. Three outcomes are possible when a mutant survives — a real test gap, a provably equivalent mutant, or something equivalent only in the cases you considered (Chapter 9.1 §7 sets them out) — and this was the middle one. Deleting the code is the correct response; a test written to cover unreachable logic would have been a test that proved nothing while appearing to.
9. Verification Connection — Duty Cycle Is a Coverage Problem
The three constraints are assertions; which duty cycles were ever tried is coverage. Both are needed, and for this chapter the coverage is the more interesting half — because a suite that only ever generated symmetric clocks has not tested the envelope at all.
// Three SEPARATE properties, for the reason section 2 gives: they fail independently, and a
// single combined property would report "the clock is illegal" without saying which of three
// very different faults occurred.
property p_tlow_min;
@(posedge clk) $rose(scl) |-> (low_ticks >= T_LOW_MIN);
endproperty
assert property (p_tlow_min)
else $error("tLOW violated: %0d ticks, minimum %0d", low_ticks, T_LOW_MIN);
property p_thigh_min;
@(posedge clk) $fell(scl) |-> (high_ticks >= T_HIGH_MIN);
endproperty
assert property (p_thigh_min)
else $error("tHIGH violated: %0d ticks, minimum %0d", high_ticks, T_HIGH_MIN);
property p_period_min;
@(posedge clk) $fell(scl) |-> ((low_ticks + high_ticks) >= T_PERIOD_MIN);
endproperty
assert property (p_period_min)
else $error("fSCL exceeded: period %0d ticks, minimum %0d",
low_ticks + high_ticks, T_PERIOD_MIN);
// And the NEGATIVE property, which is the one that stops a well-meaning change from
// breaking clock stretching. tLOW has no maximum; a timeout added here would flag every
// transfer to a device that stretches, and Chapter 10.3 section 8 makes the general case
// for asserting what must NOT happen wherever a wrong belief is reasonable.
property p_tlow_has_no_maximum;
@(posedge clk) (scl == 1'b0) |-> !timing_violation;
endproperty
assert property (p_tlow_has_no_maximum)
else $error("a long LOW phase was reported as a violation -- tLOW has no maximum"); covergroup i2c_duty_cg with function sample(int low, int high, int t_low_min,
int t_high_min, int period_min);
// Duty as a PERCENTAGE of the period, binned around the legal window rather than evenly.
// At Fast-mode the legal range is roughly 24 % to 48 %, so an evenly spread bin set would
// put most of its bins outside the region of interest and none on its edges.
duty: coverpoint ((high * 100) / (low + high)) {
bins way_low = {[0:20]};
bins at_min = {[21:27]}; // near tHIGH(min) as a share of the period
bins mid = {[28:44]};
bins at_max = {[45:52]}; // near the tLOW(min) limit -- the section 2 edge
bins illegal_hi = {[53:100]}; // a 50 % divider lands HERE at Fast-mode
}
// The three verdicts crossed with duty. This is the cross that would have caught the
// naive divider of section 2: it shows a cell where the frequency bin is legal, the
// tHIGH bin is legal, and the tLOW verdict is set.
vlow: coverpoint viol_low;
vhigh: coverpoint viol_high;
vper: coverpoint viol_period;
duty_x_vlow: cross duty, vlow;
// Stretched phases are a distinct regime and must appear, because a suite that never
// stretches has not shown that the minima are one-sided.
stretched: coverpoint (low > 10 * t_low_min);
endgroup10. FPGA and ASIC Implications
The checker is three counters and three compares — around 70 flops at TICK_W = 16. Nothing here constrains the clock.
Sizing TICK_W from the longest phase you intend to measure, not the shortest. Clock stretching makes tLOW unbounded, and a counter that saturates reports a small number — the worst way for a measurement to fail, because it silently turns a 30 µs stretch into an apparent violation. At 100 MHz, 16 bits is 655 µs, which covers any plausible stretch; 12 bits is 41 µs, which does not.
The generating side is where the real lesson lands. §2's callout is a statement about masters: an SCL generator needs two terminal counts, one per phase, not one count and a toggle at half. That is a handful of extra flops and it is the difference between a compliant Fast-mode clock and one that violates tLOW on every period. If you take one implementation note from this chapter, it is that.
And the identity of §4 sets the design target. Since the four terms exactly fill the period at the limits, a master targeting fSCL(max) must have a phase budget that beats the table somewhere. In practice the easiest term to beat is tr, by sizing the pull-up down — which costs static current and is Chapter 11.7's trade. The easiest term to give up is fSCL: running at 380 kHz instead of 400 buys 130 ns of period, which is more margin than any other single change.
11. Debugging — The 400 kHz Clock That Violated tLOW on Every Period
Pitfall — reclocking a Standard-mode divider for Fast-mode
// An SCL generator that had run at 100 kHz for two product generations. One terminal count,
// toggle at half:
//
// always @(posedge clk) begin
// if (cnt == HALF) begin scl <= ~scl; cnt <= 0; end
// else cnt <= cnt + 1;
// end
//
// At 100 kHz that gives 5 us high and 5 us low, which clears Standard-mode's 4.0 and 4.7
// comfortably. Correct, and correct for years.
//
// A new product needed 400 kHz. HALF was recomputed, the generator was otherwise untouched,
// and the result is 1.25 us each way:
//
// tHIGH = 1.25 us vs 0.6 us minimum -> fine, twice the requirement
// tLOW = 1.25 us vs 1.3 us minimum -> 50 ns SHORT, on EVERY period
// fSCL = 400 kHz vs 400 kHz maximum -> exactly at the limit, legal
//
// Two of the three constraints are comfortably met. The third is violated continuously.Most devices worked. One did not -- a temperature sensor that returned plausible but wrong values on perhaps one read in twenty, and only on some units.
Everything pointed at the sensor. Two of the three other devices on the same bus were flawless, which is a strong argument that the bus is fine and the part is not. Replacing the sensor with a unit from a different date code changed the failure rate, which is an even stronger one.
A logic-analyser capture showed a textbook 400 kHz clock with a 50 % duty and clean bytes. The frequency was measured and confirmed against the datasheet. The duty cycle was noted approvingly as "nice and symmetric".
What resolved it was reading Table 10 rather than the frequency row of it. tLOW(min) is 1.3 us at Fast-mode, and 1.25 is less than 1.3. A symmetric 400 kHz clock is not a legal Fast-mode clock -- tLOW(min) is 52 % of the period, so the duty cycle cannot exceed 48 %.
The devices that worked were tolerant of the short low phase. The sensor that failed used more of its tVD;DAT budget, so the 50 ns it lost mattered to it and not to the others -- which is exactly why the failure looked like a bad part.
The generator divided a period in half instead of sizing the two phases separately. That is legal in Standard-mode, where a 50 % duty at 100 kHz clears both minima, and illegal in Fast-mode and Fast-mode Plus, where tLOW(min) is more than half the period floor.
The deeper cause is treating "the clock is 400 kHz" as the compliance statement. Frequency is one of three constraints, and it is the one a 50 % divider gets right.
12. Common Misconceptions
"A 400 kHz clock is a legal Fast-mode clock." Only if its duty cycle is at most 48 %. tLOW(min) is 1.3 µs out of a 2.5 µs period floor, so more than half the period must be low.
"A 50 % duty cycle is the safe default." It is legal in Standard-mode and illegal at the maximum frequency in both Fast-mode and Fast-mode Plus. §11 is that mistake in the field.
"tLOW and tHIGH are two views of the frequency." They are independent constraints. §7's test 4 shows a clock meeting both phase minima and still violating fSCL.
"A long low phase is a violation." tLOW has no maximum — that is what makes clock stretching legal. A checker that added a timeout would flag every transfer to a device that stretches.
"The edges are a signal-integrity concern, separate from the clock." §4's identity puts them inside the period budget: tLOW + tHIGH + tr + tf exactly equals 1/fSCL at the limits, so a slow edge takes time from a phase that has a minimum.
"A worst-case register can start at zero." Then it reports zero forever. A minimum-tracker must start at its maximum, which §7 asserts explicitly and mutation B3 injects.
"An idle bus is a clock at 0 Hz." fSCL includes 0 in its range, but SCL parked high is no clock — there are no periods to measure, and reporting some would invent data.
13. Reason It Through
A master produces 1.25 µs high and 1.25 µs low. Which constraints does it meet, and what would a frequency-only checker say?
It meets tHIGH (1.25 ≥ 0.6, twice over) and fSCL (exactly 400 kHz, at the limit). It violates tLOW by 50 ns, on every period. A checker watching only frequency and high time passes it — which is mutation B1, and the reason §7's test 2 exists.
Why does tLOW(min) exceed tHIGH(min) in every speed mode?
Because the low phase is when SDA may change, so four things have to fit inside it — the hold across SCL's fall, the transmitter's response, the line's settling, and the receiver's setup — while the high phase has one job, being long enough to sample. In Fast-mode those four sum to 0.9 + 0.3 + 0.1 = 1.3 µs, which is tLOW(min) exactly.
Given §4's identity, is there any configuration that meets all three constraints with margin on all four terms?
No. At the limits the four terms exactly fill the period, so margin on one has to come from another. A real design beats the table somewhere: a smaller pull-up for tr, a stronger driver for tf, or — usually cheapest — a slightly slower clock, which buys margin on everything at once.
A checker's min_low_seen reads 0 after an hour of clean traffic. Two explanations — which is more likely?
Either a genuinely zero-length low phase occurred, which is impossible on a functioning bus, or the register was initialised to zero and has never been updated because no measurement was smaller. The second, overwhelmingly. A minimum-tracker initialised to zero reports zero forever, which is why §7 asserts the reset value and why mutation B3 is in the suite.
Why is a period measured falling-edge to falling-edge rather than by timing the two phases separately and adding them?
So that the low and high phases in one report come from the same period. Timed separately they can come from different periods, and on an irregular clock the sum is then a number that describes no actual period — while the irregular clock is exactly the fault being looked for. §7's test 1 asserts t_period == t_low + t_high on the measured values to pin the structure down.
14. Understanding Check
15. Summary
A legal clock is three constraints and a frequency is one of them. The low phase, the high phase and the period each have their own limit, and none implies the others — so a clock can satisfy any two and fail the third.
A symmetric clock at the maximum frequency is illegal in Fast-mode and Fast-mode Plus. tLOW(min) exceeds half the period floor in both, so the duty cycle cannot exceed 48 % at Fast-mode. Size the two phases separately from the table; never divide a period.
The low phase carries the longer obligation because it is where everything happens. Hold, transmitter response, settling and setup all fit inside it, and in Fast-mode they sum to exactly tLOW(min).
tLOW(min) + tHIGH(min) + tr(max) + tf(max) = 1/fSCL(max), exactly, in all three modes. The period budget is fully allocated, so every real design beats the table somewhere — and the edges are part of the clock budget rather than a separate electrical concern.
The minima are one-sided. Waiting longer is always compliant, which is what makes clock stretching legal and what a checker must not accidentally forbid.
A worst-case tracker starts at its maximum and keeps the extreme, not the latest. Initialised to zero it reports zero forever; keeping the most recent value hides every violation but the last.
16. What Comes Next
Chapter 11.3 moves from the clock to the data on it: tSU;DAT and tHD;DAT, the two parameters every bit of every byte must satisfy. They are measured against different edges — setup before the rise, hold after the fall — and that asymmetry is forced rather than conventional, for the reason Chapter 11.1 §6a gives about which end of an interval its reference edge sits at.
The chapter also resolves something in Table 10 that reads as a contradiction: tHD;DAT(min) is zero for I²C devices, and yet footnote [3] requires a device to provide 300 ns internally. Both are true, they are about different things, and the difference explains why an apparently unconstrained parameter is in practice one of the tightest.
Continue learning
Related tutorials
- Related topic
tVD;DAT and tVD;ACK — I²C Data Valid Time
The module's first maximum, and it inverts everything: the comparison, the worst-case tracker, and what a passing measurement means. Plus why the acknowledge gets its own parameter when Table 10 gives it an identical number.
- Related topic
tSU;STA and tHD;STA — START and Repeated-START Margins
The first parameters measured between two different signals rather than against a clock edge — and the timing-level reason a repeated START is not simply a START in the middle of a transfer.
- Related topic
Bus Feedback — Clock Stretching and Arbitration From One Comparison
Clock stretching and arbitration loss are not two features. They are one comparison — a line this master released that reads back low — applied to two wires, differing only in a timing qualifier and an intent gate. Builds both from a single comparator and shows why a master can only ever lose by trying to send a one.
- Related topic
Address Shift Register, Address Match and Direction Decode
Three jobs that fail separately: receiving eight bits, deciding whether they name this device, and deciding whether to answer. A design with one state called address cannot tell you which of them broke — and the direction bit governs everything after the byte has left the wire.
