I²C · Module 11
Building an I²C Timing Budget
Eight chapters established sixteen parameters; this one adds them up and finds that per-parameter compliance is necessary and not sufficient. Builds the budget as synthesisable hardware and closes every deficit the module uncovered.
Eight chapters, sixteen parameters, and every one of them checked against its own limit. This chapter does the thing none of them could do alone.
Every parameter can be individually compliant and the transfer can still fail.
That is not a subtlety — it is the module's central result, and three chapters found it independently before this one set out to. Chapter 11.2 found the clock's four terms exactly filling the period with zero slack. Chapter 11.4 found the low phase's three terms overrunning at Fast-mode Plus by exactly tr(max). Chapter 11.8 found a filter's latency turning a 30 ns margin into a 30 ns deficit with nothing out of specification anywhere.
A limit says what one parameter may be. A budget says whether they fit together. This chapter builds that as hardware.
1. Everything the Module Found, in One Place
Four results, one pattern: the specification's maxima bound the legal region; they are not a corner you may sit in. And each time a sum overran, the deficit named a term:
| deficit | equals | so the term to beat is |
|---|---|---|
| result 2, −120 ns | tr(max) exactly | the rise time — a current-source pull-up |
| result 3, an empty window | Rp(min) exceeding Rp(max) | the driver's sink current — 20 mA rather than 3 mA |
That is a diagnostic worth keeping. When a worst-case sum overruns by precisely one of its terms, the specification is telling you which term it does not expect you to take at maximum.
2. Two Budgets, Not One
The sixteen parameters resolve into exactly two sums, and the two are structurally different.
The period budget is about the clock generator. Its terms are the two phases a master produces and the two edges the board produces, and its limit is the period the target frequency allows. It is a master's budget, and a board's.
The low-phase budget is about the transmitter and the receiver. Its terms are the hold a device must provide, the response time it takes, the filter latency it adds, the settling the line needs and the setup the receiver requires — and its limit is the low phase the clock actually provides.
| period budget | low-phase budget | |
|---|---|---|
| owner | the master's SCL generator | the transmitting device |
| limit | 1/fSCL at the target frequency | the tLOW the clock provides |
| terms | tLOW, tHIGH, tr, tf | tVD;DAT, tSU;DAT, tr, filter |
| who can fix a deficit | the master, or the board | the device, or the master |
And they are coupled through tLOW, which appears as a term in one and as the limit of the other. That coupling is the whole reason the two must be computed together, and it is the source of the modelling error §3 records.
3. The Modelling Error That Cost the Design a Rewrite
The block's first version had a port called t_low_min, holding the specification's minimum low phase. That was wrong, and the way it was wrong is the most useful thing in this chapter.
tLOW is not one quantity. It is two, and they play opposite roles:
| what it is | role | |
|---|---|---|
tLOW(min) | the specification's floor | a constraint the design must clear |
| the actual low phase | what the master's generator produces | a resource the low-phase budget spends |
The first version conflated them, so the low-phase budget was checked against the specification minimum rather than against the low phase the design actually provides. That makes the block answer a question nobody asked: "would these terms fit inside the shortest legal low phase?" — when the question that matters is "do they fit inside the low phase this clock produces?"
The consequence was a block that could not model the correct fix. The standard response to a low-phase deficit is to lengthen the low phase, which is legal without limit (Chapter 11.2 §1: tLOW has no maximum). A block checking against the minimum shows no improvement when you do that, because the number it compares against does not move.
The port was renamed t_low, documented as the low phase the design provides, and the two budgets then compose correctly: t_low is spent by the low-phase budget and contributed to the period budget. §7's test 8 lengthens both phases and watches the low-phase budget close while the period budget tightens — which is the actual trade, and which the first version could not express.
4. The Low Phase That Does Not Close, Drawn
620 ns of obligation into a 500 ns low phase
10 cyclesThe figure is the module's argument in one image. Every term is at its own limit and none is violated — the 450 ns is tVD;DAT(max), the 50 ns is tSU;DAT(min), the 120 ns is tr(max) — and the sum does not fit. There is no parameter to point at.
Note which term is absent: footnote [3]'s 300 ns hold obligation. It is measured from the same falling edge as tVD;DAT, so the two are a floor and a ceiling on one transition rather than consecutive intervals (Chapter 11.4 §3), and adding them would double-count. A filter's latency, by contrast, genuinely does precede the response and is a fourth term — Chapter 11.8 §4 works that case, and §7's tests 11 and 12 confirm it lands on this budget and not on the period.
5. Margin and Deficit as Two Unsigned Outputs
A small implementation decision with a real justification. The block could report one signed number — positive for margin, negative for deficit. It reports two unsigned ones instead.
Because the two mean different things to different readers. A margin is a design-quality metric: how much headroom is there, and is it enough for temperature and process variation? A deficit is a bug report: this configuration cannot work, and here is by how much. Collapsing them into one signed value makes every consumer test the sign before it knows which kind of number it has.
And because a signed subtraction in RTL invites the classic width error. period_avail - per_req on unsigned operands wraps to an enormous positive number when it should be negative, and the wrap is silent. Computing the two directions separately under an explicit comparison makes the intent unmistakable:
// The comparison is made ONCE, combinationally, and both outputs are derived from it. The
// alternative -- one signed difference -- wraps silently on unsigned operands and forces every
// reader to check a sign before knowing whether it holds good news or a bug.
assign per_req = t_low + t_high + t_r + t_f;
assign low_req = t_vd_dat + t_su_dat + t_r + filter_latency;
assign per_ok_c = (per_req <= period_avail); // <= , not < : an exact fit is LEGAL, and
assign low_ok_c = (low_req <= t_low); // section 1 shows the spec's own values land there
// Gated on `evaluate`, so a verdict belongs to one configuration rather than flickering as
// inputs settle. A budget is evaluated for a design, not sampled continuously.
always_ff @(posedge clk) if (evaluate) begin
period_margin <= per_ok_c ? (period_avail - per_req) : '0;
period_deficit <= per_ok_c ? '0 : (per_req - period_avail);
low_margin <= low_ok_c ? (t_low - low_req) : '0;
low_deficit <= low_ok_c ? '0 : (low_req - t_low);
budget_ok <= per_ok_c && low_ok_c;
valid <= 1'b1;
endNote budget_ok is the conjunction of both budgets. §7's tests 5 to 8 are Fast-mode Plus with its period budget closing and its low-phase budget failing, and the aggregate must be false — a block reporting only the budget it happened to check first is worse than one reporting neither. Mutation I3 is that defect.
6. The Budget Block in Three Languages
Two things the diagram makes visible that the code does not.
t_r feeds both budgets. The rise time is spent twice — once as part of the clock period and once as part of the low phase's settling — which is why it is the term that appears in every deficit the module found.
t_low feeds a sum and a comparator. §3's point, drawn: it is a resource for one budget and a contribution to the other, and that dual role is exactly what the original t_low_min naming obscured.
// AN I2C TIMING BUDGET, in hardware. This is the capstone of Module 11: it takes the
// parameters the other eight blocks measure and answers the one question a bring-up
// actually asks -- does this bus close?
//
// It checks THREE budgets, and they are genuinely different constraints rather than three
// views of one.
//
// 1. THE PERIOD BUDGET. tLOW + tHIGH + tr + tf <= 1/fSCL
//
// Table 10 makes this an EXACT identity at the limits, in all three speed modes:
// Standard 4.7 + 4.0 + 1.000 + 0.300 = 10.0 us = 1/100 kHz
// Fast 1.3 + 0.6 + 0.300 + 0.300 = 2.5 us = 1/400 kHz
// Fm+ 0.5 + 0.26 + 0.120 + 0.120 = 1.0 us = 1/1000 kHz
// So at the limits there is ZERO margin. Any real design must therefore beat at least
// one of the four terms, and which one it beats is a board-level decision: a smaller
// pull-up buys rise time, a slower clock buys everything.
//
// 2. THE LOW-PHASE BUDGET. tVD;DAT + tSU;DAT + tr <= tLOW
//
// Inside the low phase the transmitter must get the next bit out (tVD;DAT), the line
// must settle (tr), and the receiver must see it stable before the rising edge
// (tSU;DAT). Checking the numbers:
// Standard 3.45 + 0.250 + 1.000 = 4.700 us vs tLOW 4.7 -> EXACTLY tight
// Fast 0.90 + 0.100 + 0.300 = 1.300 us vs tLOW 1.3 -> EXACTLY tight
// Fm+ 0.45 + 0.050 + 0.120 = 0.620 us vs tLOW 0.5 -> DOES NOT FIT
// Fast-mode Plus does not close on the table's own worst-case numbers. That is not an
// error in the table; it means an Fm+ transmitter cannot take its full tVD;DAT on a bus
// with its full rise time -- something has to be better than worst case. Worth knowing
// before designing an Fm+ device, and not visible from any single parameter.
//
// 3. THE FILTER BUDGET. the spike filter's latency comes off the low phase too.
//
// Chapter 11.8's filter delays every transition by T_SP + 1 samples. That delay is real
// and lands inside the low phase, so a design with a spike filter has less room than the
// table's arithmetic suggests. A budget that ignored it would close on paper and fail on
// silicon.
//
// The block is combinational arithmetic with registered outputs. Everything is in ticks of
// one sample clock, so a user converts the table once and the hardware never deals in
// nanoseconds -- which is also why the checkers in this module take ticks.
module i2c_timing_budget #(
parameter int TICK_W = 16
)(
input logic clk,
input logic rst_n,
input logic evaluate, // pulse: latch a verdict for these inputs
// ---- the budget's inputs, all in sample-clock ticks ----
// The low phase this design actually PROVIDES -- not the table's minimum. The two are
// different numbers and conflating them is a real modelling error: raising the clock
// period does not change a spec minimum, but it does lengthen the low phase a design
// gives, and it is the latter the low-phase budget is spent out of. That tLOW also has
// to meet the table's minimum is a separate check, and Chapter 11.2's block makes it on
// the wire.
input logic [TICK_W-1:0] t_low,
input logic [TICK_W-1:0] t_high,
input logic [TICK_W-1:0] t_r,
input logic [TICK_W-1:0] t_f,
input logic [TICK_W-1:0] period_avail, // 1 / fSCL(max)
input logic [TICK_W-1:0] t_vd_dat,
input logic [TICK_W-1:0] t_su_dat,
input logic [TICK_W-1:0] filter_latency, // Chapter 11.8: T_SP + 1, or zero
// ---- the period budget ----
output logic valid,
output logic [TICK_W-1:0] period_required,
output logic period_ok,
output logic [TICK_W-1:0] period_margin, // slack when ok, ZERO when not
output logic [TICK_W-1:0] period_deficit, // shortfall when not ok, ZERO when ok
// ---- the low-phase budget, including the filter ----
output logic [TICK_W-1:0] low_required,
output logic low_ok,
output logic [TICK_W-1:0] low_margin,
output logic [TICK_W-1:0] low_deficit,
// ---- the overall verdict ----
output logic budget_ok
);
// Margins are reported as two unsigned numbers rather than one signed one. That is not
// squeamishness about signed arithmetic: a budget report is read by people, and
// "3 ticks of margin" and "3 ticks short" are different sentences. A single signed
// number invites a reader to miss the sign, which on a budget is the whole answer.
logic [TICK_W-1:0] per_req, low_req;
logic per_ok_c, low_ok_c;
assign per_req = t_low + t_high + t_r + t_f;
// The filter latency is added to the LOW-phase requirement, not the period: it delays
// the data becoming valid, which is a low-phase obligation. Adding it to the period
// instead would be a plausible-looking error that under-reports the pressure on tLOW.
assign low_req = t_vd_dat + t_su_dat + t_r + filter_latency;
assign per_ok_c = (per_req <= period_avail);
assign low_ok_c = (low_req <= t_low);
always_ff @(posedge clk) begin
if (!rst_n) begin
valid <= 1'b0;
period_required <= '0;
period_ok <= 1'b0;
period_margin <= '0;
period_deficit <= '0;
low_required <= '0;
low_ok <= 1'b0;
low_margin <= '0;
low_deficit <= '0;
budget_ok <= 1'b0;
end else begin
valid <= 1'b0;
if (evaluate) begin
valid <= 1'b1;
period_required <= per_req;
period_ok <= per_ok_c;
period_margin <= per_ok_c ? (period_avail - per_req) : '0;
period_deficit <= per_ok_c ? '0 : (per_req - period_avail);
low_required <= low_req;
low_ok <= low_ok_c;
low_margin <= low_ok_c ? (t_low - low_req) : '0;
low_deficit <= low_ok_c ? '0 : (low_req - t_low);
// The overall verdict is the AND of every constraint. A budget that closes
// on the period and fails on the low phase does not close -- and reporting
// only the headline number is how a design ships with a violation that was
// computed and then not looked at.
budget_ok <= per_ok_c && low_ok_c;
end
end
end
endmodule `timescale 1ns/1ps
// The three real speed modes, converted from Table 10 to ticks of a 1 GHz reference so
// every value in the table lands on an integer: one tick is 1 ns.
//
// tLOW tHIGH tr tf 1/fSCL tVD;DAT tSU;DAT
// Standard-mode 4700 4000 1000 300 10000 3450 250
// Fast-mode 1300 600 300 300 2500 900 100
// Fast-mode Plus 500 260 120 120 1000 450 50
//
// The testbench asserts the EXACT identities from Table 10 rather than approximate ones,
// because they are exact: if any of them came out with margin, either the table was
// transcribed wrongly or the arithmetic is wrong, and both are worth failing on.
module i2c_timing_budget_tb;
localparam int TICK_W = 16;
logic clk = 1'b0;
always #5 clk = ~clk;
logic rst_n = 1'b0;
logic evaluate = 1'b0;
logic [TICK_W-1:0] t_low = '0, t_high = '0, t_r = '0, t_f = '0;
logic [TICK_W-1:0] period_avail = '0, t_vd_dat = '0, t_su_dat = '0, filter_latency = '0;
logic valid, period_ok, low_ok, budget_ok;
logic [TICK_W-1:0] period_required, period_margin, period_deficit;
logic [TICK_W-1:0] low_required, low_margin, low_deficit;
int errors = 0;
logic [TICK_W-1:0] held;
int n_valid;
int nv_before;
i2c_timing_budget #(.TICK_W(TICK_W)) dut (.*);
// `valid` is a one-cycle pulse, so it is COUNTED rather than sampled after the fact --
// the same lesson Chapter 8.1's testbench records about waiting on a level.
always @(posedge clk) if (rst_n && valid) n_valid++;
initial begin #200000; $display("FAIL: watchdog expired"); $finish; end
task automatic tick(input int n);
begin repeat (n) @(negedge clk); end
endtask
// Load one speed mode and evaluate it.
task automatic eval_mode(input int lo, input int hi, input int tr, input int tf,
input int per, input int vd, input int su, input int fl);
begin
t_low = lo[TICK_W-1:0];
t_high = hi[TICK_W-1:0];
t_r = tr[TICK_W-1:0];
t_f = tf[TICK_W-1:0];
period_avail = per[TICK_W-1:0];
t_vd_dat = vd[TICK_W-1:0];
t_su_dat = su[TICK_W-1:0];
filter_latency = fl[TICK_W-1:0];
tick(2);
evaluate = 1'b1; @(negedge clk); evaluate = 1'b0;
tick(2);
end
endtask
initial begin
tick(3);
if (valid !== 1'b0) begin $display("FAIL: valid out of reset"); errors++; end
rst_n = 1'b1; tick(2);
// ================================================================
// 1 — STANDARD-MODE at the table's limits, no spike filter.
// The period budget must close EXACTLY: 4700+4000+1000+300 = 10000.
// ================================================================
nv_before = n_valid;
eval_mode(4700, 4000, 1000, 300, 10000, 3450, 250, 0);
if (n_valid !== nv_before + 1) begin
$display("FAIL: %0d verdict pulses from one evaluation", n_valid - nv_before);
errors++; end
if (period_required !== 16'd10000) begin
$display("FAIL: Standard period required %0d, expected 10000", period_required);
errors++; end
if (period_ok !== 1'b1) begin
$display("FAIL: the Standard-mode period budget did not close"); errors++; end
if (period_margin !== '0) begin
$display("FAIL: Standard period margin %0d -- Table 10's identity is EXACT, so it must be ZERO",
period_margin); errors++; end
if (period_deficit !== '0) begin
$display("FAIL: Standard period deficit %0d with the budget closing", period_deficit);
errors++; end
// The low-phase budget is also exactly tight: 3450+250+1000 = 4700 = tLOW.
if (low_required !== 16'd4700) begin
$display("FAIL: Standard low-phase required %0d, expected 4700", low_required);
errors++; end
if (low_ok !== 1'b1 || low_margin !== '0) begin
$display("FAIL: the Standard low-phase budget should close with EXACTLY zero margin");
errors++; end
if (budget_ok !== 1'b1) begin
$display("FAIL: Standard-mode should close overall"); errors++; end
// ================================================================
// 2 — FAST-MODE at the limits. Both identities exact again.
// ================================================================
eval_mode(1300, 600, 300, 300, 2500, 900, 100, 0);
if (period_required !== 16'd2500 || period_ok !== 1'b1 || period_margin !== '0) begin
$display("FAIL: Fast-mode period: required %0d, ok %b, margin %0d -- expected 2500, 1, 0",
period_required, period_ok, period_margin); errors++; end
if (low_required !== 16'd1300 || low_ok !== 1'b1 || low_margin !== '0) begin
$display("FAIL: Fast-mode low phase: required %0d, ok %b, margin %0d -- expected 1300, 1, 0",
low_required, low_ok, low_margin); errors++; end
if (budget_ok !== 1'b1) begin
$display("FAIL: Fast-mode should close overall"); errors++; end
// ================================================================
// 3 — FAST-MODE PLUS at the limits. The PERIOD budget closes exactly, and the
// LOW-PHASE budget DOES NOT: 450+50+120 = 620 against a tLOW of 500.
//
// This is the finding of the whole chapter. Fm+ does not close on the table's
// own worst-case numbers, which means an Fm+ transmitter cannot take its full
// tVD;DAT on a bus with its full rise time. It is not visible from any single
// parameter and it is not an error in the table -- something simply has to be
// better than worst case.
// ================================================================
eval_mode(500, 260, 120, 120, 1000, 450, 50, 0);
if (period_required !== 16'd1000 || period_ok !== 1'b1 || period_margin !== '0) begin
$display("FAIL: Fm+ period: required %0d, ok %b, margin %0d -- expected 1000, 1, 0",
period_required, period_ok, period_margin); errors++; end
if (low_required !== 16'd620) begin
$display("FAIL: Fm+ low-phase required %0d, expected 620", low_required); errors++; end
if (low_ok !== 1'b0) begin
$display("FAIL: the Fm+ low-phase budget was reported as closing -- 620 > 500");
errors++; end
if (low_deficit !== 16'd120) begin
$display("FAIL: Fm+ low-phase deficit %0d, expected 620 - 500 = 120", low_deficit);
errors++; end
if (low_margin !== '0) begin
$display("FAIL: a failing budget reported %0d of margin -- margin and deficit must not both be nonzero",
low_margin); errors++; end
// And the overall verdict must be FALSE even though the headline period closed.
if (budget_ok !== 1'b0) begin
$display("FAIL: Fm+ closed overall while its low-phase budget failed -- the overall verdict must be the AND of every constraint");
errors++; end
// ================================================================
// 4 — a REAL Fast-mode design that beats the table. A 250 ns rise instead of 300,
// and a transmitter that responds in 400 ns instead of 900. Both budgets now
// close with genuine margin, which is what a working design looks like.
// ================================================================
eval_mode(1300, 600, 250, 300, 2500, 400, 100, 0);
if (period_ok !== 1'b1 || period_margin !== 16'd50) begin
$display("FAIL: a 50 ns faster rise should give exactly 50 ticks of period margin, got %0d",
period_margin); errors++; end
if (low_ok !== 1'b1 || low_margin !== 16'd550) begin
$display("FAIL: low-phase margin %0d, expected 1300 - 750 = 550", low_margin);
errors++; end
if (budget_ok !== 1'b1) begin
$display("FAIL: a design with margin on both budgets should close"); errors++; end
// ================================================================
// 5 — THE SPIKE FILTER'S LATENCY IS REAL. The same design as test 4, now with a
// Fast-mode spike filter whose latency is T_SP + 1 = 6 ticks at 1 ns each...
// except tSP is 50 ns, so at this reference the latency is 51 ticks. It comes
// off the LOW phase, not the period.
// ================================================================
eval_mode(1300, 600, 250, 300, 2500, 400, 100, 51);
if (low_required !== 16'd801) begin
$display("FAIL: with a 51-tick filter the low phase needs %0d, expected 750 + 51 = 801",
low_required); errors++; end
if (low_margin !== 16'd499) begin
$display("FAIL: low-phase margin %0d, expected 1300 - 801 = 499", low_margin);
errors++; end
// The PERIOD requirement must be UNCHANGED: the filter delays data, not the clock's
// own phases. Adding it to the period would be a plausible-looking error.
if (period_required !== 16'd2450) begin
$display("FAIL: the filter latency changed the PERIOD requirement to %0d -- it belongs to the low phase",
period_required); errors++; end
// ================================================================
// 6 — a filter big enough to BREAK an otherwise-closing budget. This is the case
// the chapter is for: the arithmetic closes on paper and fails once the filter
// is accounted for.
// ================================================================
eval_mode(1300, 600, 250, 300, 2500, 900, 100, 51);
if (low_required !== 16'd1301) begin
$display("FAIL: low-phase required %0d, expected 900+100+250+51 = 1301", low_required);
errors++; end
if (low_ok !== 1'b0 || low_deficit !== 16'd1) begin
$display("FAIL: a budget missing by ONE tick was reported ok=%b deficit=%0d",
low_ok, low_deficit); errors++; end
if (budget_ok !== 1'b0) begin
$display("FAIL: a one-tick deficit must not close"); errors++; end
// ================================================================
// 7 — THE BOUNDARY of the comparison itself. Required exactly equal to available
// must CLOSE, because a budget that exactly fits is met. One tick more must not.
// ================================================================
eval_mode(1000, 500, 100, 100, 1700, 100, 100, 0);
if (period_required !== 16'd1700 || period_ok !== 1'b1) begin
$display("FAIL: a period requirement exactly equal to what is available was rejected");
errors++; end
eval_mode(1000, 500, 100, 101, 1700, 100, 100, 0);
if (period_ok !== 1'b0 || period_deficit !== 16'd1) begin
$display("FAIL: a one-tick over-budget was accepted (ok=%b deficit=%0d)",
period_ok, period_deficit); errors++; end
// And the MARGIN must be zero. Computing it unconditionally would underflow to a huge
// number on a failing budget, so a report would show both an enormous margin and a
// deficit -- and a reader cannot tell from that which one to believe.
if (period_margin !== '0) begin
$display("FAIL: a failing period budget reported %0d of margin -- margin and deficit must never both be nonzero",
period_margin); errors++; end
// ================================================================
// 8 — a SLOW BUS. Halving the clock rate and spending the extra time on both phases
// fixes BOTH budgets, and the distinction matters: raising period_avail alone
// would fix the period and leave the low phase exactly as tight as before,
// because the low-phase budget is spent out of the tLOW a design PROVIDES. This
// is the universal remedy and the only lever that moves every constraint at once.
// ================================================================
eval_mode(2600, 1200, 300, 300, 5000, 900, 100, 51);
if (period_required !== 16'd4400 || period_ok !== 1'b1 || period_margin !== 16'd600) begin
$display("FAIL: a half-rate bus: required %0d, ok %b, margin %0d -- expected 4400, 1, 600",
period_required, period_ok, period_margin); errors++; end
if (low_ok !== 1'b1 || low_margin !== 16'd1249) begin
$display("FAIL: low phase at half rate: ok %b, margin %0d -- expected 1, 1249",
low_ok, low_margin); errors++; end
if (budget_ok !== 1'b1) begin
$display("FAIL: a bus at half rate with both phases lengthened should close");
errors++; end
// ================================================================
// 9 — the verdict is only produced when ASKED. Without an evaluate pulse the
// outputs must hold, so a consumer cannot read a half-updated budget.
// ================================================================
begin
held = period_required;
t_low = 16'd9999; // change the inputs without evaluating
tick(10);
if (period_required !== held) begin
$display("FAIL: the outputs moved without an evaluate pulse"); errors++; end
if (valid !== 1'b0) begin
$display("FAIL: valid is asserted outside an evaluation"); errors++; end
end
if (errors == 0)
$display("PASS: all three of Table 10's period identities are exact, Fm+ does not close its low phase, the filter latency lands on the low phase only, margin and deficit are never both nonzero");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule // AN I2C TIMING BUDGET, in hardware. This is the capstone of Module 11: it takes the
// parameters the other eight blocks measure and answers the one question a bring-up
// actually asks -- does this bus close?
//
// It checks THREE budgets, and they are genuinely different constraints rather than three
// views of one.
//
// 1. THE PERIOD BUDGET. tLOW + tHIGH + tr + tf <= 1/fSCL
//
// Table 10 makes this an EXACT identity at the limits, in all three speed modes:
// Standard 4.7 + 4.0 + 1.000 + 0.300 = 10.0 us = 1/100 kHz
// Fast 1.3 + 0.6 + 0.300 + 0.300 = 2.5 us = 1/400 kHz
// Fm+ 0.5 + 0.26 + 0.120 + 0.120 = 1.0 us = 1/1000 kHz
// So at the limits there is ZERO margin. Any real design must therefore beat at least
// one of the four terms, and which one it beats is a board-level decision: a smaller
// pull-up buys rise time, a slower clock buys everything.
//
// 2. THE LOW-PHASE BUDGET. tVD;DAT + tSU;DAT + tr <= tLOW
//
// Inside the low phase the transmitter must get the next bit out (tVD;DAT), the line
// must settle (tr), and the receiver must see it stable before the rising edge
// (tSU;DAT). Checking the numbers:
// Standard 3.45 + 0.250 + 1.000 = 4.700 us vs tLOW 4.7 -> EXACTLY tight
// Fast 0.90 + 0.100 + 0.300 = 1.300 us vs tLOW 1.3 -> EXACTLY tight
// Fm+ 0.45 + 0.050 + 0.120 = 0.620 us vs tLOW 0.5 -> DOES NOT FIT
// Fast-mode Plus does not close on the table's own worst-case numbers. That is not an
// error in the table; it means an Fm+ transmitter cannot take its full tVD;DAT on a bus
// with its full rise time -- something has to be better than worst case. Worth knowing
// before designing an Fm+ device, and not visible from any single parameter.
//
// 3. THE FILTER BUDGET. the spike filter's latency comes off the low phase too.
//
// Chapter 11.8's filter delays every transition by T_SP + 1 samples. That delay is real
// and lands inside the low phase, so a design with a spike filter has less room than the
// table's arithmetic suggests. A budget that ignored it would close on paper and fail on
// silicon.
//
// The block is combinational arithmetic with registered outputs. Everything is in ticks of
// one sample clock, so a user converts the table once and the hardware never deals in
// nanoseconds -- which is also why the checkers in this module take ticks.
// (Verilog-2001)
module i2c_timing_budget #(
parameter TICK_W = 16
)(
input wire clk,
input wire rst_n,
input wire evaluate, // pulse: latch a verdict for these inputs
// ---- the budget's inputs, all in sample-clock ticks ----
// The low phase this design actually PROVIDES -- not the table's minimum. The two are
// different numbers and conflating them is a real modelling error: raising the clock
// period does not change a spec minimum, but it does lengthen the low phase a design
// gives, and it is the latter the low-phase budget is spent out of. That tLOW also has
// to meet the table's minimum is a separate check, and Chapter 11.2's block makes it on
// the wire.
input wire [TICK_W-1:0] t_low,
input wire [TICK_W-1:0] t_high,
input wire [TICK_W-1:0] t_r,
input wire [TICK_W-1:0] t_f,
input wire [TICK_W-1:0] period_avail, // 1 / fSCL(max)
input wire [TICK_W-1:0] t_vd_dat,
input wire [TICK_W-1:0] t_su_dat,
input wire [TICK_W-1:0] filter_latency, // Chapter 11.8: T_SP + 1, or zero
// ---- the period budget ----
output reg valid,
output reg [TICK_W-1:0] period_required,
output reg period_ok,
output reg [TICK_W-1:0] period_margin, // slack when ok, ZERO when not
output reg [TICK_W-1:0] period_deficit, // shortfall when not ok, ZERO when ok
// ---- the low-phase budget, including the filter ----
output reg [TICK_W-1:0] low_required,
output reg low_ok,
output reg [TICK_W-1:0] low_margin,
output reg [TICK_W-1:0] low_deficit,
// ---- the overall verdict ----
output reg budget_ok
);
// Margins are reported as two unsigned numbers rather than one signed one. That is not
// squeamishness about signed arithmetic: a budget report is read by people, and
// "3 ticks of margin" and "3 ticks short" are different sentences. A single signed
// number invites a reader to miss the sign, which on a budget is the whole answer.
wire [TICK_W-1:0] per_req, low_req;
wire per_ok_c, low_ok_c;
assign per_req = t_low + t_high + t_r + t_f;
// The filter latency is added to the LOW-phase requirement, not the period: it delays
// the data becoming valid, which is a low-phase obligation. Adding it to the period
// instead would be a plausible-looking error that under-reports the pressure on tLOW.
assign low_req = t_vd_dat + t_su_dat + t_r + filter_latency;
assign per_ok_c = (per_req <= period_avail);
assign low_ok_c = (low_req <= t_low);
always @(posedge clk) begin
if (!rst_n) begin
valid <= 1'b0;
period_required <= {TICK_W{1'b0}};
period_ok <= 1'b0;
period_margin <= {TICK_W{1'b0}};
period_deficit <= {TICK_W{1'b0}};
low_required <= {TICK_W{1'b0}};
low_ok <= 1'b0;
low_margin <= {TICK_W{1'b0}};
low_deficit <= {TICK_W{1'b0}};
budget_ok <= 1'b0;
end else begin
valid <= 1'b0;
if (evaluate) begin
valid <= 1'b1;
period_required <= per_req;
period_ok <= per_ok_c;
period_margin <= per_ok_c ? (period_avail - per_req) : {TICK_W{1'b0}};
period_deficit <= per_ok_c ? {TICK_W{1'b0}} : (per_req - period_avail);
low_required <= low_req;
low_ok <= low_ok_c;
low_margin <= low_ok_c ? (t_low - low_req) : {TICK_W{1'b0}};
low_deficit <= low_ok_c ? {TICK_W{1'b0}} : (low_req - t_low);
// The overall verdict is the AND of every constraint. A budget that closes
// on the period and fails on the low phase does not close -- and reporting
// only the headline number is how a design ships with a violation that was
// computed and then not looked at.
budget_ok <= per_ok_c && low_ok_c;
end
end
end
endmodule `timescale 1ns/1ps
// The three real speed modes, converted from Table 10 to ticks of a 1 GHz reference so
// every value in the table lands on an integer: one tick is 1 ns.
//
// tLOW tHIGH tr tf 1/fSCL tVD;DAT tSU;DAT
// Standard-mode 4700 4000 1000 300 10000 3450 250
// Fast-mode 1300 600 300 300 2500 900 100
// Fast-mode Plus 500 260 120 120 1000 450 50
//
// The testbench asserts the EXACT identities from Table 10 rather than approximate ones,
// because they are exact: if any of them came out with margin, either the table was
// transcribed wrongly or the arithmetic is wrong, and both are worth failing on.
module i2c_timing_budget_tb; // Verilog-2001
localparam TICK_W = 16;
reg clk = 1'b0;
always #5 clk = ~clk;
reg rst_n = 1'b0;
reg evaluate = 1'b0;
reg [TICK_W-1:0] t_low = 0, t_high = 0, t_r = 0, t_f = 0;
reg [TICK_W-1:0] period_avail = 0, t_vd_dat = 0, t_su_dat = 0, filter_latency = 0;
wire valid, period_ok, low_ok, budget_ok;
wire [TICK_W-1:0] period_required, period_margin, period_deficit;
wire [TICK_W-1:0] low_required, low_margin, low_deficit;
integer errors = 0;
reg [TICK_W-1:0] held;
integer n_valid = 0;
integer nv_before = 0;
i2c_timing_budget #(.TICK_W(TICK_W)) dut (
.clk(clk), .rst_n(rst_n), .evaluate(evaluate), .t_low(t_low), .t_high(t_high),
.t_r(t_r), .t_f(t_f), .period_avail(period_avail), .t_vd_dat(t_vd_dat),
.t_su_dat(t_su_dat), .filter_latency(filter_latency), .valid(valid),
.period_required(period_required), .period_ok(period_ok),
.period_margin(period_margin), .period_deficit(period_deficit),
.low_required(low_required), .low_ok(low_ok), .low_margin(low_margin),
.low_deficit(low_deficit), .budget_ok(budget_ok));
// `valid` is a one-cycle pulse, so it is COUNTED rather than sampled after the fact --
// the same lesson Chapter 8.1's testbench records about waiting on a level.
always @(posedge clk) if (rst_n && valid) n_valid = n_valid + 1;
initial begin #200000; $display("FAIL: watchdog expired"); $finish; end
task tick;
input integer n;
begin repeat (n) @(negedge clk); end
endtask
// Load one speed mode and evaluate it.
task eval_mode;
input integer lo;
input integer hi;
input integer tr;
input integer tf;
input integer per;
input integer vd;
input integer su;
input integer fl;
begin
t_low = lo;
t_high = hi;
t_r = tr;
t_f = tf;
period_avail = per;
t_vd_dat = vd;
t_su_dat = su;
filter_latency = fl;
tick(2);
evaluate = 1'b1; @(negedge clk); evaluate = 1'b0;
tick(2);
end
endtask
initial begin
tick(3);
if (valid !== 1'b0) begin $display("FAIL: valid out of reset"); errors = errors + 1; end
rst_n = 1'b1; tick(2);
// ================================================================
// 1 — STANDARD-MODE at the table's limits, no spike filter.
// The period budget must close EXACTLY: 4700+4000+1000+300 = 10000.
// ================================================================
nv_before = n_valid;
eval_mode(4700, 4000, 1000, 300, 10000, 3450, 250, 0);
if (n_valid !== nv_before + 1) begin
$display("FAIL: %0d verdict pulses from one evaluation", n_valid - nv_before);
errors = errors + 1; end
if (period_required !== 16'd10000) begin
$display("FAIL: Standard period required %0d, expected 10000", period_required);
errors = errors + 1; end
if (period_ok !== 1'b1) begin
$display("FAIL: the Standard-mode period budget did not close"); errors = errors + 1; end
if (period_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: Standard period margin %0d -- Table 10's identity is EXACT, so it must be ZERO",
period_margin); errors = errors + 1; end
if (period_deficit !== {TICK_W{1'b0}}) begin
$display("FAIL: Standard period deficit %0d with the budget closing", period_deficit);
errors = errors + 1; end
// The low-phase budget is also exactly tight: 3450+250+1000 = 4700 = tLOW.
if (low_required !== 16'd4700) begin
$display("FAIL: Standard low-phase required %0d, expected 4700", low_required);
errors = errors + 1; end
if (low_ok !== 1'b1 || low_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: the Standard low-phase budget should close with EXACTLY zero margin");
errors = errors + 1; end
if (budget_ok !== 1'b1) begin
$display("FAIL: Standard-mode should close overall"); errors = errors + 1; end
// ================================================================
// 2 — FAST-MODE at the limits. Both identities exact again.
// ================================================================
eval_mode(1300, 600, 300, 300, 2500, 900, 100, 0);
if (period_required !== 16'd2500 || period_ok !== 1'b1 || period_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: Fast-mode period: required %0d, ok %b, margin %0d -- expected 2500, 1, 0",
period_required, period_ok, period_margin); errors = errors + 1; end
if (low_required !== 16'd1300 || low_ok !== 1'b1 || low_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: Fast-mode low phase: required %0d, ok %b, margin %0d -- expected 1300, 1, 0",
low_required, low_ok, low_margin); errors = errors + 1; end
if (budget_ok !== 1'b1) begin
$display("FAIL: Fast-mode should close overall"); errors = errors + 1; end
// ================================================================
// 3 — FAST-MODE PLUS at the limits. The PERIOD budget closes exactly, and the
// LOW-PHASE budget DOES NOT: 450+50+120 = 620 against a tLOW of 500.
//
// This is the finding of the whole chapter. Fm+ does not close on the table's
// own worst-case numbers, which means an Fm+ transmitter cannot take its full
// tVD;DAT on a bus with its full rise time. It is not visible from any single
// parameter and it is not an error in the table -- something simply has to be
// better than worst case.
// ================================================================
eval_mode(500, 260, 120, 120, 1000, 450, 50, 0);
if (period_required !== 16'd1000 || period_ok !== 1'b1 || period_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: Fm+ period: required %0d, ok %b, margin %0d -- expected 1000, 1, 0",
period_required, period_ok, period_margin); errors = errors + 1; end
if (low_required !== 16'd620) begin
$display("FAIL: Fm+ low-phase required %0d, expected 620", low_required); errors = errors + 1; end
if (low_ok !== 1'b0) begin
$display("FAIL: the Fm+ low-phase budget was reported as closing -- 620 > 500");
errors = errors + 1; end
if (low_deficit !== 16'd120) begin
$display("FAIL: Fm+ low-phase deficit %0d, expected 620 - 500 = 120", low_deficit);
errors = errors + 1; end
if (low_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: a failing budget reported %0d of margin -- margin and deficit must not both be nonzero",
low_margin); errors = errors + 1; end
// And the overall verdict must be FALSE even though the headline period closed.
if (budget_ok !== 1'b0) begin
$display("FAIL: Fm+ closed overall while its low-phase budget failed -- the overall verdict must be the AND of every constraint");
errors = errors + 1; end
// ================================================================
// 4 — a REAL Fast-mode design that beats the table. A 250 ns rise instead of 300,
// and a transmitter that responds in 400 ns instead of 900. Both budgets now
// close with genuine margin, which is what a working design looks like.
// ================================================================
eval_mode(1300, 600, 250, 300, 2500, 400, 100, 0);
if (period_ok !== 1'b1 || period_margin !== 16'd50) begin
$display("FAIL: a 50 ns faster rise should give exactly 50 ticks of period margin, got %0d",
period_margin); errors = errors + 1; end
if (low_ok !== 1'b1 || low_margin !== 16'd550) begin
$display("FAIL: low-phase margin %0d, expected 1300 - 750 = 550", low_margin);
errors = errors + 1; end
if (budget_ok !== 1'b1) begin
$display("FAIL: a design with margin on both budgets should close"); errors = errors + 1; end
// ================================================================
// 5 — THE SPIKE FILTER'S LATENCY IS REAL. The same design as test 4, now with a
// Fast-mode spike filter whose latency is T_SP + 1 = 6 ticks at 1 ns each...
// except tSP is 50 ns, so at this reference the latency is 51 ticks. It comes
// off the LOW phase, not the period.
// ================================================================
eval_mode(1300, 600, 250, 300, 2500, 400, 100, 51);
if (low_required !== 16'd801) begin
$display("FAIL: with a 51-tick filter the low phase needs %0d, expected 750 + 51 = 801",
low_required); errors = errors + 1; end
if (low_margin !== 16'd499) begin
$display("FAIL: low-phase margin %0d, expected 1300 - 801 = 499", low_margin);
errors = errors + 1; end
// The PERIOD requirement must be UNCHANGED: the filter delays data, not the clock's
// own phases. Adding it to the period would be a plausible-looking error.
if (period_required !== 16'd2450) begin
$display("FAIL: the filter latency changed the PERIOD requirement to %0d -- it belongs to the low phase",
period_required); errors = errors + 1; end
// ================================================================
// 6 — a filter big enough to BREAK an otherwise-closing budget. This is the case
// the chapter is for: the arithmetic closes on paper and fails once the filter
// is accounted for.
// ================================================================
eval_mode(1300, 600, 250, 300, 2500, 900, 100, 51);
if (low_required !== 16'd1301) begin
$display("FAIL: low-phase required %0d, expected 900+100+250+51 = 1301", low_required);
errors = errors + 1; end
if (low_ok !== 1'b0 || low_deficit !== 16'd1) begin
$display("FAIL: a budget missing by ONE tick was reported ok=%b deficit=%0d",
low_ok, low_deficit); errors = errors + 1; end
if (budget_ok !== 1'b0) begin
$display("FAIL: a one-tick deficit must not close"); errors = errors + 1; end
// ================================================================
// 7 — THE BOUNDARY of the comparison itself. Required exactly equal to available
// must CLOSE, because a budget that exactly fits is met. One tick more must not.
// ================================================================
eval_mode(1000, 500, 100, 100, 1700, 100, 100, 0);
if (period_required !== 16'd1700 || period_ok !== 1'b1) begin
$display("FAIL: a period requirement exactly equal to what is available was rejected");
errors = errors + 1; end
eval_mode(1000, 500, 100, 101, 1700, 100, 100, 0);
if (period_ok !== 1'b0 || period_deficit !== 16'd1) begin
$display("FAIL: a one-tick over-budget was accepted (ok=%b deficit=%0d)",
period_ok, period_deficit); errors = errors + 1; end
// And the MARGIN must be zero. Computing it unconditionally would underflow to a huge
// number on a failing budget, so a report would show both an enormous margin and a
// deficit -- and a reader cannot tell from that which one to believe.
if (period_margin !== {TICK_W{1'b0}}) begin
$display("FAIL: a failing period budget reported %0d of margin -- margin and deficit must never both be nonzero",
period_margin); errors = errors + 1; end
// ================================================================
// 8 — a SLOW BUS. Halving the clock rate and spending the extra time on both phases
// fixes BOTH budgets, and the distinction matters: raising period_avail alone
// would fix the period and leave the low phase exactly as tight as before,
// because the low-phase budget is spent out of the tLOW a design PROVIDES. This
// is the universal remedy and the only lever that moves every constraint at once.
// ================================================================
eval_mode(2600, 1200, 300, 300, 5000, 900, 100, 51);
if (period_required !== 16'd4400 || period_ok !== 1'b1 || period_margin !== 16'd600) begin
$display("FAIL: a half-rate bus: required %0d, ok %b, margin %0d -- expected 4400, 1, 600",
period_required, period_ok, period_margin); errors = errors + 1; end
if (low_ok !== 1'b1 || low_margin !== 16'd1249) begin
$display("FAIL: low phase at half rate: ok %b, margin %0d -- expected 1, 1249",
low_ok, low_margin); errors = errors + 1; end
if (budget_ok !== 1'b1) begin
$display("FAIL: a bus at half rate with both phases lengthened should close");
errors = errors + 1; end
// ================================================================
// 9 — the verdict is only produced when ASKED. Without an evaluate pulse the
// outputs must hold, so a consumer cannot read a half-updated budget.
// ================================================================
begin
held = period_required;
t_low = 16'd9999; // change the inputs without evaluating
tick(10);
if (period_required !== held) begin
$display("FAIL: the outputs moved without an evaluate pulse"); errors = errors + 1; end
if (valid !== 1'b0) begin
$display("FAIL: valid is asserted outside an evaluation"); errors = errors + 1; end
end
if (errors == 0)
$display("PASS: all three of Table 10's period identities are exact, Fm+ does not close its low phase, the filter latency lands on the low phase only, margin and deficit are never both nonzero");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- AN I2C TIMING BUDGET, in hardware. The capstone of Module 11: it takes the parameters the
-- other eight blocks measure and answers the one question a bring-up actually asks -- does
-- this bus close?
--
-- It checks budgets that are genuinely different constraints rather than views of one.
--
-- 1. THE PERIOD BUDGET. tLOW + tHIGH + tr + tf <= 1/fSCL
--
-- Table 10 makes this an EXACT identity at the limits, in all three speed modes:
-- Standard 4.7 + 4.0 + 1.000 + 0.300 = 10.0 us = 1/100 kHz
-- Fast 1.3 + 0.6 + 0.300 + 0.300 = 2.5 us = 1/400 kHz
-- Fm+ 0.5 + 0.26 + 0.120 + 0.120 = 1.0 us = 1/1000 kHz
-- So at the limits there is ZERO margin. Any real design must beat at least one of the
-- four terms, and which one it beats is a board-level decision: a smaller pull-up buys
-- rise time, a slower clock buys everything.
--
-- 2. THE LOW-PHASE BUDGET. tVD;DAT + tSU;DAT + tr (+ filter latency) <= tLOW
--
-- Standard 3.45 + 0.250 + 1.000 = 4.700 us vs tLOW 4.7 -> EXACTLY tight
-- Fast 0.90 + 0.100 + 0.300 = 1.300 us vs tLOW 1.3 -> EXACTLY tight
-- Fm+ 0.45 + 0.050 + 0.120 = 0.620 us vs tLOW 0.5 -> DOES NOT FIT
-- Fast-mode Plus does not close on the table's own worst-case numbers. That is not an
-- error in the table; it means an Fm+ transmitter cannot take its full tVD;DAT on a bus
-- with its full rise time -- something has to be better than worst case.
--
-- 3. THE FILTER. Chapter 11.8's filter delays every transition by T_SP + 1 samples, and that
-- delay lands inside the LOW phase. A budget that ignored it would close on paper and
-- fail on silicon.
entity i2c_timing_budget is
generic (
TICK_W : positive := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
evaluate : in std_logic; -- pulse: latch a verdict
-- The low phase this design actually PROVIDES -- not the table's minimum. The two are
-- different numbers and conflating them is a real modelling error: raising the clock
-- period does not change a spec minimum, but it does lengthen the low phase a design
-- gives, and it is the latter the low-phase budget is spent out of. That tLOW also
-- meets the table's minimum is a separate check, made on the wire by Chapter 11.2.
t_low : in unsigned(TICK_W - 1 downto 0);
t_high : in unsigned(TICK_W - 1 downto 0);
t_r : in unsigned(TICK_W - 1 downto 0);
t_f : in unsigned(TICK_W - 1 downto 0);
period_avail : in unsigned(TICK_W - 1 downto 0); -- 1 / fSCL(max)
t_vd_dat : in unsigned(TICK_W - 1 downto 0);
t_su_dat : in unsigned(TICK_W - 1 downto 0);
filter_latency : in unsigned(TICK_W - 1 downto 0); -- Chapter 11.8: T_SP + 1, or 0
valid : out std_logic;
period_required : out unsigned(TICK_W - 1 downto 0);
period_ok : out std_logic;
period_margin : out unsigned(TICK_W - 1 downto 0); -- slack when ok, else ZERO
period_deficit : out unsigned(TICK_W - 1 downto 0); -- shortfall when not ok, else 0
low_required : out unsigned(TICK_W - 1 downto 0);
low_ok : out std_logic;
low_margin : out unsigned(TICK_W - 1 downto 0);
low_deficit : out unsigned(TICK_W - 1 downto 0);
budget_ok : out std_logic
);
end entity;
architecture rtl of i2c_timing_budget is
constant ZERO : unsigned(TICK_W - 1 downto 0) := (others => '0');
-- Margins are reported as two unsigned numbers rather than one signed one. That is not
-- squeamishness about signed arithmetic: a budget report is read by people, and "3 ticks
-- of margin" and "3 ticks short" are different sentences. A single signed number invites
-- a reader to miss the sign, which on a budget is the whole answer.
signal per_req, low_req : unsigned(TICK_W - 1 downto 0);
signal per_ok_c, low_ok_c : std_logic;
begin
per_req <= t_low + t_high + t_r + t_f;
-- The filter latency is added to the LOW-phase requirement, not the period: it delays the
-- data becoming valid, which is a low-phase obligation. Adding it to the period instead
-- would be a plausible-looking error that under-reports the pressure on tLOW.
low_req <= t_vd_dat + t_su_dat + t_r + filter_latency;
per_ok_c <= '1' when per_req <= period_avail else '0';
low_ok_c <= '1' when low_req <= t_low else '0';
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
valid <= '0';
period_required <= (others => '0');
period_ok <= '0';
period_margin <= (others => '0');
period_deficit <= (others => '0');
low_required <= (others => '0');
low_ok <= '0';
low_margin <= (others => '0');
low_deficit <= (others => '0');
budget_ok <= '0';
else
valid <= '0';
if evaluate = '1' then
valid <= '1';
period_required <= per_req;
period_ok <= per_ok_c;
if per_ok_c = '1' then
period_margin <= period_avail - per_req;
period_deficit <= ZERO;
else
period_margin <= ZERO;
period_deficit <= per_req - period_avail;
end if;
low_required <= low_req;
low_ok <= low_ok_c;
if low_ok_c = '1' then
low_margin <= t_low - low_req;
low_deficit <= ZERO;
else
low_margin <= ZERO;
low_deficit <= low_req - t_low;
end if;
-- The overall verdict is the AND of every constraint. A budget that
-- closes on the period and fails on the low phase does not close -- and
-- reporting only the headline number is how a design ships with a
-- violation that was computed and then not looked at.
budget_ok <= per_ok_c and low_ok_c;
end if;
end if;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- The three real speed modes, converted from Table 10 to ticks of a 1 GHz reference so every
-- value in the table lands on an integer: one tick is 1 ns.
--
-- tLOW tHIGH tr tf 1/fSCL tVD;DAT tSU;DAT
-- Standard-mode 4700 4000 1000 300 10000 3450 250
-- Fast-mode 1300 600 300 300 2500 900 100
-- Fast-mode Plus 500 260 120 120 1000 450 50
--
-- The testbench asserts the EXACT identities from Table 10 rather than approximate ones,
-- because they are exact: if any came out with margin, either the table was transcribed
-- wrongly or the arithmetic is wrong, and both are worth failing on.
entity i2c_timing_budget_tb is
end entity;
architecture sim of i2c_timing_budget_tb is
constant TICK_W : positive := 16;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal evaluate : std_logic := '0';
signal t_low, t_high, t_r, t_f : unsigned(TICK_W - 1 downto 0) := (others => '0');
signal period_avail, t_vd_dat, t_su_dat, filter_latency
: unsigned(TICK_W - 1 downto 0) := (others => '0');
signal valid, period_ok, low_ok, budget_ok : std_logic;
signal period_required, period_margin, period_deficit : unsigned(TICK_W - 1 downto 0);
signal low_required, low_margin, low_deficit : unsigned(TICK_W - 1 downto 0);
-- owned solely by the counting process
signal n_valid : natural := 0;
signal test_done : std_logic := '0';
begin
dut : entity work.i2c_timing_budget
generic map (TICK_W => TICK_W)
port map (clk => clk, rst_n => rst_n, evaluate => evaluate,
t_low => t_low, t_high => t_high, t_r => t_r, t_f => t_f,
period_avail => period_avail, t_vd_dat => t_vd_dat, t_su_dat => t_su_dat,
filter_latency => filter_latency,
valid => valid, period_required => period_required, period_ok => period_ok,
period_margin => period_margin, period_deficit => period_deficit,
low_required => low_required, low_ok => low_ok, low_margin => low_margin,
low_deficit => low_deficit, budget_ok => budget_ok);
clk <= not clk after 5 ns;
watchdog : process
begin
wait for 200 us;
if test_done = '0' then
report "watchdog expired" severity failure;
end if;
wait;
end process;
-- `valid` is a one-cycle pulse, so it is COUNTED rather than sampled after the fact --
-- the same lesson Chapter 8.1's testbench records about waiting on a level.
counter : process (clk)
begin
if rising_edge(clk) and rst_n = '1' and valid = '1' then
n_valid <= n_valid + 1;
end if;
end process;
stim : process
variable errs : natural := 0;
variable nv_before : natural;
variable held : unsigned(TICK_W - 1 downto 0);
procedure tick (n : in positive) is
begin
for i in 1 to n loop wait until falling_edge(clk); end loop;
end procedure;
procedure eval_mode (lo, hi, tr, tf, per, vd, su, fl : in natural) is
begin
t_low <= to_unsigned(lo, TICK_W);
t_high <= to_unsigned(hi, TICK_W);
t_r <= to_unsigned(tr, TICK_W);
t_f <= to_unsigned(tf, TICK_W);
period_avail <= to_unsigned(per, TICK_W);
t_vd_dat <= to_unsigned(vd, TICK_W);
t_su_dat <= to_unsigned(su, TICK_W);
filter_latency <= to_unsigned(fl, TICK_W);
tick(2);
evaluate <= '1'; wait until falling_edge(clk); evaluate <= '0';
tick(2);
end procedure;
begin
tick(3);
if valid /= '0' then
report "valid out of reset" severity error; errs := errs + 1; end if;
rst_n <= '1'; tick(2);
-- 1: STANDARD-MODE at the table's limits, no spike filter. The period budget must
-- close EXACTLY: 4700 + 4000 + 1000 + 300 = 10000.
nv_before := n_valid;
eval_mode(4700, 4000, 1000, 300, 10000, 3450, 250, 0);
if n_valid /= nv_before + 1 then
report "wrong number of verdict pulses from one evaluation" severity error;
errs := errs + 1; end if;
if period_required /= to_unsigned(10000, TICK_W) then
report "Standard period requirement wrong, expected 10000" severity error;
errs := errs + 1; end if;
if period_ok /= '1' then
report "the Standard-mode period budget did not close" severity error;
errs := errs + 1; end if;
if period_margin /= to_unsigned(0, TICK_W) then
report "Standard period margin nonzero -- Table 10's identity is EXACT"
severity error; errs := errs + 1; end if;
if low_required /= to_unsigned(4700, TICK_W) then
report "Standard low-phase requirement wrong, expected 4700" severity error;
errs := errs + 1; end if;
if low_ok /= '1' or low_margin /= to_unsigned(0, TICK_W) then
report "the Standard low-phase budget should close with EXACTLY zero margin"
severity error; errs := errs + 1; end if;
if budget_ok /= '1' then
report "Standard-mode should close overall" severity error; errs := errs + 1; end if;
-- 2: FAST-MODE at the limits. Both identities exact again.
eval_mode(1300, 600, 300, 300, 2500, 900, 100, 0);
if period_required /= to_unsigned(2500, TICK_W) or period_ok /= '1'
or period_margin /= to_unsigned(0, TICK_W) then
report "Fast-mode period budget wrong" severity error; errs := errs + 1; end if;
if low_required /= to_unsigned(1300, TICK_W) or low_ok /= '1'
or low_margin /= to_unsigned(0, TICK_W) then
report "Fast-mode low-phase budget wrong" severity error; errs := errs + 1; end if;
if budget_ok /= '1' then
report "Fast-mode should close overall" severity error; errs := errs + 1; end if;
-- 3: FAST-MODE PLUS at the limits. The PERIOD closes exactly and the LOW PHASE DOES
-- NOT: 450 + 50 + 120 = 620 against a tLOW of 500. This is the finding of the whole
-- chapter -- Fm+ does not close on the table's own worst-case numbers.
eval_mode(500, 260, 120, 120, 1000, 450, 50, 0);
if period_required /= to_unsigned(1000, TICK_W) or period_ok /= '1'
or period_margin /= to_unsigned(0, TICK_W) then
report "Fm+ period budget wrong" severity error; errs := errs + 1; end if;
if low_required /= to_unsigned(620, TICK_W) then
report "Fm+ low-phase requirement wrong, expected 620" severity error;
errs := errs + 1; end if;
if low_ok /= '0' then
report "the Fm+ low-phase budget was reported as closing -- 620 > 500"
severity error; errs := errs + 1; end if;
if low_deficit /= to_unsigned(120, TICK_W) then
report "Fm+ low-phase deficit wrong, expected 620 - 500 = 120" severity error;
errs := errs + 1; end if;
if low_margin /= to_unsigned(0, TICK_W) then
report "a failing budget reported margin -- margin and deficit must not both be nonzero"
severity error; errs := errs + 1; end if;
if budget_ok /= '0' then
report "Fm+ closed overall while its low-phase budget failed" severity error;
errs := errs + 1; end if;
-- 4: a REAL Fast-mode design that beats the table. A 250 ns rise and a 400 ns
-- transmitter response. Both budgets close with genuine margin.
eval_mode(1300, 600, 250, 300, 2500, 400, 100, 0);
if period_ok /= '1' or period_margin /= to_unsigned(50, TICK_W) then
report "a 50 ns faster rise should give exactly 50 ticks of period margin"
severity error; errs := errs + 1; end if;
if low_ok /= '1' or low_margin /= to_unsigned(550, TICK_W) then
report "low-phase margin wrong, expected 1300 - 750 = 550" severity error;
errs := errs + 1; end if;
if budget_ok /= '1' then
report "a design with margin on both budgets should close" severity error;
errs := errs + 1; end if;
-- 5: THE SPIKE FILTER'S LATENCY IS REAL, and it lands on the LOW phase only.
eval_mode(1300, 600, 250, 300, 2500, 400, 100, 51);
if low_required /= to_unsigned(801, TICK_W) then
report "with a 51-tick filter the low phase needs 750 + 51 = 801" severity error;
errs := errs + 1; end if;
if low_margin /= to_unsigned(499, TICK_W) then
report "low-phase margin wrong, expected 1300 - 801 = 499" severity error;
errs := errs + 1; end if;
-- The PERIOD requirement must be UNCHANGED: the filter delays data, not the clock's
-- own phases. Adding it to the period would be a plausible-looking error.
if period_required /= to_unsigned(2450, TICK_W) then
report "the filter latency changed the PERIOD requirement -- it belongs to the low phase"
severity error; errs := errs + 1; end if;
-- 6: a filter big enough to BREAK an otherwise-closing budget, by ONE tick.
eval_mode(1300, 600, 250, 300, 2500, 900, 100, 51);
if low_required /= to_unsigned(1301, TICK_W) then
report "low-phase requirement wrong, expected 900+100+250+51 = 1301" severity error;
errs := errs + 1; end if;
if low_ok /= '0' or low_deficit /= to_unsigned(1, TICK_W) then
report "a budget missing by ONE tick was reported as closing" severity error;
errs := errs + 1; end if;
if budget_ok /= '0' then
report "a one-tick deficit must not close" severity error; errs := errs + 1; end if;
-- 7: THE BOUNDARY of the comparison itself. Required exactly equal to available must
-- CLOSE, because a budget that exactly fits is met. One tick more must not.
eval_mode(1000, 500, 100, 100, 1700, 100, 100, 0);
if period_required /= to_unsigned(1700, TICK_W) or period_ok /= '1' then
report "a period requirement exactly equal to what is available was rejected"
severity error; errs := errs + 1; end if;
eval_mode(1000, 500, 100, 101, 1700, 100, 100, 0);
if period_ok /= '0' or period_deficit /= to_unsigned(1, TICK_W) then
report "a one-tick over-budget was accepted" severity error; errs := errs + 1; end if;
-- And the MARGIN must be zero. Computing it unconditionally would underflow to a huge
-- number on a failing budget, so a report would show both an enormous margin and a
-- deficit -- and a reader cannot tell from that which one to believe.
if period_margin /= to_unsigned(0, TICK_W) then
report "a failing period budget reported margin -- margin and deficit must never "
& "both be nonzero" severity error; errs := errs + 1; end if;
-- 8: a SLOW BUS. Halving the clock rate AND spending the extra time on both phases
-- fixes both budgets. Raising period_avail alone would fix the period and leave the
-- low phase exactly as tight, because the low-phase budget is spent out of the tLOW a
-- design PROVIDES. This is the only lever that moves every constraint at once.
eval_mode(2600, 1200, 300, 300, 5000, 900, 100, 51);
if period_required /= to_unsigned(4400, TICK_W) or period_ok /= '1'
or period_margin /= to_unsigned(600, TICK_W) then
report "a half-rate bus's period budget is wrong" severity error;
errs := errs + 1; end if;
if low_ok /= '1' or low_margin /= to_unsigned(1249, TICK_W) then
report "a half-rate bus's low-phase budget is wrong" severity error;
errs := errs + 1; end if;
if budget_ok /= '1' then
report "a bus at half rate with both phases lengthened should close"
severity error; errs := errs + 1; end if;
-- 9: the verdict is only produced when ASKED. Without an evaluate pulse the outputs
-- must hold, so a consumer cannot read a half-updated budget.
held := period_required;
t_low <= to_unsigned(9999, TICK_W);
tick(10);
if period_required /= held then
report "the outputs moved without an evaluate pulse" severity error;
errs := errs + 1; end if;
if valid /= '0' then
report "valid is asserted outside an evaluation" severity error;
errs := errs + 1; end if;
if errs = 0 then
report "i2c_timing_budget self-check complete: all three of Table 10's period "
& "identities are exact, Fm+ does not close its low phase, the filter "
& "latency lands on the low phase only, margin and deficit are never both "
& "nonzero" severity note;
else
report "i2c_timing_budget self-check FAILED" severity error;
end if;
test_done <= '1';
wait;
end process;
end architecture;6a. Five Decisions Worth Defending
The block computes; it does not measure. Every other design in this module observes a bus. This one takes numbers — from a datasheet, a board estimate, a synthesis report — and does arithmetic. That makes it the only block here that is useful before silicon exists, and the only one whose inputs are a design decision rather than an observation.
A verdict is latched on an evaluate pulse, not recomputed continuously. A budget belongs to a configuration, and a verdict that moved as inputs settled would be unreadable — worse, it would invite sampling it at the wrong moment. valid says when the answer means something. Mutation I6 removes the gating and §7's test 17 catches it.
t_low is what the design provides, not the specification's floor. §3 is the argument and the rewrite.
Margin and deficit are separate unsigned outputs. §5's reasoning: different meanings, and no silent unsigned wrap.
budget_ok is a conjunction. A configuration is only viable if both budgets close. §7's test 5 is the case where one does and one does not.
Filter latency is an explicit input, not folded into t_vd_dat. It could be hidden inside the response time, and Chapter 11.8 §11 is what happens when it is: a device quotes a tVD;DAT measured from the filtered edge and understates the parameter by exactly the filter latency. Naming it as its own term makes it impossible to forget, and makes the "choose a smaller tSP" fix a one-input experiment.
6b. Verified Execution
$ iverilog -g2012 -o d9 i2c_timing_budget.sv i2c_timing_budget_tb.sv && ./d9
PASS: all three of Table 10's period identities are exact, Fm+ does not close its low phase,
the filter latency lands on the low phase only, margin and deficit are never both nonzero
i2c_timing_budget_tb.sv:245: $finish called at 600000 (1ps)
$ iverilog -g2005 -o v9 i2c_timing_budget.v i2c_timing_budget_tb.v && ./v9
PASS: all three of Table 10's period identities are exact, Fm+ does not close its low phase,
the filter latency lands on the low phase only, margin and deficit are never both nonzero
i2c_timing_budget_tb.v:260: $finish called at 600000 (1ps)
$ nvc -a i2c_timing_budget.vhd i2c_timing_budget_tb.vhd
$ nvc -e i2c_timing_budget_tb && nvc -r i2c_timing_budget_tb --stop-time=20us
** Note: 600ns+0: i2c_timing_budget self-check complete: all three of Table 10's period
identities are exact, Fm+ does not close its low phase, the filter latency lands on the low
phase only, margin and deficit are never both nonzeroAll three at 600 ns — the shortest run in the module, because a block that computes needs no bus traffic to drive it. Nine configurations applied in nine cycles.
7. What the Testbench Proves
The stimulus applies parameter sets and pulses evaluate for each. The three real speed modes are converted from Table 10 to ticks of a 1 GHz reference, so every value in the table lands on an integer: one tick is 1 ns.
| # | configuration | what it establishes |
|---|---|---|
| 1 | reset | valid is not asserted |
| 2 | Standard-mode at the table's values | period required 10000, closes, margin exactly zero, deficit zero |
| 3 | Standard-mode low phase | required 4700, closes with exactly zero margin; closes overall |
| 4 | Fast-mode at the table's values | period 2500 and low phase 1300, both closing with zero margin |
| 5 | Fm+ at the table's values | period 1000 closes with zero margin; low phase required 620 |
| 6 | that Fm+ low phase | reported as failing — 620 > 500 — with a deficit of exactly 120 |
| 7 | that failing budget | reports zero margin — margin and deficit never both non-zero |
| 8 | Fm+ overall | does not close, because the verdict is the AND of both budgets |
| 9 | a 50 ns faster rise | gives exactly 50 ticks of period margin |
| 10 | a design with real headroom | low-phase margin exactly 1300 − 750 = 550; closes |
| 11 | that design plus a 51-tick filter | low phase needs 750 + 51 = 801; margin 1300 − 801 = 499 |
| 12 | the same filter | leaves the period requirement unchanged — it belongs to the low phase |
| 13 | a budget missing by one tick | does not close; deficit is 1 |
| 14 | a requirement exactly equal to what is available | accepted |
| 15 | one tick over | rejected, with a deficit of 1 and zero margin |
| 16 | a half-rate bus with both phases lengthened | period margin 600, low-phase margin 1249, closes |
| 17 | no evaluate pulse | the outputs do not move, and valid stays low |
Tests 2 to 5 are the module's arithmetic executed rather than asserted in prose. Standard-mode and Fast-mode produce a margin of exactly zero in both budgets, and Fm+ produces zero on the period and a 620-tick requirement on the low phase. If any of those had come out non-zero, one of Chapter 11.2 §4 or Chapter 11.4 §4 would contain an arithmetic error. They do not.
Test 6 is the Fast-mode-Plus deficit, and it is exactly 120 ticks. That is tr(max) at Fm+, which is the observation Chapter 11.4 §4 used to conclude the specification expects the pull-up to be beaten. The block reports it as a number rather than an argument.
Tests 11 and 12 are the filter's place in the budget, settled. A 51-tick filter latency adds 51 ticks to the low-phase requirement and leaves the period requirement untouched. That is Chapter 11.8 §4's asymmetry as a pair of assertions, and mutation I1 is the version that charges it to the period instead — a defect that would make a filter look harmless on the budget that has slack and free on the budget that does not.
Test 16 is the test §3's rewrite made possible, and it is the one worth reading the numbers on. A half-rate bus with both phases lengthened has 600 ticks of period margin and 1249 of low-phase margin: both budgets improve, because the period available grew as well as the low phase provided. A block that compared the low-phase requirement against tLOW(min) rather than against the low phase the design provides would show no low-phase improvement at all, because the constant it compared against would not have moved.
Test 17 is the handshake. The block latches a verdict when evaluate pulses and holds it; it does not recompute continuously. That matters for how it is used — a budget is evaluated for a configuration, and a verdict that flickered as inputs settled would be unreadable. Mutation I6 removes the gating.
Tests 13 to 15 are the one-tick boundary from three sides: one under, exactly equal, one over. Exactly equal must close — a budget that precisely fits is legal — and mutation I4 is the version that rejects it.
8. Mutation Testing
Six defects injected into the SystemVerilog block.
| # | injected defect | outcome |
|---|---|---|
| I1 | the filter latency is added to the period instead of the low phase | killed — test 12 |
| I2 | the rise time is left out of the low-phase budget | killed — test 3 |
| I3 | the overall verdict ignores the low-phase budget | killed — test 8 |
| I4 | a budget that exactly fits is rejected | killed — test 14 |
| I5 | margin and deficit are both reported | killed — test 7 |
| I6 | the verdict is produced continuously rather than when asked | killed — test 17 |
Six injected, six killed. Three notes.
I2 is the mutation that needed a test the first suite did not have. t_r feeds both sums, and in every configuration the first suite tried the two budgets moved in the same direction — so removing the rise time from the low-phase sum changed a number no assertion read independently, and the aggregate verdict was unaffected.
Closing it required asserting low_required by value: test 3 checks that Standard-mode's low phase requires exactly 4700, and with t_r missing it requires 3700. That is a thousand-tick error that no verdict-based test detected, because 3700 also closes against 4700.
The general lesson applies to any block with shared inputs: a term that feeds two computations cannot be verified by a test in which both computations agree. The fix is to assert the intermediate requirement, not just the final verdict — the same conclusion Chapter 11.5 §7 reaches from its mutation E2, which shifted a measurement in the safe direction and was invisible to every verdict-only test.
I5 is the mutation §5 was written to make possible. Reporting both margin and deficit unconditionally gives a correct-looking deficit when the budget fails and an enormous wrapped margin — 65535 — when it succeeds. Test 7 catches it because it asserts that a failing budget reports zero margin. The two-unsigned-outputs structure exists precisely so that this is an injected defect rather than the natural implementation.
I4 is the off-by-one that matters most in practice. Tests 2 to 5 establish that the specification's own values produce budgets that close with exactly zero margin — so a block that rejected an exactly-fitting budget would declare every one of Table 10's three speed modes unviable. The <= is not a stylistic preference here; it is the difference between a tool that says "Standard-mode is legal" and one that says it is not.
9. Verification Connection — A Budget Is a Constraint Space
// The unusual thing here is that these properties have no temporal content worth speaking of.
// Every other property in this module constrains the ORDER of events; these constrain a
// RELATIONSHIP between numbers, which makes them closer to a formal specification of the
// block than to a protocol check -- and makes the block a good formal-verification target.
// Margin and deficit are mutually exclusive, always. This is the property that the
// two-unsigned-output structure of section 5 exists to make checkable.
property p_exclusive;
@(posedge clk) (period_margin == 0) || (period_deficit == 0);
endproperty
assert property (p_exclusive)
else $error("margin and deficit both non-zero -- an unsigned wrap");
// The aggregate is a conjunction, not a disjunction and not either budget alone.
property p_aggregate;
@(posedge clk) budget_ok == ((period_deficit == 0) && (low_deficit == 0));
endproperty
assert property (p_aggregate)
else $error("budget_ok does not agree with the two deficits");
// MONOTONICITY, which is the property a budget block most needs and which no single
// configuration demonstrates: lengthening the low phase must never make the low-phase budget
// worse. It is what section 3's rewrite was for, and it is false for the pre-rewrite version.
property p_low_monotonic;
@(posedge clk) ($past(low_deficit) > 0 && t_low > $past(t_low) && $stable(low_req))
|-> (low_deficit < $past(low_deficit));
endproperty
assert property (p_low_monotonic)
else $error("a longer low phase did not improve the low-phase budget");
// And the NEGATIVE property: a closing budget must not report a deficit. Trivial to state and
// exactly what mutation I4 violates, which is the argument for writing it down.
property p_no_phantom_deficit;
@(posedge clk) (low_req <= t_low) |-> (low_deficit == 0);
endproperty
assert property (p_no_phantom_deficit)
else $error("a budget that closes reported a deficit"); // This covergroup's axes are a DESIGN space, not a state space. Its purpose is to answer
// "which regions of the parameter space has anyone ever evaluated?" -- which for a budget
// block is the only interesting coverage question.
covergroup i2c_budget_cg with function sample(int per_margin, int per_deficit,
int low_margin, int low_deficit,
int mode, int filter_ns);
// Both budgets binned identically, with a single-value bin at zero because section 1's
// whole finding is that the specification's own values land EXACTLY there.
per_state: coverpoint (per_deficit > 0 ? -per_deficit : per_margin) {
bins deficit = {[$:-1]};
bins exact = {0}; // the table's own values, results 1 and 2
bins tight = {[1:50]};
bins ample = {[51:$]};
}
low_state: coverpoint (low_deficit > 0 ? -low_deficit : low_margin) {
bins deficit = {[$:-1]};
bins exact = {0};
bins tight = {[1:50]};
bins ample = {[51:$]};
}
// THE cross for this block, and the one mutation I5 required. A term feeding both sums is
// only observable where the two budgets DISAGREE, so the off-diagonal cells are the ones
// that carry information -- and a run that fills only the diagonal has not verified the
// shared terms at all.
disagreement: cross per_state, low_state;
// Speed mode, because section 1's results differ by mode: Std and Fast close exactly and
// Fm+ does not. A suite that only ran one mode has seen one of the two behaviours.
speed: coverpoint mode { bins standard = {0}; bins fast = {1}; bins fm_plus = {2}; }
low_x_speed: cross low_state, speed;
// Filter latency as its own axis, because it is the term Chapter 11.8 shows is both
// optional in size and decisive at Fm+. Zero must be covered -- Standard-mode uses it.
filt: coverpoint filter_ns {
bins none = {0}; // Standard-mode, no tSP requirement
bins small = {[1:30]};
bins maximum = {[31:60]}; // the tSP maximum, which breaks Fm+
}
filt_x_speed: cross filt, speed;
endgroup10. FPGA and ASIC Implications
The block is two adders and two comparators — around 90 flops at W = 16. It is almost certainly not something you synthesise into a shipping product; its natural home is a simulation environment, a bring-up register block, or a spreadsheet. Building it as RTL is worth doing anyway, because the arithmetic becomes executable and testable rather than a table in a document that drifts.
Where it does belong in silicon is a bring-up mode. A design that can be told its measured tr and its configured tSP and report a deficit gives a board engineer a number on the first power-up, before any transfer is attempted. That is worth ninety flops.
The two budgets have two different owners, and a deficit report should say which. A period deficit is the master's or the board's; a low-phase deficit is the transmitting device's or — via a longer low phase — the master's again. A report that says only "budget failed" sends the question to the wrong engineer, which is the same failure mode Chapter 11.7 §9's assertion message guards against.
The practical resolutions for a low-phase deficit, in the order they usually cost least:
| fix | cost | chapter |
|---|---|---|
reduce tSP below its maximum | latency traded for noise margin | 11.8 |
| lengthen the low phase | a slower clock | 11.2 |
reduce tr — stronger pull-up | static current | 11.7 |
reduce tr — current source | board complexity, Fm+ pads | 11.7 |
| speed up the device's response | RTL rework | 11.4 |
And note that lengthening the low phase is the only fix that costs nothing but speed. tLOW has no maximum, so it is always available — which is why it is the fix a design should reach for first when the frequency is negotiable, and why clock stretching exists as a protocol feature at all. That is Module 12.
11. Debugging — The Board That Was Compliant Everywhere and Worked Nowhere
Pitfall — checking every parameter and never checking the sum
// A Fast-mode-Plus sensor hub. Every parameter checked against Table 10 in a formal design
// review, with a signed-off compliance matrix:
//
// tHD;DAT provided 300 ns >= 0 ns required (footnote [3]: 300 ns) PASS
// tSU;DAT provided 80 ns >= 50 ns PASS
// tVD;DAT from the pad 340 ns <= 450 ns PASS
// tr measured 110 ns <= 120 ns PASS
// tf measured 90 ns <= 120 ns PASS
// tLOW generated 500 ns >= 500 ns PASS
// tHIGH generated 280 ns >= 260 ns PASS
// fSCL 1.0 MHz <= 1.0 MHz PASS
// tSP filter 50 ns <= 50 ns PASS
//
// Nine parameters, nine passes, one signature. The matrix was correct -- every entry was
// verified in silicon or on the board, and not one of them is wrong.The bus did not work at 1 MHz. Not intermittently -- reads returned corrupted data on a majority of transfers, immediately, on every board.
At 400 kHz everything was flawless, which made the obvious hypothesis "something is marginal at 1 MHz" and sent the team looking for the marginal parameter. There was not one. Every entry in the matrix was re-measured on a failing board and every one still passed, several with room.
Three weeks went into signal integrity. Pull-ups were varied from 300 ohm to 2.2 kohm, which changed tr across the whole legal range and never fixed it. Ground stitching was added. Two different sensor vendors' parts were tried. A four-layer board was respun as six-layer.
What broke it was somebody adding the low-phase terms on a whiteboard:
the slave's response from its FILTERED falling edge 280 ns filter latency, T_SP=5 at 100 MHz, (5+1) clocks 60 ns tSU;DAT this design provides 80 ns tr to settle 110 ns ----------------------------------------------------- 530 ns tLOW the clock provides 500 ns
DEFICIT 30 ns
Every term inside its own limit -- the true tVD;DAT from the pad is 280 + 60 = 340 ns against a 450 ns ceiling, which is the row the matrix already contained and passed. The sum is 30 ns outside the phase.
There was never a parameter to find, which is exactly why three weeks of looking for one failed. And it is why varying the pull-up did not fix it: dropping tr from 110 to 60 ns WOULD have closed the budget, but the smallest legal pull-up still left 80 ns and nobody was measuring the sum, so nobody could see the experiment working.
Note also which term is NOT in that list. Footnote [3]'s 300 ns hold obligation appears in the matrix and not in the budget, because tHD;DAT and tVD;DAT are measured from the same falling edge -- a floor and a ceiling on one transition, not two consecutive intervals. Adding both would have produced a 60 ns deficit instead of a 30 ns one and sent the fix in the wrong direction. Chapter 11.4 section 3 is the geometry.
Per-parameter compliance was treated as sufficient. It is necessary and not sufficient: the low phase must CONTAIN the response, the filter latency ahead of it, the receiver's setup and the line's settling -- and at Fast-mode Plus the specification's own worst cases already overrun it by 120 ns before a filter is added at all (Chapter 11.4 section 4), so every working Fm+ design is living inside margin it bought by beating tVD;DAT.
The compliance matrix had no row for the sum, because the specification has no row for the sum. Table 10 lists sixteen limits and no budgets, so a review that walks the table finds every individual violation and cannot find this one.
The filter was the largest avoidable term and the most invisible: instantiated from the pad library, set to the specification's maximum on the reasoning that more filtering is safer, and appearing in nobody's parameter list because tSP is not a delay the datasheet quotes.
12. Common Misconceptions
"If every parameter is compliant, the transfer works." The module's central result is that it does not. §11 is nine verified passes and a bus that failed on every board.
"The specification would list a budget if one mattered." A specification lists limits, because limits are what a device can be tested against. A budget is a consequence of the limits and belongs in the design review, not in Table 10.
"There is one timing budget." There are two: the period budget, owned by the master and the board, and the low-phase budget, owned by the transmitting device. They are coupled through tLOW, which is a term in one and the limit of the other.
"tLOW(min) is the low phase to budget against." tLOW(min) is a constraint; the low phase your clock provides is the resource. §3 is the rewrite that distinction forced, and conflating them makes the standard fix — lengthen the low phase — invisible.
"Margin and deficit are one signed number." They mean different things to different readers, and an unsigned subtraction in the wrong direction wraps silently. Two unsigned outputs under one comparison.
"A deficit means some parameter is out of specification." It means the sum is, which is a different and harder thing to find because there is nothing to point at.
"Filter latency is part of tVD;DAT and needs no separate term." Only if the tVD;DAT figure was measured from the pad. Chapter 11.8 §11 is a datasheet that understated it by exactly the filter latency.
"Varying a parameter across its legal range and seeing no fix proves it is not the cause." It may mean the metric is wrong. §11's pull-up sweep was moving in the right direction with no number showing it.
13. Reason It Through
A compliance matrix shows nine parameters passing and the bus does not work. Where do you look?
At the two sums. t_low + t_high + t_r + t_f against the period the target frequency allows, and t_vd_dat + t_su_dat + t_r + filter against the low phase the clock provides. Neither appears in Table 10, and they are the only place this failure is visible.
Why must t_low be the low phase the design provides rather than tLOW(min)?
Because the low phase plays two roles: it is a constraint the clock must clear and a resource the low-phase budget spends. Checking against the minimum answers "would these terms fit in the shortest legal low phase?", which makes the standard fix — lengthen the low phase, always legal since tLOW has no maximum — produce no improvement in the model.
Why did a mutation removing t_r from the low-phase sum survive the first suite, and what closed it?
Because t_r feeds both sums and both budgets moved together in every configuration tried, so the missing term changed a number no assertion read on its own. What closed it was asserting low_required by value: Standard-mode must require exactly 4700, and without the rise time it requires 3700 — a thousand-tick error that still closes against 4700, so no verdict-based test could see it. A term feeding two computations must be checked through the intermediate it feeds.
Two deficits are reported to a team: a period deficit and a low-phase deficit. Who owns each?
The period deficit belongs to the master's clock generator or the board — its terms are the phases produced and the edges the board produces. The low-phase deficit belongs to the transmitting device, or to the master if the answer is a longer low phase. A report saying only "the budget failed" sends both to whoever reads it first.
Of the five fixes for a low-phase deficit, which costs least and why is it the one to reach for?
Lengthening the low phase, because tLOW has no maximum so it is always available and it costs nothing but speed. Every other fix trades static current, board complexity or RTL rework. It is also why clock stretching exists as a protocol feature: making the low phase longer is the universally available remedy.
Three times in this module a worst-case sum overran by exactly one of its terms. What is that pattern telling you?
That the specification does not expect all its worst cases jointly, and that the term matching the deficit is the one it assumes you will not take at maximum. For the Fm+ low phase the deficit was tr(max), pointing at the pull-up; for the filtered Fm+ case it was half the filter latency, pointing at tSP. The arithmetic identifies the intended trade.
14. Understanding Check
15. Summary
Per-parameter compliance is necessary and not sufficient. Sixteen parameters can each be inside their limits while the phase that must contain them cannot. That is the module's central result and there is nothing in Table 10 that expresses it.
There are two budgets, coupled through tLOW. The period budget belongs to the master and the board; the low-phase budget to the transmitting device. tLOW is the limit of one and a term in the other, and t_r is spent in both.
A specification minimum and a design's actual value are different quantities that share a name. Conflating them was a design error here, and the test is which direction improves things: a constraint you want to be below, a resource you want to be above.
The specification's maxima bound the legal region; they are not a corner you may sit in. Four times this module found a worst-case sum that does not close, and each deficit named the term the specification does not expect at maximum.
Report margin and deficit as two unsigned values under one comparison. Different meanings for different readers, and no silent wrap.
A term feeding two computations is only verifiable where the two disagree. That is why mutation I5 survived a suite in which every configuration had both budgets moving together.
Compute the budget before the simulation starts. A configuration that is unviable by arithmetic should say so at elaboration, not as corrupted data three hours in.
And the cheapest fix is almost always a longer low phase, because tLOW has no maximum. That is not a workaround — it is the reason the protocol has a mechanism for exactly that.
16. What Comes Next
This chapter's last line is the next module's first.
Every budget in §10's table trades something real for margin — current, board complexity, RTL effort — except one. Lengthening the low phase costs only speed, and it is always legal because tLOW has no maximum. Chapter 11.2 §1 noted that as a curiosity about a one-sided limit; §10 here shows it is the universally available remedy for the tightest budget in the specification.
Module 12 is that remedy turned into a protocol feature. Clock stretching lets a slave — the device with the low-phase obligation and no control over the clock — hold SCL low until it is ready. It is the mechanism by which a device that cannot meet the low-phase budget at the master's chosen frequency gets the low phase it needs anyway, negotiated per byte rather than fixed at design time.
It also inverts something this module has assumed throughout. Nine chapters have treated SCL as the master's signal and the timing as the master's to get right. Clock stretching makes SCL a shared signal, and with that comes a new class of failure the timing parameters cannot describe: a slave that stretches and never releases, a master that does not tolerate stretching at all, and the interaction between stretching and the tBUF and arbitration rules that Chapter 11.6 and Module 13 are about.
Continue learning
Related tutorials
- Related topic
I²C Transaction Atomicity and Bus Ownership Across Phases
What is and is not atomic on an I²C bus, stated precisely. Three things end bus ownership and one that looks like it should does not — and telling them apart needs one input the wire cannot supply.
- Related topic
Why I²C Clock Stretching Exists
The one mechanism that lets a target push back on a clock it does not own. Covers the mismatch it solves, the specification's two stretching levels, and why 'optional' makes a non-stretching bus an electrical contract.
- Related topic
I²C Stretch Bounds, Timeouts and the No-Assumption Rule
The specification places no limit on how long a slave may hold the clock, so every timeout is a system policy rather than a compliance check. Includes the recovery asymmetry most engineers know only half of.
- Related topic
The SCL Timing Generator — Phases, Strobes and the Readback Rule
Where Table 10's microseconds become counts of system-clock cycles. Derives the period budget that must include rise and fall time, shows why rounding down is always illegal and rounding up always legal, and builds a generator that leaves its low phase only when the line actually reads back high — which implements clock stretching and clock synchronization with no extra logic.
