I²C · Module 14
Standard, Fast and Fast-Mode Plus — The Mainstream Modes
Three modes, one protocol, four numbers. Derives the Table 10 budget identity that makes those four numbers a single constraint, shows why a faster edge can push a clock out of specification, and explains why Fast-mode compliance is about synchronizing and stretching rather than throughput.
Thirteen modules have been silent about speed. Every rule so far — the wired-AND, the acknowledge, stretching, synchronization, arbitration — reads identically at 100 kHz and at 1 MHz, and that is not an accident of presentation. The protocol genuinely does not change.
So this module has a narrow question to answer: what actually differs between Standard-mode, Fast-mode and Fast-mode Plus?
The answer is four numbers in Table 10, and one number in Table 9. Nothing else. No new conditions, no new byte formats, no new addresses, no new states.
That sounds like an anticlimax. It is the opposite. Those four numbers turn out not to be four independent limits but one budget, and once you see that, three things that look like separate facts collapse into consequences of it — including the reason a faster edge can make a clock illegal, and the reason Chapter 11.7's pull-up window came out empty at the table's own limits.
1. Five Categories, Two Buses
Read the shape of that list rather than the numbers. It is not a ladder of five rungs; it is four rungs and a separate thing.
The four bidirectional modes are one family. A device from any of them can be operated at any lower rate, which is what "downward-compatible" means, and the reason it is possible at all is that they share the open-drain electrical contract of Chapter 2.5. Ultra Fast-mode is not on that ladder. It is push-pull and unidirectional, and the specification says so in the same breath as the compatibility claim — because the compatibility claim does not extend to it. Chapter 14.3 is about what that costs.
This chapter takes the first three rungs. Chapter 14.2 takes the fourth, which is the only one that changes the protocol rather than the numbers.
2. What Is Identical
Worth stating explicitly, because the list is longer than the list of differences.
So across all three modes, unchanged:
| unchanged | established in |
|---|---|
| START and STOP conditions | Chapter 5.2 |
| the 9-bit byte frame and the acknowledge | Chapter 7.2 |
| 7-bit and 10-bit addressing | Chapter 6.1 |
| the reserved addresses | Chapter 6.3 |
| clock stretching | Module 12 |
| clock synchronization and arbitration | Module 13 |
VIL = 0.3 VDD, VIH = 0.7 VDD | Chapter 2.6 |
VnL = 0.1 VDD, VnH = 0.2 VDD noise margins | Chapter 11.7 |
Cb maximum of 400 pF (Standard and Fast; 550 pF in Fm+) | Chapter 11.7 |
A Standard-mode transaction capture and a Fast-mode one are the same picture at different scales. That is the whole reason the previous eleven modules could be written without mentioning speed.
3. What Differs — Four Numbers
And one number that is not in Table 10 at all, which turns out to matter more than any of them:
Note what that means. Standard-mode and Fast-mode have the same output stage. Fast-mode is not a stronger driver; it is the same driver asked to settle faster. Fm+ is where the silicon actually changes, and Chapter 14.4 shows that the 20 mA, not the timing, is what buys Fm+ its extra capacitance.
4. The Budget Identity
Here is the result this module is built on. Chapter 11.9 derived it for the three modes one at a time; stated together it is more striking.
For every one of the three modes,
tLOW(min) +tHIGH(min) +tr(max) +tf(max)= 1 /fSCL(max)— exactly.
| mode | tLOW | tHIGH | tr | tf | sum | 1/fSCL(max) | slack |
|---|---|---|---|---|---|---|---|
| Standard | 4700 | 4000 | 1000 | 300 | 10000 ns | 10000 ns | 0 |
| Fast | 1300 | 600 | 300 | 300 | 2500 ns | 2500 ns | 0 |
| Fm+ | 500 | 260 | 120 | 120 | 1000 ns | 1000 ns | 0 |
Three modes, three exact hits. That is not a coincidence and it is not rounding: it is how the table was constructed. The four numbers are one constraint expressed four ways, and the specification states the relationship itself in §7.2.1:
Equation 3 is normally read as a way to derate a slow bus. Read it the other way and it is the definition of the headline rate: put each mode's own minima and maxima in, and the mode's own advertised frequency comes out. The design in §7 does exactly that and the check is in the transcript.
4a. The Consequence Nobody Expects
If the four limits were independent, a bit cell would be legal whenever each one was individually satisfied. Because they are one budget, that is false — and false in a direction that catches people out.
Take a Fast-mode cell sitting exactly on both phase minima, tLOW = 1300 ns and tHIGH = 600 ns, with a tf of 300 ns. Now make the rising edge faster: 100 ns instead of 300 ns. Every individual parameter is comfortably legal — 100 ns is inside Table 10's 20…300 ns window for tr, and neither phase moved.
The period is now 1300 + 100 + 600 + 300 = 2300 ns. That is 435 kHz. The clock is over the Fast-mode limit, and the only thing that changed was an edge getting better.
A quicker edge is not a free win. It is period you have to spend somewhere, and if you do not spend it on a phase you have overclocked the bus.
This is not a contrived case. It is what happens when someone strengthens a pull-up to fix a marginal rise time without revisiting the clock divider — the edge problem goes away and a frequency violation appears in its place. Test 4 in §7 is that exact cell, and it is the reason the monitor reports a period violation with every other flag clear.
5. "Fast-Mode Device" Does Not Mean "Runs At 400 kbit/s"
The definition is one sentence and it is not the one most engineers would write.
So the compliance bar for a Fast-mode device is not throughput. It is:
- Can synchronize with a 400 kbit/s clock — the mechanism of Chapter 13.2.
- Can stretch to slow the transfer down — the mechanism of Module 12.
A device that can only manage 150 kbit/s of actual work is a conforming Fast-mode device provided it can track a 400 kbit/s clock's edges and hold SCL low when it needs time. Both of those are capabilities the previous two modules built, which is why they had to come first.
Speed conformance is defined in terms of keeping up with the edges and being able to ask for more time — not in terms of the rate you achieve.
That is a much more useful definition for a real system, because it makes the fast/slow mixture on a real board a matter of negotiation rather than of exclusion. And it explains why the two capabilities are mandatory in Table 2 while raw speed is nowhere in it.
6. Downward Compatible, Not Upward Compatible
The specification is unusually blunt here, and the asymmetry is worth taking seriously.
"Unpredictable states" is strong language for a document this careful. It is also exactly what the budget identity predicts, and §7's monitor demonstrates it in both directions on the same two waveforms:
A legal Standard-mode cell, judged as Fast-mode. tLOW 4700 is fine, tHIGH 4000 is fine, the period is four times longer than required. It fails on one parameter: the 1000 ns rise time is 3.3 times the Fast-mode allowance of 300 ns. That single number is the whole of the incompatibility, and it is an electrical number, not a protocol one.
A legal Fast-mode cell, judged as Standard-mode. Three failures at once: tLOW too short, tHIGH too short, and the period too fast. A Standard-mode part asked to receive this simply does not see a valid clock.
| the cell | judged as Standard | judged as Fast |
|---|---|---|
| 4700 / 1000 / 4000 / 300 | legal | fails on tr only |
| 1300 / 300 / 600 / 300 | fails tLOW, tHIGH, period | legal |
The asymmetry is therefore not a matter of politeness. Going down means one device's edges are slower than another needs — recoverable, because slower edges on a shared bus are simply the bus's edges. Going up means a device is being asked to resolve phases shorter than its input path can register, and nothing on the bus tells it that is happening.
7. One Bit Cell, Two Verdicts
A legal Standard-mode bit cell, and the one parameter that makes it illegal in Fast-mode
10 cyclesOne waveform, two verdicts, and the difference is a single interval. Everything else about the cell — the phases, the frame, the data on SDA that is not drawn — is identical and irrelevant to the outcome.
8. The Envelope Monitor in Three Languages
The design measures one SCL bit cell the way Table 10 defines it and reports which constraint is tightest, not merely whether the cell passed. That choice is the point: on a bus that is failing intermittently, knowing that the binding constraint is the rise time rather than the low phase is the difference between changing a resistor and changing a clock divider.
The line arrives as two comparator outputs rather than one logic level, because a digital level cannot express an edge rate. th30 is high when the line is above 0.3 VDD, th70 when it is above 0.7 VDD, and the four intervals fall out of the crossings exactly as the specification measures them.
// -----------------------------------------------------------------------------
// i2c_mode_envelope.sv
// Per-mode SCL timing-envelope monitor for Standard-mode, Fast-mode and
// Fast-mode Plus.
//
// Measures one SCL bit cell the way UM10204 defines it -- between the 30 % and
// 70 % threshold crossings -- and checks it against the Table 10 limits of the
// selected mode:
//
// tLOW 30 % falling -> 30 % rising must be >= tLOW(min)
// tr 30 % rising -> 70 % rising must be <= tr(max), >= tr(min)
// tHIGH 70 % rising -> 70 % falling must be >= tHIGH(min)
// tf 70 % falling -> 30 % falling must be <= tf(max)
// period tLOW+tr+tHIGH+tf must be >= 1/fSCL(max)
//
// The period check is the interesting one. In all three modes Table 10 is
// internally exact:
// tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
// so the four limits are not independent -- they are one budget. A cell can
// satisfy every individual minimum and still be illegal because the edges ate
// the slack. This monitor reports which of the five constraints is the tightest
// (`binding`), not merely whether the cell passed.
//
// tf(min) is deliberately NOT checked: Table 10 specifies it as
// 20 x (VDD / 5.5 V) ns, a supply-dependent value this monitor is not given.
// tr(min) IS checked -- Table 10 states it as a flat 20 ns for Fast-mode and
// leaves it unspecified ("-") for Standard-mode and Fast-mode Plus.
//
// The line is presented as two comparator outputs rather than one logic level,
// because a digital level cannot express an edge rate.
// -----------------------------------------------------------------------------
module i2c_mode_envelope #(
parameter int TICK_NS = 10, // ns per clk; every Table 10 value is a multiple of 10
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic [1:0] mode, // 0 = Standard, 1 = Fast, 2 = Fast-mode Plus
input logic th30, // 1 when the line is above 30 % of VDD
input logic th70, // 1 when the line is above 70 % of VDD
output logic [CNT_W-1:0] meas_tlow, // ns
output logic [CNT_W-1:0] meas_thigh, // ns
output logic [CNT_W-1:0] meas_tr, // ns
output logic [CNT_W-1:0] meas_tf, // ns
output logic [CNT_W-1:0] meas_period, // ns, = tlow + tr + thigh + tf
output logic cell_valid, // one pulse per completed bit cell
output logic legal, // that cell violated nothing
output logic [2:0] binding, // tightest constraint, see BIND_* below
output logic signed [CNT_W:0] worst_margin, // ns of slack on `binding` (negative = violated)
output logic viol_tlow,
output logic viol_thigh,
output logic viol_tr,
output logic viol_tf,
output logic viol_period,
output logic [CNT_W-1:0] cells_seen,
output logic [CNT_W-1:0] cells_legal
);
localparam [1:0] MODE_STD = 2'd0;
localparam [1:0] MODE_FAST = 2'd1;
localparam [1:0] MODE_FMP = 2'd2;
localparam [2:0] BIND_NONE = 3'd0;
localparam [2:0] BIND_TLOW = 3'd1;
localparam [2:0] BIND_THIGH = 3'd2;
localparam [2:0] BIND_TR = 3'd3;
localparam [2:0] BIND_TF = 3'd4;
localparam [2:0] BIND_PERIOD = 3'd5;
// ---------------------------------------------------------------------
// Table 10 limits, in ns. Standard / Fast / Fast-mode Plus.
// ---------------------------------------------------------------------
function [CNT_W-1:0] lim_tlow_min (input [1:0] m);
case (m)
MODE_STD : lim_tlow_min = 16'd4700;
MODE_FAST: lim_tlow_min = 16'd1300;
default : lim_tlow_min = 16'd500;
endcase
endfunction
function [CNT_W-1:0] lim_thigh_min (input [1:0] m);
case (m)
MODE_STD : lim_thigh_min = 16'd4000;
MODE_FAST: lim_thigh_min = 16'd600;
default : lim_thigh_min = 16'd260;
endcase
endfunction
function [CNT_W-1:0] lim_tr_max (input [1:0] m);
case (m)
MODE_STD : lim_tr_max = 16'd1000;
MODE_FAST: lim_tr_max = 16'd300;
default : lim_tr_max = 16'd120;
endcase
endfunction
// Table 10 gives tr(min) only for Fast-mode. "-" elsewhere means no limit.
function [CNT_W-1:0] lim_tr_min (input [1:0] m);
case (m)
MODE_FAST: lim_tr_min = 16'd20;
default : lim_tr_min = 16'd0;
endcase
endfunction
function [CNT_W-1:0] lim_tf_max (input [1:0] m);
case (m)
MODE_STD : lim_tf_max = 16'd300;
MODE_FAST: lim_tf_max = 16'd300;
default : lim_tf_max = 16'd120;
endcase
endfunction
// 1 / fSCL(max): 100 kHz, 400 kHz, 1000 kHz.
function [CNT_W-1:0] lim_period_min (input [1:0] m);
case (m)
MODE_STD : lim_period_min = 16'd10000;
MODE_FAST: lim_period_min = 16'd2500;
default : lim_period_min = 16'd1000;
endcase
endfunction
// ---------------------------------------------------------------------
// Threshold-crossing edge detection
// ---------------------------------------------------------------------
logic th30_q, th70_q;
wire th30_rise = th30 && !th30_q;
wire th30_fall = !th30 && th30_q;
wire th70_rise = th70 && !th70_q;
wire th70_fall = !th70 && th70_q;
// ---------------------------------------------------------------------
// Measurement FSM. The bit cell boundary is the 30 % falling edge.
// ---------------------------------------------------------------------
localparam [2:0] S_SYNC = 3'd0, // wait for the first 30 % falling edge
S_LOW = 3'd1,
S_RISE = 3'd2,
S_HIGH = 3'd3,
S_FALL = 3'd4;
logic [2:0] state;
logic [CNT_W-1:0] cnt;
logic [CNT_W-1:0] acc_tlow, acc_tr, acc_thigh;
localparam [CNT_W-1:0] TICK = TICK_NS;
// ---------------------------------------------------------------------
// Evaluation of a completed cell. Margins are signed: negative = violated.
// ---------------------------------------------------------------------
logic signed [CNT_W:0] m_tlow, m_thigh, m_tr, m_tf, m_period;
logic signed [CNT_W:0] best;
logic [2:0] best_id;
logic cell_ok;
logic signed [CNT_W:0] m_tr_hi, m_tr_lo;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
th30_q <= 1'b1;
th70_q <= 1'b1;
state <= S_SYNC;
cnt <= {CNT_W{1'b0}};
acc_tlow <= {CNT_W{1'b0}};
acc_tr <= {CNT_W{1'b0}};
acc_thigh <= {CNT_W{1'b0}};
meas_tlow <= {CNT_W{1'b0}};
meas_thigh <= {CNT_W{1'b0}};
meas_tr <= {CNT_W{1'b0}};
meas_tf <= {CNT_W{1'b0}};
meas_period <= {CNT_W{1'b0}};
cell_valid <= 1'b0;
legal <= 1'b0;
binding <= BIND_NONE;
worst_margin <= {(CNT_W+1){1'b0}};
viol_tlow <= 1'b0;
viol_thigh <= 1'b0;
viol_tr <= 1'b0;
viol_tf <= 1'b0;
viol_period <= 1'b0;
cells_seen <= {CNT_W{1'b0}};
cells_legal <= {CNT_W{1'b0}};
end else begin
th30_q <= th30;
th70_q <= th70;
cell_valid <= 1'b0;
case (state)
// Do not measure a partial cell: wait for a clean cell boundary.
S_SYNC: begin
if (th30_fall) begin
state <= S_LOW;
cnt <= {CNT_W{1'b0}};
end
end
S_LOW: begin
if (th30_rise) begin
acc_tlow <= cnt + TICK; // the crossing tick belongs to the phase
cnt <= {CNT_W{1'b0}};
state <= S_RISE;
end else begin
cnt <= cnt + TICK;
end
end
S_RISE: begin
if (th70_rise) begin
acc_tr <= cnt + TICK;
cnt <= {CNT_W{1'b0}};
state <= S_HIGH;
end else if (th30_fall) begin
// Never reached 70 %: an aborted edge. Resynchronise.
state <= S_LOW;
cnt <= {CNT_W{1'b0}};
end else begin
cnt <= cnt + TICK;
end
end
S_HIGH: begin
if (th70_fall) begin
acc_thigh <= cnt + TICK;
cnt <= {CNT_W{1'b0}};
state <= S_FALL;
end else begin
cnt <= cnt + TICK;
end
end
S_FALL: begin
if (th30_fall) begin
meas_tlow <= acc_tlow;
meas_thigh <= acc_thigh;
meas_tr <= acc_tr;
meas_tf <= cnt + TICK;
meas_period <= acc_tlow + acc_tr + acc_thigh + cnt + TICK;
cell_valid <= 1'b1;
cells_seen <= cells_seen + 1'b1;
// ---- verdict for the cell that just completed ----
m_tlow = $signed({1'b0, acc_tlow}) - $signed({1'b0, lim_tlow_min(mode)});
m_thigh = $signed({1'b0, acc_thigh}) - $signed({1'b0, lim_thigh_min(mode)});
// tr is bounded on BOTH sides in Fast-mode (20 ns .. 300 ns), so its
// margin is the tighter of the two. Where Table 10 gives no minimum
// (Standard, Fm+) lim_tr_min is 0 and the upper bound always wins.
m_tr_hi = $signed({1'b0, lim_tr_max(mode)}) - $signed({1'b0, acc_tr});
m_tr_lo = $signed({1'b0, acc_tr}) - $signed({1'b0, lim_tr_min(mode)});
m_tr = (lim_tr_min(mode) != 0 && m_tr_lo < m_tr_hi) ? m_tr_lo : m_tr_hi;
m_tf = $signed({1'b0, lim_tf_max(mode)}) - $signed({1'b0, cnt + TICK});
m_period = $signed({1'b0, acc_tlow + acc_tr + acc_thigh + cnt + TICK})
- $signed({1'b0, lim_period_min(mode)});
viol_tlow <= (m_tlow < 0);
viol_thigh <= (m_thigh < 0);
viol_tr <= (m_tr < 0);
viol_tf <= (m_tf < 0);
viol_period <= (m_period < 0);
cell_ok = !(m_tlow < 0) && !(m_thigh < 0) && !(m_tr < 0)
&& !(m_tf < 0) && !(m_period < 0);
legal <= cell_ok;
if (cell_ok) cells_legal <= cells_legal + 1'b1;
// Tightest constraint. Ties resolve in table order, so a cell
// sitting exactly on two limits reports the earlier one.
best = m_tlow; best_id = BIND_TLOW;
if (m_thigh < best) begin best = m_thigh; best_id = BIND_THIGH; end
if (m_tr < best) begin best = m_tr; best_id = BIND_TR; end
if (m_tf < best) begin best = m_tf; best_id = BIND_TF; end
if (m_period < best) begin best = m_period; best_id = BIND_PERIOD; end
worst_margin <= best;
binding <= best_id;
cnt <= {CNT_W{1'b0}};
state <= S_LOW; // next cell starts at this same edge
end else if (th70_rise) begin
// The line came back up without crossing 30 %: not a cell end.
cnt <= cnt + TICK;
state <= S_HIGH;
end else begin
cnt <= cnt + TICK;
end
end
default: state <= S_SYNC;
endcase
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_mode_envelope_tb.sv
// Independent oracle for i2c_mode_envelope.
//
// The bench drives threshold-crossing pairs that describe a bit cell of a known
// shape, then checks the DUT's measurement AND its verdict against limits the
// bench holds separately. The bench never reads the DUT's own limit functions.
//
// The point of the suite is the budget identity of Table 10:
// tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
// Tests 1-3 show it holds exactly in all three modes. Test 4 shows the
// consequence that surprises people: making an edge FASTER while the phases sit
// at their minima produces an ILLEGAL clock, because the period floor is a
// floor, not a ceiling.
// -----------------------------------------------------------------------------
module i2c_mode_envelope_tb;
localparam integer TICK_NS = 10;
localparam [1:0] M_STD = 2'd0;
localparam [1:0] M_FAST = 2'd1;
localparam [1:0] M_FMP = 2'd2;
localparam [2:0] BIND_NONE = 3'd0, BIND_TLOW = 3'd1, BIND_THIGH = 3'd2,
BIND_TR = 3'd3, BIND_TF = 3'd4, BIND_PERIOD = 3'd5;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic [1:0] mode = M_FAST;
logic th30 = 1'b1;
logic th70 = 1'b1;
logic [15:0] meas_tlow, meas_thigh, meas_tr, meas_tf, meas_period;
logic [15:0] cells_seen, cells_legal;
logic cell_valid, legal;
logic [2:0] binding;
logic signed [16:0] worst_margin;
logic viol_tlow, viol_thigh, viol_tr, viol_tf, viol_period;
integer errors = 0;
integer k; // used by hold() only
integer n; // outer loops -- must not be k, hold() clobbers it
i2c_mode_envelope #(.TICK_NS(TICK_NS), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n), .mode(mode), .th30(th30), .th70(th70),
.meas_tlow(meas_tlow), .meas_thigh(meas_thigh), .meas_tr(meas_tr),
.meas_tf(meas_tf), .meas_period(meas_period),
.cell_valid(cell_valid), .legal(legal), .binding(binding),
.worst_margin(worst_margin),
.viol_tlow(viol_tlow), .viol_thigh(viol_thigh), .viol_tr(viol_tr),
.viol_tf(viol_tf), .viol_period(viol_period),
.cells_seen(cells_seen), .cells_legal(cells_legal));
always #(TICK_NS/2) clk = ~clk;
// -------------------------------------------------------------------
// Stimulus. Levels change on the negedge so the DUT never has to resolve
// a transition that lands on its own sampling edge.
// -------------------------------------------------------------------
task hold (input integer ns_, input integer b30, input integer b70);
begin
@(negedge clk);
th30 = b30[0];
th70 = b70[0];
for (k = 0; k < ns_/TICK_NS; k = k + 1) @(posedge clk);
end
endtask
// One bit cell: LOW, then the rising edge, then HIGH, then the falling edge.
// The cell is reported by the DUT at the START of the next LOW phase.
task emit_cell (input integer tlow_, input integer tr_,
input integer thigh_, input integer tf_);
begin
hold(tlow_, 0, 0);
hold(tr_, 1, 0);
hold(thigh_, 1, 1);
hold(tf_, 1, 0);
end
endtask
task flush; // a trailing LOW so the last cell is reported
begin hold(200, 0, 0); end
endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; th30 = 1'b1; th70 = 1'b1;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
@(posedge clk);
end
endtask
// -------------------------------------------------------------------
// Checks
// -------------------------------------------------------------------
task ck_int (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input got, input exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0b expected %0b", what, got, exp);
errors = errors + 1;
end
end
endtask
// Full expectation for one measured cell.
task expect_cell (input [200*8:1] tag,
input integer e_tlow, input integer e_tr,
input integer e_thigh, input integer e_tf,
input e_legal,
input e_vlow, input e_vhigh, input e_vtr, input e_vtf, input e_vper,
input [2:0] e_bind, input integer e_margin);
begin
$display("%0s", tag);
ck_int("meas_tlow", meas_tlow, e_tlow);
ck_int("meas_tr", meas_tr, e_tr);
ck_int("meas_thigh", meas_thigh, e_thigh);
ck_int("meas_tf", meas_tf, e_tf);
ck_int("meas_period", meas_period, e_tlow + e_tr + e_thigh + e_tf);
ck_bit("legal", legal, e_legal);
ck_bit("viol_tlow", viol_tlow, e_vlow);
ck_bit("viol_thigh", viol_thigh, e_vhigh);
ck_bit("viol_tr", viol_tr, e_vtr);
ck_bit("viol_tf", viol_tf, e_vtf);
ck_bit("viol_period", viol_period, e_vper);
ck_int("binding", binding, e_bind);
ck_int("worst_margin", worst_margin, e_margin);
end
endtask
initial begin
$display("=== i2c_mode_envelope: Table 10 envelope checks ===");
// ----------------------------------------------------------------
// 1. Standard-mode exactly on the budget.
// 4700 + 1000 + 4000 + 300 = 10000 ns = 1/100 kHz.
// Every one of the five margins is zero at once.
// ----------------------------------------------------------------
do_reset;
mode = M_STD;
emit_cell(4700, 1000, 4000, 300);
flush;
expect_cell("T1 Standard-mode, all five limits met exactly",
4700, 1000, 4000, 300, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
ck_int("T1 cells_seen", cells_seen, 1);
ck_int("T1 cells_legal", cells_legal, 1);
// ----------------------------------------------------------------
// 2. Fast-mode exactly on the budget. 1300+300+600+300 = 2500.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 300, 600, 300);
flush;
expect_cell("T2 Fast-mode, all five limits met exactly",
1300, 300, 600, 300, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
// ----------------------------------------------------------------
// 3. Fast-mode Plus exactly on the budget. 500+120+260+120 = 1000.
// ----------------------------------------------------------------
do_reset;
mode = M_FMP;
emit_cell(500, 120, 260, 120);
flush;
expect_cell("T3 Fast-mode Plus, all five limits met exactly",
500, 120, 260, 120, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
// ----------------------------------------------------------------
// 4. THE CONSEQUENCE OF THE IDENTITY.
// Fast-mode, phases at their minima, but a FASTER rise (100 ns, well
// inside the 20..300 ns window). Every individual parameter is legal;
// the cell is illegal because the period fell to 2300 ns, i.e. the
// clock is running at 435 kHz. A quicker edge is not a free win.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 100, 600, 300);
flush;
expect_cell("T4 Fast-mode: every parameter legal, period floor broken",
1300, 100, 600, 300, 1'b0,
1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -200);
// ----------------------------------------------------------------
// 5. tLOW below its minimum, in isolation. tHIGH is stretched so the
// period floor is still met and only one flag can fire.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1200, 300, 1300, 300);
flush;
expect_cell("T5 Fast-mode: tLOW 100 ns short, nothing else",
1200, 300, 1300, 300, 1'b0,
1'b1,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, -100);
// ----------------------------------------------------------------
// 6. tHIGH below its minimum, in isolation.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(2000, 300, 500, 300);
flush;
expect_cell("T6 Fast-mode: tHIGH 100 ns short, nothing else",
2000, 300, 500, 300, 1'b0,
1'b0,1'b1,1'b0,1'b0,1'b0, BIND_THIGH, -100);
// ----------------------------------------------------------------
// 7. Rise time above tr(max).
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 400, 600, 300);
flush;
expect_cell("T7 Fast-mode: tr 100 ns too slow",
1300, 400, 600, 300, 1'b0,
1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -100);
// ----------------------------------------------------------------
// 8. Rise time below tr(min). Table 10 gives Fast-mode a 20 ns FLOOR on
// the rise time; an edge can be too fast as well as too slow. The
// margin is two-sided, so this ranks as the binding constraint even
// though the upper bound has 290 ns of room.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(2000, 10, 600, 300);
flush;
expect_cell("T8 Fast-mode: tr 10 ns, below the 20 ns floor",
2000, 10, 600, 300, 1'b0,
1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -10);
// ----------------------------------------------------------------
// 9. Fall time above tf(max).
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 300, 600, 400);
flush;
expect_cell("T9 Fast-mode: tf 100 ns too slow",
1300, 300, 600, 400, 1'b0,
1'b0,1'b0,1'b0,1'b1,1'b0, BIND_TF, -100);
// ----------------------------------------------------------------
// 10. "Downward-compatible, not upward-compatible" (UM10204 5.1),
// measured rather than asserted.
// (a) A perfectly legal STANDARD-mode cell, judged as Fast-mode:
// illegal on the rise time alone. Its 1000 ns edge is 3.3x the
// Fast-mode allowance.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(4700, 1000, 4000, 300);
flush;
expect_cell("T10a legal Standard cell judged as Fast: fails on tr only",
4700, 1000, 4000, 300, 1'b0,
1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -700);
// (b) A perfectly legal FAST-mode cell, judged as Standard-mode:
// both phases too short AND the clock too fast. Three flags.
do_reset;
mode = M_STD;
emit_cell(1300, 300, 600, 300);
flush;
expect_cell("T10b legal Fast cell judged as Standard: three violations",
1300, 300, 600, 300, 1'b0,
1'b1,1'b1,1'b0,1'b0,1'b1, BIND_PERIOD, -7500);
// ----------------------------------------------------------------
// 11. An aborted rising edge. The line clears 30 % but sags back below
// it without ever reaching 70 %. That is not a bit cell and must not
// be measured as one; the monitor resynchronises and reports exactly
// one cell -- the good one that follows.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
hold(1300, 0, 0); // LOW
hold(200, 1, 0); // above 30 %, never reaches 70 %
hold(1300, 0, 0); // back down: aborted
hold(300, 1, 0); // now a real edge
hold(600, 1, 1);
hold(300, 1, 0);
flush;
$display("T11 aborted rising edge is not counted as a cell");
ck_int("T11 cells_seen", cells_seen, 1);
ck_int("T11 cells_legal", cells_legal, 1);
ck_int("T11 meas_tlow", meas_tlow, 1300);
ck_int("T11 meas_tr", meas_tr, 300);
// ----------------------------------------------------------------
// 12. Five back-to-back legal Fast-mode cells: the counters track.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
for (n = 0; n < 5; n = n + 1) emit_cell(1300, 300, 600, 300);
flush;
$display("T12 five back-to-back legal cells");
ck_int("T12 cells_seen", cells_seen, 5);
ck_int("T12 cells_legal", cells_legal, 5);
// ----------------------------------------------------------------
// 13. Mixed run: 3 legal then 2 illegal. cells_legal must not follow
// cells_seen.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 400, 600, 300); // tr too slow
emit_cell(1200, 300, 1300, 300); // tLOW short
flush;
$display("T13 three legal then two illegal cells");
ck_int("T13 cells_seen", cells_seen, 5);
ck_int("T13 cells_legal", cells_legal, 3);
ck_bit("T13 last legal", legal, 1'b0);
ck_int("T13 last binding", binding, BIND_TLOW);
// ----------------------------------------------------------------
// 14. Fast-mode Plus one tick under the period floor: 500+110+260+120
// = 990 ns. The rise time is legal (110 <= 120) and both phases are
// at their minima, so again only the budget catches it.
// ----------------------------------------------------------------
do_reset;
mode = M_FMP;
emit_cell(500, 110, 260, 120);
flush;
expect_cell("T14 Fm+ 10 ns under the 1000 ns period floor",
500, 110, 260, 120, 1'b0,
1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -10);
// ----------------------------------------------------------------
// 15. Standard-mode has no tr(min): a 10 ns rise is legal there, and the
// same 10 ns rise was a violation in Fast-mode (T8). Same edge, two
// verdicts, because Table 10 specifies the floor only for Fast-mode.
// ----------------------------------------------------------------
do_reset;
mode = M_STD;
emit_cell(6000, 10, 4000, 300);
flush;
// tHIGH and tf both sit exactly on their limits here, so the margin is 0
// twice over and the documented tie-break (table order) reports tHIGH.
expect_cell("T15 Standard-mode: a 10 ns rise is legal (no tr floor)",
6000, 10, 4000, 300, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_THIGH, 0);
if (errors == 0)
$display("=== i2c_mode_envelope: ALL CHECKS PASSED ===");
else
$display("=== i2c_mode_envelope: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule // -----------------------------------------------------------------------------
// i2c_mode_envelope.sv
// Per-mode SCL timing-envelope monitor for Standard-mode, Fast-mode and
// Fast-mode Plus.
//
// Measures one SCL bit cell the way UM10204 defines it -- between the 30 % and
// 70 % threshold crossings -- and checks it against the Table 10 limits of the
// selected mode:
//
// tLOW 30 % falling -> 30 % rising must be >= tLOW(min)
// tr 30 % rising -> 70 % rising must be <= tr(max), >= tr(min)
// tHIGH 70 % rising -> 70 % falling must be >= tHIGH(min)
// tf 70 % falling -> 30 % falling must be <= tf(max)
// period tLOW+tr+tHIGH+tf must be >= 1/fSCL(max)
//
// The period check is the interesting one. In all three modes Table 10 is
// internally exact:
// tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
// so the four limits are not independent -- they are one budget. A cell can
// satisfy every individual minimum and still be illegal because the edges ate
// the slack. This monitor reports which of the five constraints is the tightest
// (`binding`), not merely whether the cell passed.
//
// tf(min) is deliberately NOT checked: Table 10 specifies it as
// 20 x (VDD / 5.5 V) ns, a supply-dependent value this monitor is not given.
// tr(min) IS checked -- Table 10 states it as a flat 20 ns for Fast-mode and
// leaves it unspecified ("-") for Standard-mode and Fast-mode Plus.
//
// The line is presented as two comparator outputs rather than one logic level,
// because a digital level cannot express an edge rate.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_mode_envelope #(
parameter TICK_NS = 10, // ns per clk; every Table 10 value is a multiple of 10
parameter CNT_W = 16
) (
input wire clk,
input wire rst_n,
input wire [1:0] mode, // 0 = Standard, 1 = Fast, 2 = Fast-mode Plus
input wire th30, // 1 when the line is above 30 % of VDD
input wire th70, // 1 when the line is above 70 % of VDD
output reg [CNT_W-1:0] meas_tlow, // ns
output reg [CNT_W-1:0] meas_thigh, // ns
output reg [CNT_W-1:0] meas_tr, // ns
output reg [CNT_W-1:0] meas_tf, // ns
output reg [CNT_W-1:0] meas_period, // ns, = tlow + tr + thigh + tf
output reg cell_valid, // one pulse per completed bit cell
output reg legal, // that cell violated nothing
output reg [2:0] binding, // tightest constraint, see BIND_* below
output reg signed [CNT_W:0] worst_margin, // ns of slack on `binding` (negative = violated)
output reg viol_tlow,
output reg viol_thigh,
output reg viol_tr,
output reg viol_tf,
output reg viol_period,
output reg [CNT_W-1:0] cells_seen,
output reg [CNT_W-1:0] cells_legal
);
localparam [1:0] MODE_STD = 2'd0;
localparam [1:0] MODE_FAST = 2'd1;
localparam [1:0] MODE_FMP = 2'd2;
localparam [2:0] BIND_NONE = 3'd0;
localparam [2:0] BIND_TLOW = 3'd1;
localparam [2:0] BIND_THIGH = 3'd2;
localparam [2:0] BIND_TR = 3'd3;
localparam [2:0] BIND_TF = 3'd4;
localparam [2:0] BIND_PERIOD = 3'd5;
// ---------------------------------------------------------------------
// Table 10 limits, in ns. Standard / Fast / Fast-mode Plus.
// ---------------------------------------------------------------------
function [CNT_W-1:0] lim_tlow_min (input [1:0] m);
case (m)
MODE_STD : lim_tlow_min = 16'd4700;
MODE_FAST: lim_tlow_min = 16'd1300;
default : lim_tlow_min = 16'd500;
endcase
endfunction
function [CNT_W-1:0] lim_thigh_min (input [1:0] m);
case (m)
MODE_STD : lim_thigh_min = 16'd4000;
MODE_FAST: lim_thigh_min = 16'd600;
default : lim_thigh_min = 16'd260;
endcase
endfunction
function [CNT_W-1:0] lim_tr_max (input [1:0] m);
case (m)
MODE_STD : lim_tr_max = 16'd1000;
MODE_FAST: lim_tr_max = 16'd300;
default : lim_tr_max = 16'd120;
endcase
endfunction
// Table 10 gives tr(min) only for Fast-mode. "-" elsewhere means no limit.
function [CNT_W-1:0] lim_tr_min (input [1:0] m);
case (m)
MODE_FAST: lim_tr_min = 16'd20;
default : lim_tr_min = 16'd0;
endcase
endfunction
function [CNT_W-1:0] lim_tf_max (input [1:0] m);
case (m)
MODE_STD : lim_tf_max = 16'd300;
MODE_FAST: lim_tf_max = 16'd300;
default : lim_tf_max = 16'd120;
endcase
endfunction
// 1 / fSCL(max): 100 kHz, 400 kHz, 1000 kHz.
function [CNT_W-1:0] lim_period_min (input [1:0] m);
case (m)
MODE_STD : lim_period_min = 16'd10000;
MODE_FAST: lim_period_min = 16'd2500;
default : lim_period_min = 16'd1000;
endcase
endfunction
// ---------------------------------------------------------------------
// Threshold-crossing edge detection
// ---------------------------------------------------------------------
reg th30_q, th70_q;
wire th30_rise = th30 && !th30_q;
wire th30_fall = !th30 && th30_q;
wire th70_rise = th70 && !th70_q;
wire th70_fall = !th70 && th70_q;
// ---------------------------------------------------------------------
// Measurement FSM. The bit cell boundary is the 30 % falling edge.
// ---------------------------------------------------------------------
localparam [2:0] S_SYNC = 3'd0, // wait for the first 30 % falling edge
S_LOW = 3'd1,
S_RISE = 3'd2,
S_HIGH = 3'd3,
S_FALL = 3'd4;
reg [2:0] state;
reg [CNT_W-1:0] cnt;
reg [CNT_W-1:0] acc_tlow, acc_tr, acc_thigh;
localparam [CNT_W-1:0] TICK = TICK_NS;
// ---------------------------------------------------------------------
// Evaluation of a completed cell. Margins are signed: negative = violated.
// ---------------------------------------------------------------------
reg signed [CNT_W:0] m_tlow, m_thigh, m_tr, m_tf, m_period;
reg signed [CNT_W:0] best;
reg [2:0] best_id;
reg cell_ok;
reg signed [CNT_W:0] m_tr_hi, m_tr_lo;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
th30_q <= 1'b1;
th70_q <= 1'b1;
state <= S_SYNC;
cnt <= {CNT_W{1'b0}};
acc_tlow <= {CNT_W{1'b0}};
acc_tr <= {CNT_W{1'b0}};
acc_thigh <= {CNT_W{1'b0}};
meas_tlow <= {CNT_W{1'b0}};
meas_thigh <= {CNT_W{1'b0}};
meas_tr <= {CNT_W{1'b0}};
meas_tf <= {CNT_W{1'b0}};
meas_period <= {CNT_W{1'b0}};
cell_valid <= 1'b0;
legal <= 1'b0;
binding <= BIND_NONE;
worst_margin <= {(CNT_W+1){1'b0}};
viol_tlow <= 1'b0;
viol_thigh <= 1'b0;
viol_tr <= 1'b0;
viol_tf <= 1'b0;
viol_period <= 1'b0;
cells_seen <= {CNT_W{1'b0}};
cells_legal <= {CNT_W{1'b0}};
end else begin
th30_q <= th30;
th70_q <= th70;
cell_valid <= 1'b0;
case (state)
// Do not measure a partial cell: wait for a clean cell boundary.
S_SYNC: begin
if (th30_fall) begin
state <= S_LOW;
cnt <= {CNT_W{1'b0}};
end
end
S_LOW: begin
if (th30_rise) begin
acc_tlow <= cnt + TICK; // the crossing tick belongs to the phase
cnt <= {CNT_W{1'b0}};
state <= S_RISE;
end else begin
cnt <= cnt + TICK;
end
end
S_RISE: begin
if (th70_rise) begin
acc_tr <= cnt + TICK;
cnt <= {CNT_W{1'b0}};
state <= S_HIGH;
end else if (th30_fall) begin
// Never reached 70 %: an aborted edge. Resynchronise.
state <= S_LOW;
cnt <= {CNT_W{1'b0}};
end else begin
cnt <= cnt + TICK;
end
end
S_HIGH: begin
if (th70_fall) begin
acc_thigh <= cnt + TICK;
cnt <= {CNT_W{1'b0}};
state <= S_FALL;
end else begin
cnt <= cnt + TICK;
end
end
S_FALL: begin
if (th30_fall) begin
meas_tlow <= acc_tlow;
meas_thigh <= acc_thigh;
meas_tr <= acc_tr;
meas_tf <= cnt + TICK;
meas_period <= acc_tlow + acc_tr + acc_thigh + cnt + TICK;
cell_valid <= 1'b1;
cells_seen <= cells_seen + 1'b1;
// ---- verdict for the cell that just completed ----
m_tlow = $signed({1'b0, acc_tlow}) - $signed({1'b0, lim_tlow_min(mode)});
m_thigh = $signed({1'b0, acc_thigh}) - $signed({1'b0, lim_thigh_min(mode)});
// tr is bounded on BOTH sides in Fast-mode (20 ns .. 300 ns), so its
// margin is the tighter of the two. Where Table 10 gives no minimum
// (Standard, Fm+) lim_tr_min is 0 and the upper bound always wins.
m_tr_hi = $signed({1'b0, lim_tr_max(mode)}) - $signed({1'b0, acc_tr});
m_tr_lo = $signed({1'b0, acc_tr}) - $signed({1'b0, lim_tr_min(mode)});
m_tr = (lim_tr_min(mode) != 0 && m_tr_lo < m_tr_hi) ? m_tr_lo : m_tr_hi;
m_tf = $signed({1'b0, lim_tf_max(mode)}) - $signed({1'b0, cnt + TICK});
m_period = $signed({1'b0, acc_tlow + acc_tr + acc_thigh + cnt + TICK})
- $signed({1'b0, lim_period_min(mode)});
viol_tlow <= (m_tlow < 0);
viol_thigh <= (m_thigh < 0);
viol_tr <= (m_tr < 0);
viol_tf <= (m_tf < 0);
viol_period <= (m_period < 0);
cell_ok = !(m_tlow < 0) && !(m_thigh < 0) && !(m_tr < 0)
&& !(m_tf < 0) && !(m_period < 0);
legal <= cell_ok;
if (cell_ok) cells_legal <= cells_legal + 1'b1;
// Tightest constraint. Ties resolve in table order, so a cell
// sitting exactly on two limits reports the earlier one.
best = m_tlow; best_id = BIND_TLOW;
if (m_thigh < best) begin best = m_thigh; best_id = BIND_THIGH; end
if (m_tr < best) begin best = m_tr; best_id = BIND_TR; end
if (m_tf < best) begin best = m_tf; best_id = BIND_TF; end
if (m_period < best) begin best = m_period; best_id = BIND_PERIOD; end
worst_margin <= best;
binding <= best_id;
cnt <= {CNT_W{1'b0}};
state <= S_LOW; // next cell starts at this same edge
end else if (th70_rise) begin
// The line came back up without crossing 30 %: not a cell end.
cnt <= cnt + TICK;
state <= S_HIGH;
end else begin
cnt <= cnt + TICK;
end
end
default: state <= S_SYNC;
endcase
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_mode_envelope_tb.sv
// Independent oracle for i2c_mode_envelope.
//
// The bench drives threshold-crossing pairs that describe a bit cell of a known
// shape, then checks the DUT's measurement AND its verdict against limits the
// bench holds separately. The bench never reads the DUT's own limit functions.
//
// The point of the suite is the budget identity of Table 10:
// tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
// Tests 1-3 show it holds exactly in all three modes. Test 4 shows the
// consequence that surprises people: making an edge FASTER while the phases sit
// at their minima produces an ILLEGAL clock, because the period floor is a
// floor, not a ceiling.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_mode_envelope_tb;
localparam integer TICK_NS = 10;
localparam [1:0] M_STD = 2'd0;
localparam [1:0] M_FAST = 2'd1;
localparam [1:0] M_FMP = 2'd2;
localparam [2:0] BIND_NONE = 3'd0, BIND_TLOW = 3'd1, BIND_THIGH = 3'd2,
BIND_TR = 3'd3, BIND_TF = 3'd4, BIND_PERIOD = 3'd5;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg [1:0] mode = M_FAST;
reg th30 = 1'b1;
reg th70 = 1'b1;
wire [15:0] meas_tlow, meas_thigh, meas_tr, meas_tf, meas_period;
wire [15:0] cells_seen, cells_legal;
wire cell_valid, legal;
wire [2:0] binding;
wire signed [16:0] worst_margin;
wire viol_tlow, viol_thigh, viol_tr, viol_tf, viol_period;
integer errors = 0;
integer k; // used by hold() only
integer n; // outer loops -- must not be k, hold() clobbers it
i2c_mode_envelope #(.TICK_NS(TICK_NS), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n), .mode(mode), .th30(th30), .th70(th70),
.meas_tlow(meas_tlow), .meas_thigh(meas_thigh), .meas_tr(meas_tr),
.meas_tf(meas_tf), .meas_period(meas_period),
.cell_valid(cell_valid), .legal(legal), .binding(binding),
.worst_margin(worst_margin),
.viol_tlow(viol_tlow), .viol_thigh(viol_thigh), .viol_tr(viol_tr),
.viol_tf(viol_tf), .viol_period(viol_period),
.cells_seen(cells_seen), .cells_legal(cells_legal));
always #(TICK_NS/2) clk = ~clk;
// -------------------------------------------------------------------
// Stimulus. Levels change on the negedge so the DUT never has to resolve
// a transition that lands on its own sampling edge.
// -------------------------------------------------------------------
task hold (input integer ns_, input integer b30, input integer b70);
begin
@(negedge clk);
th30 = b30[0];
th70 = b70[0];
for (k = 0; k < ns_/TICK_NS; k = k + 1) @(posedge clk);
end
endtask
// One bit cell: LOW, then the rising edge, then HIGH, then the falling edge.
// The cell is reported by the DUT at the START of the next LOW phase.
task emit_cell (input integer tlow_, input integer tr_,
input integer thigh_, input integer tf_);
begin
hold(tlow_, 0, 0);
hold(tr_, 1, 0);
hold(thigh_, 1, 1);
hold(tf_, 1, 0);
end
endtask
task flush; // a trailing LOW so the last cell is reported
begin hold(200, 0, 0); end
endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; th30 = 1'b1; th70 = 1'b1;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
@(posedge clk);
end
endtask
// -------------------------------------------------------------------
// Checks
// -------------------------------------------------------------------
task ck_int (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input got, input exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0b expected %0b", what, got, exp);
errors = errors + 1;
end
end
endtask
// Full expectation for one measured cell.
task expect_cell (input [200*8:1] tag,
input integer e_tlow, input integer e_tr,
input integer e_thigh, input integer e_tf,
input e_legal,
input e_vlow, input e_vhigh, input e_vtr, input e_vtf, input e_vper,
input [2:0] e_bind, input integer e_margin);
begin
$display("%0s", tag);
ck_int("meas_tlow", meas_tlow, e_tlow);
ck_int("meas_tr", meas_tr, e_tr);
ck_int("meas_thigh", meas_thigh, e_thigh);
ck_int("meas_tf", meas_tf, e_tf);
ck_int("meas_period", meas_period, e_tlow + e_tr + e_thigh + e_tf);
ck_bit("legal", legal, e_legal);
ck_bit("viol_tlow", viol_tlow, e_vlow);
ck_bit("viol_thigh", viol_thigh, e_vhigh);
ck_bit("viol_tr", viol_tr, e_vtr);
ck_bit("viol_tf", viol_tf, e_vtf);
ck_bit("viol_period", viol_period, e_vper);
ck_int("binding", binding, e_bind);
ck_int("worst_margin", worst_margin, e_margin);
end
endtask
initial begin
$display("=== i2c_mode_envelope: Table 10 envelope checks ===");
// ----------------------------------------------------------------
// 1. Standard-mode exactly on the budget.
// 4700 + 1000 + 4000 + 300 = 10000 ns = 1/100 kHz.
// Every one of the five margins is zero at once.
// ----------------------------------------------------------------
do_reset;
mode = M_STD;
emit_cell(4700, 1000, 4000, 300);
flush;
expect_cell("T1 Standard-mode, all five limits met exactly",
4700, 1000, 4000, 300, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
ck_int("T1 cells_seen", cells_seen, 1);
ck_int("T1 cells_legal", cells_legal, 1);
// ----------------------------------------------------------------
// 2. Fast-mode exactly on the budget. 1300+300+600+300 = 2500.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 300, 600, 300);
flush;
expect_cell("T2 Fast-mode, all five limits met exactly",
1300, 300, 600, 300, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
// ----------------------------------------------------------------
// 3. Fast-mode Plus exactly on the budget. 500+120+260+120 = 1000.
// ----------------------------------------------------------------
do_reset;
mode = M_FMP;
emit_cell(500, 120, 260, 120);
flush;
expect_cell("T3 Fast-mode Plus, all five limits met exactly",
500, 120, 260, 120, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, 0);
// ----------------------------------------------------------------
// 4. THE CONSEQUENCE OF THE IDENTITY.
// Fast-mode, phases at their minima, but a FASTER rise (100 ns, well
// inside the 20..300 ns window). Every individual parameter is legal;
// the cell is illegal because the period fell to 2300 ns, i.e. the
// clock is running at 435 kHz. A quicker edge is not a free win.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 100, 600, 300);
flush;
expect_cell("T4 Fast-mode: every parameter legal, period floor broken",
1300, 100, 600, 300, 1'b0,
1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -200);
// ----------------------------------------------------------------
// 5. tLOW below its minimum, in isolation. tHIGH is stretched so the
// period floor is still met and only one flag can fire.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1200, 300, 1300, 300);
flush;
expect_cell("T5 Fast-mode: tLOW 100 ns short, nothing else",
1200, 300, 1300, 300, 1'b0,
1'b1,1'b0,1'b0,1'b0,1'b0, BIND_TLOW, -100);
// ----------------------------------------------------------------
// 6. tHIGH below its minimum, in isolation.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(2000, 300, 500, 300);
flush;
expect_cell("T6 Fast-mode: tHIGH 100 ns short, nothing else",
2000, 300, 500, 300, 1'b0,
1'b0,1'b1,1'b0,1'b0,1'b0, BIND_THIGH, -100);
// ----------------------------------------------------------------
// 7. Rise time above tr(max).
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 400, 600, 300);
flush;
expect_cell("T7 Fast-mode: tr 100 ns too slow",
1300, 400, 600, 300, 1'b0,
1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -100);
// ----------------------------------------------------------------
// 8. Rise time below tr(min). Table 10 gives Fast-mode a 20 ns FLOOR on
// the rise time; an edge can be too fast as well as too slow. The
// margin is two-sided, so this ranks as the binding constraint even
// though the upper bound has 290 ns of room.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(2000, 10, 600, 300);
flush;
expect_cell("T8 Fast-mode: tr 10 ns, below the 20 ns floor",
2000, 10, 600, 300, 1'b0,
1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -10);
// ----------------------------------------------------------------
// 9. Fall time above tf(max).
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 300, 600, 400);
flush;
expect_cell("T9 Fast-mode: tf 100 ns too slow",
1300, 300, 600, 400, 1'b0,
1'b0,1'b0,1'b0,1'b1,1'b0, BIND_TF, -100);
// ----------------------------------------------------------------
// 10. "Downward-compatible, not upward-compatible" (UM10204 5.1),
// measured rather than asserted.
// (a) A perfectly legal STANDARD-mode cell, judged as Fast-mode:
// illegal on the rise time alone. Its 1000 ns edge is 3.3x the
// Fast-mode allowance.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(4700, 1000, 4000, 300);
flush;
expect_cell("T10a legal Standard cell judged as Fast: fails on tr only",
4700, 1000, 4000, 300, 1'b0,
1'b0,1'b0,1'b1,1'b0,1'b0, BIND_TR, -700);
// (b) A perfectly legal FAST-mode cell, judged as Standard-mode:
// both phases too short AND the clock too fast. Three flags.
do_reset;
mode = M_STD;
emit_cell(1300, 300, 600, 300);
flush;
expect_cell("T10b legal Fast cell judged as Standard: three violations",
1300, 300, 600, 300, 1'b0,
1'b1,1'b1,1'b0,1'b0,1'b1, BIND_PERIOD, -7500);
// ----------------------------------------------------------------
// 11. An aborted rising edge. The line clears 30 % but sags back below
// it without ever reaching 70 %. That is not a bit cell and must not
// be measured as one; the monitor resynchronises and reports exactly
// one cell -- the good one that follows.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
hold(1300, 0, 0); // LOW
hold(200, 1, 0); // above 30 %, never reaches 70 %
hold(1300, 0, 0); // back down: aborted
hold(300, 1, 0); // now a real edge
hold(600, 1, 1);
hold(300, 1, 0);
flush;
$display("T11 aborted rising edge is not counted as a cell");
ck_int("T11 cells_seen", cells_seen, 1);
ck_int("T11 cells_legal", cells_legal, 1);
ck_int("T11 meas_tlow", meas_tlow, 1300);
ck_int("T11 meas_tr", meas_tr, 300);
// ----------------------------------------------------------------
// 12. Five back-to-back legal Fast-mode cells: the counters track.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
for (n = 0; n < 5; n = n + 1) emit_cell(1300, 300, 600, 300);
flush;
$display("T12 five back-to-back legal cells");
ck_int("T12 cells_seen", cells_seen, 5);
ck_int("T12 cells_legal", cells_legal, 5);
// ----------------------------------------------------------------
// 13. Mixed run: 3 legal then 2 illegal. cells_legal must not follow
// cells_seen.
// ----------------------------------------------------------------
do_reset;
mode = M_FAST;
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 400, 600, 300); // tr too slow
emit_cell(1200, 300, 1300, 300); // tLOW short
flush;
$display("T13 three legal then two illegal cells");
ck_int("T13 cells_seen", cells_seen, 5);
ck_int("T13 cells_legal", cells_legal, 3);
ck_bit("T13 last legal", legal, 1'b0);
ck_int("T13 last binding", binding, BIND_TLOW);
// ----------------------------------------------------------------
// 14. Fast-mode Plus one tick under the period floor: 500+110+260+120
// = 990 ns. The rise time is legal (110 <= 120) and both phases are
// at their minima, so again only the budget catches it.
// ----------------------------------------------------------------
do_reset;
mode = M_FMP;
emit_cell(500, 110, 260, 120);
flush;
expect_cell("T14 Fm+ 10 ns under the 1000 ns period floor",
500, 110, 260, 120, 1'b0,
1'b0,1'b0,1'b0,1'b0,1'b1, BIND_PERIOD, -10);
// ----------------------------------------------------------------
// 15. Standard-mode has no tr(min): a 10 ns rise is legal there, and the
// same 10 ns rise was a violation in Fast-mode (T8). Same edge, two
// verdicts, because Table 10 specifies the floor only for Fast-mode.
// ----------------------------------------------------------------
do_reset;
mode = M_STD;
emit_cell(6000, 10, 4000, 300);
flush;
// tHIGH and tf both sit exactly on their limits here, so the margin is 0
// twice over and the documented tie-break (table order) reports tHIGH.
expect_cell("T15 Standard-mode: a 10 ns rise is legal (no tr floor)",
6000, 10, 4000, 300, 1'b1,
1'b0,1'b0,1'b0,1'b0,1'b0, BIND_THIGH, 0);
if (errors == 0)
$display("=== i2c_mode_envelope: ALL CHECKS PASSED ===");
else
$display("=== i2c_mode_envelope: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_mode_envelope.vhd
-- Per-mode SCL timing-envelope monitor for Standard-mode, Fast-mode and
-- Fast-mode Plus. Behavioural twin of i2c_mode_envelope.sv / .v.
--
-- Measures one SCL bit cell the way UM10204 defines it -- between the 30 % and
-- 70 % threshold crossings -- and checks it against the Table 10 limits of the
-- selected mode:
--
-- tLOW 30 % falling -> 30 % rising must be >= tLOW(min)
-- tr 30 % rising -> 70 % rising must be <= tr(max), >= tr(min)
-- tHIGH 70 % rising -> 70 % falling must be >= tHIGH(min)
-- tf 70 % falling -> 30 % falling must be <= tf(max)
-- period tLOW+tr+tHIGH+tf must be >= 1/fSCL(max)
--
-- In all three modes Table 10 is internally exact:
-- tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
-- so the four limits are one budget, not four independent numbers. A cell can
-- satisfy every individual minimum and still be illegal because the edges ate
-- the slack, and this monitor reports which constraint is tightest.
--
-- tf(min) is deliberately NOT checked: Table 10 specifies it as
-- 20 x (VDD / 5.5 V) ns, a supply-dependent value this monitor is not given.
-- tr(min) IS checked -- a flat 20 ns for Fast-mode, unspecified elsewhere.
--
-- The margins are held in plain unconstrained `integer`. A range-constrained
-- integer subtype would turn an out-of-range intermediate into a simulation
-- crash instead of a reported violation, which is the wrong failure mode for a
-- monitor whose whole job is to report violations.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_mode_envelope is
generic (
TICK_NS : integer := 10; -- ns per clk; every Table 10 value is a multiple of 10
CNT_W : integer := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
mode : in std_logic_vector(1 downto 0); -- 0 = Std, 1 = Fast, 2 = Fm+
th30 : in std_logic; -- 1 when the line is above 30 % of VDD
th70 : in std_logic; -- 1 when the line is above 70 % of VDD
meas_tlow : out unsigned(CNT_W-1 downto 0);
meas_thigh : out unsigned(CNT_W-1 downto 0);
meas_tr : out unsigned(CNT_W-1 downto 0);
meas_tf : out unsigned(CNT_W-1 downto 0);
meas_period : out unsigned(CNT_W-1 downto 0);
cell_valid : out std_logic;
legal : out std_logic;
binding : out unsigned(2 downto 0);
worst_margin : out integer;
viol_tlow : out std_logic;
viol_thigh : out std_logic;
viol_tr : out std_logic;
viol_tf : out std_logic;
viol_period : out std_logic;
cells_seen : out unsigned(CNT_W-1 downto 0);
cells_legal : out unsigned(CNT_W-1 downto 0)
);
end entity i2c_mode_envelope;
architecture rtl of i2c_mode_envelope is
constant MODE_STD : std_logic_vector(1 downto 0) := "00";
constant MODE_FAST : std_logic_vector(1 downto 0) := "01";
constant BIND_TLOW : integer := 1;
constant BIND_THIGH : integer := 2;
constant BIND_TR : integer := 3;
constant BIND_TF : integer := 4;
constant BIND_PERIOD : integer := 5;
-- Table 10 limits, in ns.
function lim_tlow_min (m : std_logic_vector(1 downto 0)) return integer is
begin
if m = MODE_STD then return 4700;
elsif m = MODE_FAST then return 1300;
else return 500;
end if;
end function;
function lim_thigh_min (m : std_logic_vector(1 downto 0)) return integer is
begin
if m = MODE_STD then return 4000;
elsif m = MODE_FAST then return 600;
else return 260;
end if;
end function;
function lim_tr_max (m : std_logic_vector(1 downto 0)) return integer is
begin
if m = MODE_STD then return 1000;
elsif m = MODE_FAST then return 300;
else return 120;
end if;
end function;
-- Table 10 gives tr(min) only for Fast-mode; "-" elsewhere means no limit.
function lim_tr_min (m : std_logic_vector(1 downto 0)) return integer is
begin
if m = MODE_FAST then return 20; else return 0; end if;
end function;
function lim_tf_max (m : std_logic_vector(1 downto 0)) return integer is
begin
if m = MODE_STD then return 300;
elsif m = MODE_FAST then return 300;
else return 120;
end if;
end function;
-- 1 / fSCL(max): 100 kHz, 400 kHz, 1000 kHz.
function lim_period_min (m : std_logic_vector(1 downto 0)) return integer is
begin
if m = MODE_STD then return 10000;
elsif m = MODE_FAST then return 2500;
else return 1000;
end if;
end function;
-- Measurement FSM. The bit cell boundary is the 30 % falling edge.
type state_t is (S_SYNC, S_LOW, S_RISE, S_HIGH, S_FALL);
signal state : state_t := S_SYNC;
signal th30_q, th70_q : std_logic := '1';
signal cnt : integer := 0;
signal acc_tlow : integer := 0;
signal acc_tr : integer := 0;
signal acc_thigh : integer := 0;
signal n_seen, n_legal : integer := 0;
begin
process (clk, rst_n)
variable m_tlow, m_thigh, m_tr, m_tf, m_period : integer;
variable m_tr_hi, m_tr_lo : integer;
variable best : integer;
variable best_id : integer;
variable cell_ok : boolean;
variable tf_now, per_now : integer;
variable th30_rise, th30_fall : boolean;
variable th70_rise, th70_fall : boolean;
begin
if rst_n = '0' then
th30_q <= '1';
th70_q <= '1';
state <= S_SYNC;
cnt <= 0;
acc_tlow <= 0;
acc_tr <= 0;
acc_thigh <= 0;
meas_tlow <= (others => '0');
meas_thigh <= (others => '0');
meas_tr <= (others => '0');
meas_tf <= (others => '0');
meas_period <= (others => '0');
cell_valid <= '0';
legal <= '0';
binding <= (others => '0');
worst_margin <= 0;
viol_tlow <= '0';
viol_thigh <= '0';
viol_tr <= '0';
viol_tf <= '0';
viol_period <= '0';
n_seen <= 0;
n_legal <= 0;
cells_seen <= (others => '0');
cells_legal <= (others => '0');
elsif rising_edge(clk) then
th30_rise := (th30 = '1') and (th30_q = '0');
th30_fall := (th30 = '0') and (th30_q = '1');
th70_rise := (th70 = '1') and (th70_q = '0');
th70_fall := (th70 = '0') and (th70_q = '1');
th30_q <= th30;
th70_q <= th70;
cell_valid <= '0';
case state is
-- Do not measure a partial cell: wait for a clean cell boundary.
when S_SYNC =>
if th30_fall then
state <= S_LOW;
cnt <= 0;
end if;
when S_LOW =>
if th30_rise then
acc_tlow <= cnt + TICK_NS; -- the crossing tick belongs to the phase
cnt <= 0;
state <= S_RISE;
else
cnt <= cnt + TICK_NS;
end if;
when S_RISE =>
if th70_rise then
acc_tr <= cnt + TICK_NS;
cnt <= 0;
state <= S_HIGH;
elsif th30_fall then
-- Never reached 70 %: an aborted edge. Resynchronise.
state <= S_LOW;
cnt <= 0;
else
cnt <= cnt + TICK_NS;
end if;
when S_HIGH =>
if th70_fall then
acc_thigh <= cnt + TICK_NS;
cnt <= 0;
state <= S_FALL;
else
cnt <= cnt + TICK_NS;
end if;
when S_FALL =>
if th30_fall then
tf_now := cnt + TICK_NS;
per_now := acc_tlow + acc_tr + acc_thigh + tf_now;
meas_tlow <= to_unsigned(acc_tlow, CNT_W);
meas_thigh <= to_unsigned(acc_thigh, CNT_W);
meas_tr <= to_unsigned(acc_tr, CNT_W);
meas_tf <= to_unsigned(tf_now, CNT_W);
meas_period <= to_unsigned(per_now, CNT_W);
cell_valid <= '1';
n_seen <= n_seen + 1;
cells_seen <= to_unsigned(n_seen + 1, CNT_W);
cnt <= 0;
state <= S_LOW; -- next cell starts at this same edge
-- ---- verdict for the cell that just completed ----
m_tlow := acc_tlow - lim_tlow_min(mode);
m_thigh := acc_thigh - lim_thigh_min(mode);
-- tr is bounded on BOTH sides in Fast-mode (20 ns .. 300 ns), so
-- its margin is the tighter of the two. Where Table 10 gives no
-- minimum, lim_tr_min is 0 and the upper bound always wins.
m_tr_hi := lim_tr_max(mode) - acc_tr;
m_tr_lo := acc_tr - lim_tr_min(mode);
if lim_tr_min(mode) /= 0 and m_tr_lo < m_tr_hi then
m_tr := m_tr_lo;
else
m_tr := m_tr_hi;
end if;
m_tf := lim_tf_max(mode) - tf_now;
m_period := per_now - lim_period_min(mode);
if m_tlow < 0 then viol_tlow <= '1'; else viol_tlow <= '0'; end if;
if m_thigh < 0 then viol_thigh <= '1'; else viol_thigh <= '0'; end if;
if m_tr < 0 then viol_tr <= '1'; else viol_tr <= '0'; end if;
if m_tf < 0 then viol_tf <= '1'; else viol_tf <= '0'; end if;
if m_period < 0 then viol_period <= '1'; else viol_period <= '0'; end if;
cell_ok := (m_tlow >= 0) and (m_thigh >= 0) and (m_tr >= 0)
and (m_tf >= 0) and (m_period >= 0);
if cell_ok then
legal <= '1';
n_legal <= n_legal + 1;
cells_legal <= to_unsigned(n_legal + 1, CNT_W);
else
legal <= '0';
end if;
-- Tightest constraint. Ties resolve in table order, so a cell
-- sitting exactly on two limits reports the earlier one.
best := m_tlow; best_id := BIND_TLOW;
if m_thigh < best then best := m_thigh; best_id := BIND_THIGH; end if;
if m_tr < best then best := m_tr; best_id := BIND_TR; end if;
if m_tf < best then best := m_tf; best_id := BIND_TF; end if;
if m_period < best then best := m_period; best_id := BIND_PERIOD; end if;
worst_margin <= best;
binding <= to_unsigned(best_id, 3);
elsif th70_rise then
-- The line came back up without crossing 30 %: not a cell end.
cnt <= cnt + TICK_NS;
state <= S_HIGH;
else
cnt <= cnt + TICK_NS;
end if;
end case;
end if;
end process;
end architecture rtl; -- ---------------------------------------------------------------------------
-- i2c_mode_envelope_tb.vhd
-- Independent oracle for i2c_mode_envelope. Behavioural twin of the
-- SystemVerilog and Verilog benches, written against the same limits held
-- separately from the design.
--
-- The point of the suite is the budget identity of Table 10:
-- tLOW(min) + tHIGH(min) + tr(max) + tf(max) == 1 / fSCL(max)
-- Tests 1-3 show it holds exactly in all three modes. Test 4 shows the
-- consequence that surprises people: making an edge FASTER while the phases sit
-- at their minima produces an ILLEGAL clock, because the period floor is a
-- floor, not a ceiling.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_mode_envelope_tb is
end entity i2c_mode_envelope_tb;
architecture sim of i2c_mode_envelope_tb is
constant TICK_NS : integer := 10;
constant CNT_W : integer := 16;
constant TCLK : time := 10 ns;
constant M_STD : std_logic_vector(1 downto 0) := "00";
constant M_FAST : std_logic_vector(1 downto 0) := "01";
constant M_FMP : std_logic_vector(1 downto 0) := "10";
constant BIND_TLOW : integer := 1;
constant BIND_THIGH : integer := 2;
constant BIND_TR : integer := 3;
constant BIND_TF : integer := 4;
constant BIND_PERIOD : integer := 5;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal mode : std_logic_vector(1 downto 0) := M_FAST;
signal th30 : std_logic := '1';
signal th70 : std_logic := '1';
signal meas_tlow, meas_thigh, meas_tr, meas_tf, meas_period : unsigned(CNT_W-1 downto 0);
signal cells_seen, cells_legal : unsigned(CNT_W-1 downto 0);
signal cell_valid, legal : std_logic;
signal binding : unsigned(2 downto 0);
signal worst_margin : integer;
signal viol_tlow, viol_thigh, viol_tr, viol_tf, viol_period : std_logic;
signal halt : boolean := false;
signal errors : integer := 0;
begin
dut : entity work.i2c_mode_envelope
generic map (TICK_NS => TICK_NS, CNT_W => CNT_W)
port map (
clk => clk, rst_n => rst_n, mode => mode, th30 => th30, th70 => th70,
meas_tlow => meas_tlow, meas_thigh => meas_thigh, meas_tr => meas_tr,
meas_tf => meas_tf, meas_period => meas_period,
cell_valid => cell_valid, legal => legal, binding => binding,
worst_margin => worst_margin,
viol_tlow => viol_tlow, viol_thigh => viol_thigh, viol_tr => viol_tr,
viol_tf => viol_tf, viol_period => viol_period,
cells_seen => cells_seen, cells_legal => cells_legal);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for TCLK/2;
clk <= '1'; wait for TCLK/2;
end loop;
wait;
end process;
stim : process
variable err : integer := 0;
procedure ck_int (what : string; got : integer; exp : integer) is
begin
if got /= exp then
report " FAIL " & what & ": got " & integer'image(got)
& " expected " & integer'image(exp) severity note;
err := err + 1;
end if;
end procedure;
procedure ck_bit (what : string; got : std_logic; exp : std_logic) is
begin
if got /= exp then
report " FAIL " & what & ": got " & std_logic'image(got)
& " expected " & std_logic'image(exp) severity note;
err := err + 1;
end if;
end procedure;
-- Levels change on the falling edge so the DUT never has to resolve a
-- transition that lands on its own sampling edge.
procedure hold (ns_v : integer; b30 : std_logic; b70 : std_logic) is
begin
wait until falling_edge(clk);
th30 <= b30;
th70 <= b70;
for k in 0 to (ns_v/TICK_NS) - 1 loop
wait until rising_edge(clk);
end loop;
end procedure;
-- One bit cell: LOW, the rising edge, HIGH, the falling edge. The cell is
-- reported by the DUT at the START of the next LOW phase.
procedure emit_cell (tlow_v : integer; tr_v : integer;
thigh_v : integer; tf_v : integer) is
begin
hold(tlow_v, '0', '0');
hold(tr_v, '1', '0');
hold(thigh_v, '1', '1');
hold(tf_v, '1', '0');
end procedure;
procedure flush is -- a trailing LOW so the last cell is reported
begin
hold(200, '0', '0');
end procedure;
procedure do_reset is
begin
wait until falling_edge(clk);
rst_n <= '0'; th30 <= '1'; th70 <= '1';
for k in 0 to 2 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk);
rst_n <= '1';
wait until rising_edge(clk);
end procedure;
procedure expect_cell (tag : string;
e_tlow, e_tr, e_thigh, e_tf : integer;
e_legal : std_logic;
e_vlow, e_vhigh, e_vtr, e_vtf, e_vper : std_logic;
e_bind : integer; e_margin : integer) is
begin
report tag severity note;
ck_int("meas_tlow", to_integer(meas_tlow), e_tlow);
ck_int("meas_tr", to_integer(meas_tr), e_tr);
ck_int("meas_thigh", to_integer(meas_thigh), e_thigh);
ck_int("meas_tf", to_integer(meas_tf), e_tf);
ck_int("meas_period", to_integer(meas_period), e_tlow + e_tr + e_thigh + e_tf);
ck_bit("legal", legal, e_legal);
ck_bit("viol_tlow", viol_tlow, e_vlow);
ck_bit("viol_thigh", viol_thigh, e_vhigh);
ck_bit("viol_tr", viol_tr, e_vtr);
ck_bit("viol_tf", viol_tf, e_vtf);
ck_bit("viol_period", viol_period, e_vper);
ck_int("binding", to_integer(binding), e_bind);
ck_int("worst_margin", worst_margin, e_margin);
end procedure;
begin
report "=== i2c_mode_envelope: Table 10 envelope checks ===" severity note;
-- 1. Standard-mode exactly on the budget.
-- 4700 + 1000 + 4000 + 300 = 10000 ns = 1/100 kHz.
-- Every one of the five margins is zero at once.
do_reset;
mode <= M_STD;
emit_cell(4700, 1000, 4000, 300);
flush;
expect_cell("T1 Standard-mode, all five limits met exactly",
4700, 1000, 4000, 300, '1', '0','0','0','0','0', BIND_TLOW, 0);
ck_int("T1 cells_seen", to_integer(cells_seen), 1);
ck_int("T1 cells_legal", to_integer(cells_legal), 1);
-- 2. Fast-mode exactly on the budget. 1300+300+600+300 = 2500.
do_reset;
mode <= M_FAST;
emit_cell(1300, 300, 600, 300);
flush;
expect_cell("T2 Fast-mode, all five limits met exactly",
1300, 300, 600, 300, '1', '0','0','0','0','0', BIND_TLOW, 0);
-- 3. Fast-mode Plus exactly on the budget. 500+120+260+120 = 1000.
do_reset;
mode <= M_FMP;
emit_cell(500, 120, 260, 120);
flush;
expect_cell("T3 Fast-mode Plus, all five limits met exactly",
500, 120, 260, 120, '1', '0','0','0','0','0', BIND_TLOW, 0);
-- 4. THE CONSEQUENCE OF THE IDENTITY. Fast-mode, phases at their minima,
-- but a FASTER rise (100 ns, well inside the 20..300 ns window). Every
-- individual parameter is legal; the cell is illegal because the period
-- fell to 2300 ns, i.e. the clock is running at 435 kHz.
do_reset;
mode <= M_FAST;
emit_cell(1300, 100, 600, 300);
flush;
expect_cell("T4 Fast-mode: every parameter legal, period floor broken",
1300, 100, 600, 300, '0', '0','0','0','0','1', BIND_PERIOD, -200);
-- 5. tLOW below its minimum, in isolation.
do_reset;
mode <= M_FAST;
emit_cell(1200, 300, 1300, 300);
flush;
expect_cell("T5 Fast-mode: tLOW 100 ns short, nothing else",
1200, 300, 1300, 300, '0', '1','0','0','0','0', BIND_TLOW, -100);
-- 6. tHIGH below its minimum, in isolation.
do_reset;
mode <= M_FAST;
emit_cell(2000, 300, 500, 300);
flush;
expect_cell("T6 Fast-mode: tHIGH 100 ns short, nothing else",
2000, 300, 500, 300, '0', '0','1','0','0','0', BIND_THIGH, -100);
-- 7. Rise time above tr(max).
do_reset;
mode <= M_FAST;
emit_cell(1300, 400, 600, 300);
flush;
expect_cell("T7 Fast-mode: tr 100 ns too slow",
1300, 400, 600, 300, '0', '0','0','1','0','0', BIND_TR, -100);
-- 8. Rise time below tr(min). Table 10 gives Fast-mode a 20 ns FLOOR on the
-- rise time; an edge can be too fast as well as too slow.
do_reset;
mode <= M_FAST;
emit_cell(2000, 10, 600, 300);
flush;
expect_cell("T8 Fast-mode: tr 10 ns, below the 20 ns floor",
2000, 10, 600, 300, '0', '0','0','1','0','0', BIND_TR, -10);
-- 9. Fall time above tf(max).
do_reset;
mode <= M_FAST;
emit_cell(1300, 300, 600, 400);
flush;
expect_cell("T9 Fast-mode: tf 100 ns too slow",
1300, 300, 600, 400, '0', '0','0','0','1','0', BIND_TF, -100);
-- 10. "Downward-compatible, not upward-compatible" (UM10204 5.1), measured
-- rather than asserted.
-- (a) A legal STANDARD-mode cell judged as Fast-mode: illegal on the
-- rise time alone. Its 1000 ns edge is 3.3x the Fast-mode allowance.
do_reset;
mode <= M_FAST;
emit_cell(4700, 1000, 4000, 300);
flush;
expect_cell("T10a legal Standard cell judged as Fast: fails on tr only",
4700, 1000, 4000, 300, '0', '0','0','1','0','0', BIND_TR, -700);
-- (b) A legal FAST-mode cell judged as Standard-mode: both phases too
-- short AND the clock too fast. Three flags.
do_reset;
mode <= M_STD;
emit_cell(1300, 300, 600, 300);
flush;
expect_cell("T10b legal Fast cell judged as Standard: three violations",
1300, 300, 600, 300, '0', '1','1','0','0','1', BIND_PERIOD, -7500);
-- 11. An aborted rising edge. The line clears 30 % but sags back below it
-- without ever reaching 70 %. That is not a bit cell and must not be
-- measured as one.
do_reset;
mode <= M_FAST;
hold(1300, '0', '0'); -- LOW
hold(200, '1', '0'); -- above 30 %, never reaches 70 %
hold(1300, '0', '0'); -- back down: aborted
hold(300, '1', '0'); -- now a real edge
hold(600, '1', '1');
hold(300, '1', '0');
flush;
report "T11 aborted rising edge is not counted as a cell" severity note;
ck_int("T11 cells_seen", to_integer(cells_seen), 1);
ck_int("T11 cells_legal", to_integer(cells_legal), 1);
ck_int("T11 meas_tlow", to_integer(meas_tlow), 1300);
ck_int("T11 meas_tr", to_integer(meas_tr), 300);
-- 12. Five back-to-back legal Fast-mode cells: the counters track.
do_reset;
mode <= M_FAST;
for k in 0 to 4 loop emit_cell(1300, 300, 600, 300); end loop;
flush;
report "T12 five back-to-back legal cells" severity note;
ck_int("T12 cells_seen", to_integer(cells_seen), 5);
ck_int("T12 cells_legal", to_integer(cells_legal), 5);
-- 13. Mixed run: 3 legal then 2 illegal.
do_reset;
mode <= M_FAST;
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 300, 600, 300);
emit_cell(1300, 400, 600, 300); -- tr too slow
emit_cell(1200, 300, 1300, 300); -- tLOW short
flush;
report "T13 three legal then two illegal cells" severity note;
ck_int("T13 cells_seen", to_integer(cells_seen), 5);
ck_int("T13 cells_legal", to_integer(cells_legal), 3);
ck_bit("T13 last legal", legal, '0');
ck_int("T13 last binding", to_integer(binding), BIND_TLOW);
-- 14. Fast-mode Plus one tick under the period floor: 500+110+260+120 = 990.
do_reset;
mode <= M_FMP;
emit_cell(500, 110, 260, 120);
flush;
expect_cell("T14 Fm+ 10 ns under the 1000 ns period floor",
500, 110, 260, 120, '0', '0','0','0','0','1', BIND_PERIOD, -10);
-- 15. Standard-mode has no tr(min): a 10 ns rise is legal there, and the
-- same 10 ns rise was a violation in Fast-mode (T8). tHIGH and tf both
-- sit exactly on their limits, so the tie-break reports tHIGH.
do_reset;
mode <= M_STD;
emit_cell(6000, 10, 4000, 300);
flush;
expect_cell("T15 Standard-mode: a 10 ns rise is legal (no tr floor)",
6000, 10, 4000, 300, '1', '0','0','0','0','0', BIND_THIGH, 0);
errors <= err;
if err = 0 then
report "=== i2c_mode_envelope: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_mode_envelope: " & integer'image(err)
& " CHECK(S) FAILED ===" severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;8a. Six Decisions Worth Defending
The cell boundary is the 30 % falling edge. Any of the four crossings could have been chosen, and choosing the 30 % fall makes the four phases measurable in one pass with no carry-over between cells: tLOW, then tr, then tHIGH, then tf, then report and immediately begin the next tLOW at the same edge. A boundary anywhere else leaves one phase spanning the report.
The crossing tick belongs to the phase. Each measurement adds one tick when the terminating crossing is seen, so a phase driven for n ticks measures n ticks. Mutation S6 drops that + TICK and 51 checks fail — because every one of the fifteen cells is then short by one tick and the budget no longer closes.
tr has a two-sided margin. Table 10 bounds Fast-mode's rise time above and below. Ranking only the upper bound would report a 10 ns edge as having 290 ns of margin while flagging it as a violation, which is incoherent. Mutation S11 is that incoherence and four checks catch it.
tf(min) is deliberately not checked, and the omission is documented in the source. Table 10 specifies it as 20 × (VDD / 5.5 V) ns — a supply-dependent value this monitor is not given. Implementing it against an assumed supply would be worse than omitting it, because the check would then be wrong rather than absent. This is the same discipline Chapter 11.8 applies to the spike filter's unspecified Standard-mode behaviour.
Ties resolve in table order. A cell sitting exactly on two limits has two margins of zero. Reporting the earlier parameter is arbitrary but it is stated, in the source and in the testbench, so test 15 can assert it rather than discover it. Mutation S8 reorders the ranking and six checks fail.
An aborted edge resynchronises rather than measuring. A line that clears 30 % and sags back without reaching 70 % is not a bit cell, and counting it as one would corrupt the cells_seen total that everything else is judged against. Mutation S9 accepts it and test 11 kills it.
8b. Verified Execution
$ iverilog -g2012 -o d i2c_mode_envelope.sv i2c_mode_envelope_tb.sv && ./d
=== i2c_mode_envelope: Table 10 envelope checks ===
T1 Standard-mode, all five limits met exactly
T2 Fast-mode, all five limits met exactly
T3 Fast-mode Plus, all five limits met exactly
T4 Fast-mode: every parameter legal, period floor broken
T5 Fast-mode: tLOW 100 ns short, nothing else
T6 Fast-mode: tHIGH 100 ns short, nothing else
T7 Fast-mode: tr 100 ns too slow
T8 Fast-mode: tr 10 ns, below the 20 ns floor
T9 Fast-mode: tf 100 ns too slow
T10a legal Standard cell judged as Fast: fails on tr only
T10b legal Fast cell judged as Standard: three violations
T11 aborted rising edge is not counted as a cell
T12 five back-to-back legal cells
T13 three legal then two illegal cells
T14 Fm+ 10 ns under the 1000 ns period floor
T15 Standard-mode: a 10 ns rise is legal (no tr floor)
=== i2c_mode_envelope: ALL CHECKS PASSED ===
i2c_mode_envelope_tb.sv:363: $finish called at 87455000 (1ps)
$ iverilog -g2005 -o v i2c_mode_envelope.v i2c_mode_envelope_tb.v && ./v
=== i2c_mode_envelope: Table 10 envelope checks ===
T1 Standard-mode, all five limits met exactly
T2 Fast-mode, all five limits met exactly
T3 Fast-mode Plus, all five limits met exactly
T4 Fast-mode: every parameter legal, period floor broken
T5 Fast-mode: tLOW 100 ns short, nothing else
T6 Fast-mode: tHIGH 100 ns short, nothing else
T7 Fast-mode: tr 100 ns too slow
T8 Fast-mode: tr 10 ns, below the 20 ns floor
T9 Fast-mode: tf 100 ns too slow
T10a legal Standard cell judged as Fast: fails on tr only
T10b legal Fast cell judged as Standard: three violations
T11 aborted rising edge is not counted as a cell
T12 five back-to-back legal cells
T13 three legal then two illegal cells
T14 Fm+ 10 ns under the 1000 ns period floor
T15 Standard-mode: a 10 ns rise is legal (no tr floor)
=== i2c_mode_envelope: ALL CHECKS PASSED ===
i2c_mode_envelope_tb.v:364: $finish called at 87455000 (1ps)
$ nvc --std=2008 -a i2c_mode_envelope.vhd i2c_mode_envelope_tb.vhd
$ nvc --std=2008 -e i2c_mode_envelope_tb && nvc --std=2008 -r i2c_mode_envelope_tb --stop-time=500us
** Note: 0ms+0: === i2c_mode_envelope: Table 10 envelope checks ===
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
** Note: 10245ns+1: T1 Standard-mode, all five limits met exactly
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:168
** Note: 12985ns+1: T2 Fast-mode, all five limits met exactly
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:178
** Note: 14225ns+1: T3 Fast-mode Plus, all five limits met exactly
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:186
** Note: 16765ns+1: T4 Fast-mode: every parameter legal, period floor broken
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:197
** Note: 20105ns+1: T5 Fast-mode: tLOW 100 ns short, nothing else
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:205
** Note: 23445ns+1: T6 Fast-mode: tHIGH 100 ns short, nothing else
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:213
** Note: 26285ns+1: T7 Fast-mode: tr 100 ns too slow
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:221
** Note: 29435ns+1: T8 Fast-mode: tr 10 ns, below the 20 ns floor
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:230
** Note: 32275ns+1: T9 Fast-mode: tf 100 ns too slow
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:238
** Note: 42515ns+1: T10a legal Standard cell judged as Fast: fails on tr only
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:249
** Note: 45255ns+1: T10b legal Fast cell judged as Standard: three violations
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:258
** Note: 49495ns+1: T11 aborted rising edge is not counted as a cell
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
** Note: 62235ns+1: T12 five back-to-back legal cells
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
** Note: 75675ns+1: T13 three legal then two illegal cells
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77
** Note: 76905ns+1: T14 Fm+ 10 ns under the 1000 ns period floor
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:308
** Note: 87455ns+1: T15 Standard-mode: a 10 ns rise is legal (no tr floor)
Procedure EXPECT_CELL [STRING, INTEGER, INTEGER, INTEGER, INTEGER, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, STD_LOGIC, INTEGER, INTEGER] at i2c_mode_envelope_tb.vhd:136
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:318
** Note: 87455ns+1: === i2c_mode_envelope: ALL CHECKS PASSED ===
Process :i2c_mode_envelope_tb:stim at i2c_mode_envelope_tb.vhd:77All three at 87455 ns, and the finish time is worth a sentence. The three implementations are independent — a SystemVerilog original, a Verilog-2001 port, and a VHDL-2008 rewrite whose margins live in unconstrained integer rather than in sized vectors — and they agree on the cycle. A divergence of even one tick would mean the measurement convention had drifted between languages, which is precisely the failure the VHDL port of Chapter 12.2 caught when both Verilog suites had passed it.
8c. What the Testbench Proves
| # | stimulus | what it establishes |
|---|---|---|
| 1 | Standard-mode, 4700 / 1000 / 4000 / 300 | all five margins zero at once — the budget is exact |
| 2 | Fast-mode, 1300 / 300 / 600 / 300 | same, at 2500 ns |
| 3 | Fm+, 500 / 120 / 260 / 120 | same, at 1000 ns |
| 4 | Fast-mode with a faster 100 ns rise | every parameter legal, period floor broken |
| 5 | tLOW 100 ns short, tHIGH stretched to compensate | tLOW alone flagged, margin −100 |
| 6 | tHIGH 100 ns short, tLOW stretched | tHIGH alone flagged |
| 7 | tr = 400 ns | tr alone flagged |
| 8 | tr = 10 ns | tr flagged on its lower bound, margin −10 |
| 9 | tf = 400 ns | tf alone flagged |
| 10a | the Standard cell, judged as Fast | fails on tr only |
| 10b | the Fast cell, judged as Standard | three violations |
| 11 | an aborted rising edge, then a good cell | exactly one cell counted |
| 12 | five back-to-back legal cells | the counters track |
| 13 | three legal then two illegal | cells_legal does not follow cells_seen |
| 14 | Fm+ 10 ns under the period floor | only the budget catches it |
| 15 | Standard-mode with a 10 ns rise | legal — no tr floor outside Fast-mode |
Tests 1 to 3 are the chapter's central claim, measured. Each drives a cell built from one mode's own table values and asserts that all five margins are simultaneously zero. Any of the six limit functions being wrong by a nanosecond breaks at least one of them.
Test 4 is §4a. It is the only test where the cell is illegal while every individual parameter is legal, and it exists to stop anyone treating Table 10 as a checklist.
Tests 5 and 6 need the compensating stretch, and that is itself instructive. Because the budget is exact, you cannot shorten one phase below its minimum while holding everything else at the limits — the period would fail too, and two flags would fire. Isolating a single violation requires stretching the other phase past its minimum, which is legal. The bench has to work around the identity to test around it.
Test 8 and test 15 are the same 10 ns edge with opposite verdicts. Legal in Standard-mode, a violation in Fast-mode, because Table 10 states the floor only for Fast-mode. A monitor with one hard-coded edge rule gets one of the two wrong.
Test 11 is about the denominator. If an aborted edge were counted, cells_seen would climb on a bus that is glitching rather than transferring, and the legality ratio everything else reports would be measured against a number that means nothing.
9. Mutation Testing
Eleven defects injected into the SystemVerilog monitor, each a single-token change, each required to make the suite fail.
| # | injected defect | outcome |
|---|---|---|
| S1 | Standard tLOW(min) 4700 → 4600 ns | killed — test 1's margin is no longer zero |
| S2 | Fast tHIGH(min) 600 → 500 ns | killed — 3 checks |
| S3 | Fast tr(max) 300 → 400 ns | killed — 6 checks, tests 7 and 10a |
| S4 | the Fast tr(min) floor removed | killed — test 8 |
| S5 | Fast period floor 2500 → 2300 ns | killed — test 4 stops failing |
| S6 | the crossing tick dropped from tLOW | killed — 51 checks |
| S7 | the period margin inverted | killed — 30 checks |
| S8 | the period ranked first, breaking the tie order | killed — 6 checks |
| S9 | an aborted rising edge accepted as a cell | killed — test 11 |
| S10 | every cell counted as legal | killed — test 13 |
| S11 | the tr margin made one-sided again | killed — 4 checks |
Eleven of eleven. Three are worth a comment.
S1 changes a limit by 100 ns and exactly one check fails. That is the signature of a suite testing the boundary rather than the interior: the only cell that can detect a 2 % shift in tLOW(min) is the one sitting exactly on it. A suite built from comfortable mid-range stimulus would have missed it entirely, and this is the argument for writing tests 1 to 3 at the limits rather than near them.
S6 fails 51 checks, and that breadth is the point. An off-by-one in the measurement convention is not a local defect; it corrupts every number the block produces. The contrast with S1 is the useful part — a suite where every mutation kills with a similar count is a suite that is only testing one thing.
S5 is the inverse of the others. It widens the period floor so test 4 stops failing, which means the mutation is detected by a test that was asserting a failure. A suite that only ever asserts success cannot catch a limit being loosened, and four of this suite's fifteen cells exist to assert specific violations for that reason.
10. Verification Connection — Parameterising A Mode Rather Than Copying A Bench
A UVM environment for a multi-speed I²C VIP faces a choice that looks cosmetic and is not: is the speed mode a configuration field or a separate agent?
The budget identity settles it. Because the four numbers are one constraint, a mode is a single coherent object and splitting it across three agents means three chances for the numbers to drift apart.
// A speed mode is FOUR NUMBERS THAT MUST SUM TO A FIFTH. Holding them in one
// config object and checking the identity in the constructor means a typo is a
// build-time failure rather than a mysterious timing violation three thousand
// transactions into a regression.
class i2c_mode_cfg extends uvm_object;
`uvm_object_utils(i2c_mode_cfg)
typedef enum { SM, FM, FMP } mode_e;
rand mode_e mode;
int unsigned tlow_min_ns, thigh_min_ns, tr_max_ns, tf_max_ns, fscl_max_khz;
function new(string name = "i2c_mode_cfg");
super.new(name);
endfunction
// UM10204 Table 10, as a single table rather than three scattered constants.
function void apply(mode_e m);
mode = m;
case (m)
SM : begin tlow_min_ns=4700; thigh_min_ns=4000; tr_max_ns=1000; tf_max_ns=300; fscl_max_khz=100; end
FM : begin tlow_min_ns=1300; thigh_min_ns= 600; tr_max_ns= 300; tf_max_ns=300; fscl_max_khz=400; end
FMP: begin tlow_min_ns= 500; thigh_min_ns= 260; tr_max_ns= 120; tf_max_ns=120; fscl_max_khz=1000; end
endcase
check_identity();
endfunction
// THE IDENTITY, AS A BUILD-TIME ASSERTION. If someone edits one number in
// the table above without editing the frequency, this fires immediately and
// names the mode -- rather than producing a VIP that drives a subtly illegal
// clock and blames the DUT.
function void check_identity();
int unsigned budget_ns = tlow_min_ns + thigh_min_ns + tr_max_ns + tf_max_ns;
int unsigned period_ns = 1_000_000 / fscl_max_khz;
if (budget_ns != period_ns)
`uvm_fatal("I2C_MODE",
$sformatf("Table 10 identity broken for %s: %0d+%0d+%0d+%0d = %0d ns but 1/fSCL = %0d ns",
mode.name(), tlow_min_ns, thigh_min_ns, tr_max_ns, tf_max_ns,
budget_ns, period_ns))
endfunction
// The bit period a generator may legally use. Note it is the MAXIMUM of the
// frequency floor and the phase-plus-edge sum, not either one alone -- which
// is the whole content of Chapter 11.9's budget.
function int unsigned min_period_ns(int unsigned tr_actual, int unsigned tf_actual);
int unsigned from_phases = tlow_min_ns + thigh_min_ns + tr_actual + tf_actual;
int unsigned from_fscl = 1_000_000 / fscl_max_khz;
return (from_phases > from_fscl) ? from_phases : from_fscl;
endfunction
endclassTwo things that class does which a scattered set of parameters cannot.
It fails at build time. check_identity runs in apply, so a mis-edited table is a uvm_fatal naming the mode, not a regression failure five thousand transactions later pointing at the DUT. This is the cheapest assertion in the environment and it protects the assumption every timing check downstream depends on.
It returns the binding period, not the frequency period. min_period_ns takes the larger of the two constraints. A generator that used 1/fSCL alone would drive a legal-frequency clock with illegal phases the moment the actual edges were slower than the table's maxima — which is the ordinary situation on a real board, as §7.2.1's remark says outright.
11. FPGA and ASIC Implications
The three modes are one RTL and three constants. Because the protocol is identical, a controller supporting all three needs one datapath and a mode-selected divider — not three state machines. Anything else is a sign that speed has leaked into the protocol logic, and it will diverge under maintenance.
The edge rates are not yours to choose on an FPGA. tr is set by the pull-up and the capacitance, and tf by the pad's drive strength and slope control. A generic FPGA I/O is a fast, strong CMOS driver with no slope control at all, which makes tf too short rather than too long — the opposite of the problem most people expect. Fast-mode's 20 ns tr floor exists for the same physical reason, and Chapter 14.4 sizes the resistor that fixes it.
Slope control is a Fast-mode requirement, not an optimisation. §5.1 lists it among the mandatory additions: "The output buffers of Fast-mode devices incorporate slope control of the falling edges of the SDA and SCL signals." On an FPGA that means selecting a slew-rate-limited I/O standard, and on an ASIC it means an output cell with a controlled edge. A design that meets Table 10's numbers with an uncontrolled edge has met the timing and missed the requirement.
Fm+ changes the pad, not the logic. 20 mA at 0.4 V from an I²C pad is a real specification on the output cell, and it is the entire reason Fm+ can drive 550 pF. If the pads cannot sink 20 mA, the part is a Fast-mode part running at 1 MHz into a small load — which may well work on the bench and will not work on the customer's longer bus.
Derate with Equation 3, not with the headline number. Measure the actual tr and tf on the assembled board, put them into fmax = 1/(tLOW+tHIGH+tr+tf), and use the result. The headline rate assumes the table's edge maxima, and a board with slower edges than that is not a Fast-mode bus no matter what the controller is configured for.
12. Debugging — The Bus That Broke When The Pull-Up Was Improved
A Fast-mode bus with a marginal rise time was fixed by dropping the pull-up from 4.7 kΩ to 1.5 kΩ. The rise time went from 340 ns to 110 ns and the scope showed a clean edge. Two slaves that had worked previously began NACKing intermittently, and one stopped responding entirely.
The slow rise time had been silently derating the bus below 400 kHz, and the four Table 10 limits are one budget. Removing 230 ns from the rising edge removed 230 ns from the period, pushing the clock to 437 kHz — over the Fast-mode maximum. Nothing about the edge was wrong; the period that the edge had been padding was never re-checked. The Standard-mode part had been out of specification all along and was only working because the real rate was near its own limit.
Re-derive the divider from Equation 3 using the measured edges: 1300 + 620 + 110 + 250 = 2280 ns is the phase-plus-edge floor, but 1/400 kHz = 2500 ns is the binding constraint, so the period must be lengthened to 2.5 µs. Then remove the Standard-mode part or drop the whole bus to 100 kbit/s, because §5.1 says it should not be on a Fast-mode bus at all.The shape of this is worth keeping. An improvement to one parameter consumed the margin of another, because they share a budget. Nothing was measured wrongly and no device was faulty. The period was simply never a free variable, and improving the edge spent it.
It is also the mechanism behind the previous chapter's closing observation: a bus can be out of specification for years and work, because the violation is masked by a second violation in the opposite direction. Fix one and the other surfaces.
13. Common Misconceptions
"Fast-mode is a faster protocol." It is the same protocol with four different numbers. There is no Fast-mode condition, byte format, address, or state that Standard-mode lacks.
"A Fast-mode device runs at 400 kbit/s." The specification's requirement is that it can synchronize with a 400 kbit/s transfer and can stretch. Throughput is not the conformance criterion. §5.
"The four timing limits are independent constraints to check off." They sum exactly to the period in all three modes. Satisfying each individually is necessary and not sufficient. §4a.
"A faster edge is always an improvement." It shortens the period, and the period has a floor. §4a and §12.
"Standard-mode parts are fine on a Fast-mode bus if the master slows down." If the master genuinely runs the whole bus at 100 kbit/s then it is a Standard-mode bus and everything is fine. What §5.1 warns against is a Fast-mode bus with a Standard-mode part on it, where the part sees phases it cannot resolve.
"Fm+ is Fast-mode at 1 MHz." Fm+ specifies a 20 mA output stage against Fast-mode's 3 mA. That is a different pad, and it is what allows 550 pF instead of 400 pF. Chapter 14.4 §5.
"tr only has a maximum." Fast-mode gives it a 20 ns minimum. An edge can be too fast. §6.
"Rise time is a property of the driver." On an open-drain bus the rising edge is made by the resistor and the capacitance, not by any driver. Only the falling edge belongs to the device. Chapter 2.5.
14. Reason It Through
A bus measures tLOW = 4.8 µs, tHIGH = 4.1 µs, tr = 900 ns, tf = 280 ns. Which modes is it legal in?
Sum: 4800 + 4100 + 900 + 280 = 10 080 ns, so 99.2 kHz. Against Standard-mode: every phase is above its minimum, both edges are inside their maxima, and the period exceeds 10 000 ns. Legal. Against Fast-mode: tr of 900 ns is three times the 300 ns allowance. Illegal, on that parameter alone — exactly the shape of §6's first row.
Same bus, and someone asks whether it could be run at 400 kHz by just changing the divider. No. At a 2.5 µs period the 900 ns rise plus the 280 ns fall leave 1320 ns for both phases together, and Fast-mode needs 1300 + 600 = 1900 ns of phase. The bus is 580 ns short before the divider is even considered. The rise time has to come down first, which means the pull-up or the capacitance has to change — it is a board problem, not a configuration one.
A Fm+ controller drives a bus whose measured edges are tr = 300 ns and tf = 250 ns. What is the real maximum rate?
Equation 3 with Fm+'s phase minima: 500 + 260 + 300 + 250 = 1310 ns, so 763 kHz — not 1 MHz. The edges are legal for Standard-mode but far outside Fm+'s own 120 ns maxima, and the phase minima cannot absorb the difference. Configuring the controller for 1 MHz on this board produces phases below Fm+'s minima, which is §12's failure with the numbers changed.
Why can a cell sitting exactly on all five limits report only one binding constraint?
Because five margins of zero is a five-way tie, and the monitor's documented rule resolves ties in table order. That is a reporting convention, not a measurement: the honest reading of binding = tLOW with worst_margin = 0 is "the tightest margin is zero and at least one constraint is tLOW". Test 1 asserts the convention so it cannot drift.
A device datasheet claims Fast-mode compliance but specifies a maximum SCL frequency of 250 kHz. Is that contradictory?
No, and §5 is why. The compliance requirement is the ability to synchronize with a 400 kbit/s transfer and to stretch. A part that can track 400 kHz edges, holds SCL low when it needs time, and cannot process faster than 250 kHz of payload is conforming. What would be contradictory is a part claiming Fast-mode compliance while unable to stretch — because then a 400 kHz master has no way to accommodate it.
15. Understanding Check
16. Summary
The protocol does not change across the three mainstream modes. Four numbers in Table 10 change, and one drive current in Table 9. Everything else — conditions, frames, addresses, acknowledge, stretching, synchronization, arbitration — is identical, which is why thirteen modules could be written without mentioning speed.
The four numbers are one budget. tLOW(min) + tHIGH(min) + tr(max) + tf(max) = 1/fSCL(max), exactly, in all three modes. The table was constructed that way and §7.2.1 states the relation as Equation 3.
So a faster edge can make a clock illegal. Edge time is period, not an addition to it. Improve a rising edge without revisiting the divider and a frequency violation replaces the edge violation — §12 is that bus.
Fast-mode compliance is about synchronizing and stretching, not throughput. A 150 kbit/s device is conforming if it can track 400 kbit/s edges and hold the clock. Both capabilities come from the two preceding modules.
Compatibility is one-way, and one number wide. A legal Standard-mode cell fails Fast-mode on the rise time alone; a legal Fast-mode cell fails Standard-mode on three parameters at once. Going down is safe; going up produces phases a slow input cannot resolve, and nothing on the bus says so.
tr is bounded from below in Fast-mode. 20 ns. An edge can be too fast, and a monitor that ranks only the upper bound reports a violation with positive margin.
Fm+ is a different output stage. 20 mA against 3 mA, and that is what buys 550 pF rather than 400 pF. Its timing is the tightest of the three; its drive is what makes the timing reachable.
Derate from measurement, not from the headline. Equation 3 with the board's real edges is the bus's real maximum rate, and it is usually below the number on the controller's configuration page.
17. What Comes Next
The three modes in this chapter differ in magnitude. The next one differs in kind.
Chapter 14.2 takes High-speed mode, and it is the only speed category that changes the protocol rather than the numbers. Entering it requires a START, an eight-bit master code, and a not-acknowledge — all at Fast-mode speed — after which the winning master switches to a different electrical regime: an open-drain pull-down with a current-source pull-up on the clock, a bus that may be bridged into two segments, and arbitration that is over before the fast phase begins.
Three things from this chapter carry straight into it. The budget identity will be tested against Hs-mode's own table and will fail there — by exactly 2.00 % at both capacitance corners, which turns out to explain where the number 3.4 Mbit/s came from. The tr maximum acquires a second value, trCL1, that applies only after a repeated START and after every acknowledge — and the reason is a current source being deliberately switched off so that a slave can still stretch. And the arbitration of Module 13 decides whether the high-speed phase happens at all, on a single bit.
Continue learning
Related tutorials
- 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
Why Push-Pull Fails on a Shared Bus
An ordinary digital output drives both levels actively, which assumes it owns the node. Attach two of them to one conductor, let them disagree, and the result is not a confused message but a low-impedance path from supply to ground through two output stages — an electrical fault no protocol discipline can prevent.
- Related topic
Open-Drain Outputs — Drive Low, Release High
The architectural move the whole bus rests on, and it is a subtraction: delete every device's ability to drive HIGH. What remains is one switch to ground, so the two states are pull LOW and release — and two devices can never impose opposite levels because only one level can be imposed at all.
- Related topic
Wired-AND — Many Drivers, One Line, No Contention
Put several open-drain devices on one node and a logic function appears in the wiring: any participant asserting LOW wins, and HIGH requires unanimous release. Derive dominant LOW, see why two devices pulling together is agreement rather than conflict, and watch three of the protocol's mechanisms become predictable.
