Skip to content
VLSI Mentor

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:

deficitequalsso the term to beat is
result 2, −120 nstr(max) exactlythe rise time — a current-source pull-up
result 3, an empty windowRp(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 budgetlow-phase budget
ownerthe master's SCL generatorthe transmitting device
limit1/fSCL at the target frequencythe tLOW the clock provides
termstLOW, tHIGH, tr, tftVD;DAT, tSU;DAT, tr, filter
who can fix a deficitthe master, or the boardthe 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 isrole
tLOW(min)the specification's floora constraint the design must clear
the actual low phasewhat the master's generator producesa 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 cycles
Ten intervals of one hundred nanoseconds each, forming one Fast-mode-Plus period. SCL is low for the first five intervals and high for the last five. A requirement row shows the data valid time occupying the first four and a half intervals, the receiver setup a small region after it, and the line settling after that, with the final region marked as overflowing past the point where SCL rises.tLOW provided: 500 nstLOW provided: 500 nsdeficit: 120 ns = tr(max)deficit:120 ns …SCL fallsSCL fallsSCL rises: 120 ns shortSCL rises: 120 ns shortsclneedstVDtVDtVDtVDsu+trOVEROVEROVEROVEROVERns450450450450170120120120120120t0t1t2t3t4t5t6t7t8t9
One Fast-mode-Plus period at 1 MHz, at Table 10's own worst-case values. The three obligations the low phase must contain total 620 ns against the 500 ns the clock provides, and the 120 ns deficit is exactly tr(max). No individual parameter is violated.

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget.sv — two unsigned outputs under one comparison
   // 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;
   end

Note 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

A dataflow arrangement. On the left, timing parameter inputs feed two summing blocks: one producing the period requirement from the two clock phases and the two edge times, and one producing the low phase requirement from the data valid time, setup time, rise time and filter latency. Each sum feeds a comparator against its own available time. Each comparator produces a margin output and a deficit output, and both comparators feed a single aggregate verdict.t_low, t_highphases providedt_r, t_fboard, Chapter 11.7t_vd_dat, t_su_datChapters 11.3, 11.4filter latencyChapter 11.8Period requiredsum of four termsLow phase requiredsum of four termsCompareagainst 1/fSCLCompareagainst t_lowPeriod marginor deficitLow marginor deficitbudget_okboth, or neithert_r in botht_low is the limit12
The budget block. Four terms sum into the period requirement and four into the low-phase requirement; each is compared against its own available time, and each comparison yields a margin or a deficit. The low phase appears twice — as a term in one budget and as the limit of the other.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget.sv — the module’s only block that computes rather than measures
   // 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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget_tb.sv — nine scenarios across all three speed modes
   `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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget.v — the same block in Verilog-2001
   // 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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget_tb.v — the Verilog testbench, structurally identical
   `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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget.vhd — the same block in VHDL
   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;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_timing_budget_tb.vhd — the VHDL testbench, single-writer throughout
   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

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, one result, one finish time
   $ 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 nonzero

All 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.

#configurationwhat it establishes
1resetvalid is not asserted
2Standard-mode at the table's valuesperiod required 10000, closes, margin exactly zero, deficit zero
3Standard-mode low phaserequired 4700, closes with exactly zero margin; closes overall
4Fast-mode at the table's valuesperiod 2500 and low phase 1300, both closing with zero margin
5Fm+ at the table's valuesperiod 1000 closes with zero margin; low phase required 620
6that Fm+ low phasereported as failing — 620 > 500 — with a deficit of exactly 120
7that failing budgetreports zero margin — margin and deficit never both non-zero
8Fm+ overalldoes not close, because the verdict is the AND of both budgets
9a 50 ns faster risegives exactly 50 ticks of period margin
10a design with real headroomlow-phase margin exactly 1300 − 750 = 550; closes
11that design plus a 51-tick filterlow phase needs 750 + 51 = 801; margin 1300 − 801 = 499
12the same filterleaves the period requirement unchanged — it belongs to the low phase
13a budget missing by one tickdoes not close; deficit is 1
14a requirement exactly equal to what is availableaccepted
15one tick overrejected, with a deficit of 1 and zero margin
16a half-rate bus with both phases lengthenedperiod margin 600, low-phase margin 1249, closes
17no evaluate pulsethe 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 defectoutcome
I1the filter latency is added to the period instead of the low phasekilled — test 12
I2the rise time is left out of the low-phase budgetkilled — test 3
I3the overall verdict ignores the low-phase budgetkilled — test 8
I4a budget that exactly fits is rejectedkilled — test 14
I5margin and deficit are both reportedkilled — test 7
I6the verdict is produced continuously rather than when askedkilled — 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

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_budget_props.sv — properties about arithmetic rather than about a bus
   // 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");
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_budget_cov.sv — the space of viable configurations
   // 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;
   endgroup

10. 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:

fixcostchapter
reduce tSP below its maximumlatency traded for noise margin11.8
lengthen the low phasea slower clock11.2
reduce tr — stronger pull-upstatic current11.7
reduce tr — current sourceboard complexity, Fm+ pads11.7
speed up the device's responseRTL rework11.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
Buggy Code
// 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.
Symptom

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.

Root Cause

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