Skip to content
VLSI Mentor

I²C · Module 23

Missing Pull-Ups, Slow Rise Times and Noise

Connecting an electrical symptom to a component. Why a maximum rise time cannot tell a resistor problem from an intermittent load, why a low pulse with no driver is a different failure from a slow edge, and what tSP has to do with either. Builds a profiler that reports a distribution and a spike count, produces four signatures deliberately, and shows that no two of them look alike.

Chapter 23.3 ends with the electrical layer indicted and the question of which component left open. Its classifier reports a maximum and four bins, which is enough to say "this bus is out of budget" and not enough to say what to change.

This chapter closes that gap. It is about the arithmetic that connects a symptom to a resistor, and about two distinctions that a single number cannot make.

1. The Arithmetic, First

Everything here rests on one relationship, established in Chapter 19.3 and 11.7:

Azvya Education Pvt. Ltd.VLSI Mentor
THE RISE, AND WHAT SETS IT
   A released line rises because a resistor charges a capacitance. From 0 to the
   specification's 0.7 x VDD threshold:

       tr  =  0.8473 x Rp x Cb

   with Rp the pull-up resistance and Cb the total bus capacitance -- traces,
   pads, connectors and every device's input capacitance, added up.

   The specification's limits (UM10204 Table 10):

       Standard-mode   100 kHz    tr <= 1000 ns
       Fast-mode       400 kHz    tr <=  300 ns
       Fast-mode Plus    1 MHz    tr <=  120 ns

   Worked, at a typical Cb of 150 pF:

       Standard-mode:   Rp <= 1000e-9 / (0.8473 x 150e-12)  =  7.87 kilohm
       Fast-mode:       Rp <=  300e-9 / (0.8473 x 150e-12)  =  2.36 kilohm
       Fast-mode Plus:  Rp <=  120e-9 / (0.8473 x 150e-12)  =  0.94 kilohm

   And the other bound, from the low-level requirement: the pad must sink enough
   current to hold VOL below 0.4 V while the pull-up fights it. At 3 mA sink,

       Rp >= (VDD - 0.4) / 3 mA   =  0.97 kilohm at 3.3 V

   So at 150 pF, Fast-mode Plus has a window of roughly 0.94 to 0.97 kilohm --
   which is to say, almost none. That is not a coincidence and it is the real
   design constraint: at high speed you do not choose Rp, you reduce Cb until a
   choice exists.

Two things follow immediately and are worth stating before any instrument appears.

A missing pull-up is not a large Rp. It is no Rp. The line has nothing to raise it and will sit low indefinitely after the last device releases — which is Chapter 23.6's symptom, not this chapter's. Devices with weak internal pull-ups muddy this, and that is exactly why 19.3 argues against relying on them: an internal pull-up of 50 kΩ against 150 pF gives tr ≈ 6.4 µs, which is six times the Standard-mode limit. The bus will work, slowly and unreliably, which is worse than not working.

Adding a device raises Cb. Every device on the bus contributes input capacitance, typically 5–10 pF, and so does every centimetre of trace and every connector. A bus that was inside budget with three devices can be outside it with five, with nothing else changed — and the failure will be blamed on the device that was added rather than on the resistor that was always marginal.

2. Two Distinctions a Number Cannot Make

Uniform is not the same as intermittent

Azvya Education Pvt. Ltd.VLSI Mentor
TWO BUSES WITH THE SAME MAXIMUM
   BUS A   every rise is 400 ns
   BUS B   most rises are 180 ns, a few are 400 ns

   max_latency = 400 ns on both. Same number, different cause, different fix.

   A is a constant: a resistor too large, or a capacitance too large, for this
     net. Present on the very first release and on every one after.

   B is BIMODAL: something intermittently loading the net. A device powering up
     or down, a bus switch changing segments, a connector making and breaking,
     a second board being hot-plugged.

A maximum cannot distinguish them, and neither can a mean — bus B's mean sits between the two modes, at a value that occurs on no actual release. The distinction needs a distribution, and four buckets are enough: a uniform bus fills one, a bimodal bus fills two with a gap.

A low pulse with no driver is not a slow edge

Both read as "the line was low when it should have been high". They are different failures with different fixes:

slow edgeglitch
shapelow continuously after a release, then rises oncea dip on a line that had already reached high
ends becausethe pull-up finished chargingit ended on its own
what it is aboutRp and Cbcoupling, ground bounce, a supply transient, a neighbouring signal
what you changea resistor, or the number of deviceslayout, shielding, grounding, decoupling

The specification supplies the discriminator as a number. tSP (Chapter 11.8) requires a device to suppress spikes up to 50 ns — so a low pulse narrower than that is noise the bus is required to tolerate, and one wider is an event that needs explaining.

A rise, a spike and a hold

10 cycles
Ten phases. A device's drive-low is asserted for the first two phases and then released. The slow-rise trace stays low for two further phases before reaching high. The glitch trace is high throughout except for a single one-phase dip in the sixth phase. The hold trace is high except for a three-phase dip beginning in the same place. No device is driving during either dip.driven lowdriven lowthe risethe riseidleidlethe device releasesthe device releasesa dip with nobody drivinga dip with nobody drivingdrive_lowslow_riseglitchholdt0t1t2t3t4t5t6t7t8t9
Figure 1 — three low-line events that a single latency number would conflate. A device drives, releases, and the pull-up takes two phases to raise the line: that is a rise. On a settled line with nobody driving, a one-phase dip is a spike the bus must tolerate, and a three-phase dip is an event. The first is about the resistor; the other two are about the layout, and they differ from each other only in width.

3. The Profiler

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_profile.sv — a rise-latency histogram and a spike counter
   // -----------------------------------------------------------------------------
   // i2c_edge_profile.sv
   // Chapter 23.5's instrument. Chapter 23.3's classifier answers "is this bus inside
   // its rise-time budget"; this one answers the next question, which is "WHICH
   // COMPONENT", and it needs two things a single latency number cannot give.
   //
   // ── ONE: A DISTRIBUTION, NOT A MAXIMUM ──────────────────────────────────────
   //
   // A maximum cannot tell these apart, and they have different causes and different
   // fixes:
   //
   //   every rise is 400 ns              a resistor too large, or a capacitance too
   //                                     large, for the whole bus. Uniform, constant,
   //                                     present on the first release and every one
   //                                     after.
   //
   //   most rises are 180 ns and a few   something intermittently loading the net --
   //   are 400 ns                        a device powering up or down, a bus switch,
   //                                     a segment being connected. BIMODAL.
   //
   // Both report max_latency = 400. They are not the same problem, and the second is
   // invisible to any instrument that keeps only an extremum. Four buckets are enough
   // to see the difference: a uniform bus fills one bucket, a bimodal bus fills two
   // with a gap between them.
   //
   // ── TWO: A LOW PULSE WITH NO DRIVER IS NOT A SLOW EDGE ──────────────────────
   //
   // Both look like "the line went low when it should have been high", and they are
   // different failures:
   //
   //   a SLOW EDGE is low CONTINUOUSLY AFTER A RELEASE and then rises once. It is
   //   the pull-up charging, and it is about R and C.
   //
   //   a GLITCH is a low excursion on a line that had ALREADY reached high, with
   //   nobody driving, that ends on its own. It is coupling, a ground bounce, a
   //   supply transient, or a neighbouring signal -- and it is about layout and
   //   shielding, not about the resistor.
   //
   // The specification gives the discriminator a number: UM10204 Table 10's tSP says
   // a device must suppress spikes up to 50 ns. A low pulse narrower than that is
   // noise the bus is required to tolerate; one wider is an event. `GLITCH_MAX` is
   // tSP expressed in sample clocks, and a pulse is counted as a glitch only if it
   // both starts from a settled HIGH line and ends within it.
   //
   // A long low excursion with nobody driving is counted separately as `n_hold`: not
   // noise, not a rise, but something holding the line. That is Chapter 23.6.
   //
   // SAMPLE-RATE HONESTY. This instrument sees what the sample clock can resolve. A
   // spike narrower than one sample period is invisible to it, exactly as it is to an
   // analyser at the same rate, and no amount of counting logic changes that. The
   // bench measures the resolution limit rather than assuming it.
   // -----------------------------------------------------------------------------

   module i2c_edge_profile #(
      // Bucket edges for the rise-latency histogram, in sample clocks. A latency L
      // lands in bucket 0 if L <= T0, bucket 1 if L <= T1, bucket 2 if L <= T2, and
      // bucket 3 otherwise. Derived from the speed mode's tr, not chosen: T1 is
      // normally the budget itself, so bucket 3 is "out of specification".
      parameter integer T0 = 2,
      parameter integer T1 = 5,
      parameter integer T2 = 9,
      // Widest low pulse still counted as a spike rather than an event. tSP in
      // sample clocks.
      parameter integer GLITCH_MAX = 2,
      parameter integer CNT_W = 16
   ) (
      input  wire clk,
      input  wire rst_n,

      input  wire all_released,   // 1 when no known device is pulling this line
      input  wire line,           // the resolved bus, read back

      // Rise-latency histogram. The buckets sum to the number of COMPLETED rises.
      output reg [CNT_W-1:0] n_b0,
      output reg [CNT_W-1:0] n_b1,
      output reg [CNT_W-1:0] n_b2,
      output reg [CNT_W-1:0] n_b3,

      // Low excursions on a line that had already settled high, with nobody driving.
      output reg [CNT_W-1:0] n_glitch,   // ended within GLITCH_MAX -- noise
      output reg [CNT_W-1:0] n_hold,     // lasted longer -- something is holding it

      output reg [CNT_W-1:0] max_latency,
      output reg [CNT_W-1:0] n_rises
   );

      localparam [CNT_W-1:0] CNT_MAX = {CNT_W{1'b1}};

      reg all_released_q, line_q;
      wire release_edge = ~all_released_q & all_released;
      wire line_fall    =  line_q & ~line;

      reg rising;              // measuring a rise that began at a release
      reg settled;             // the line has been high with nobody driving
      reg excursion;           // measuring a low pulse on a settled line
      reg [CNT_W-1:0] timer;

      always @(posedge clk) begin
         if (!rst_n) begin
            all_released_q <= 1'b1; line_q <= 1'b1;
            n_b0 <= 0; n_b1 <= 0; n_b2 <= 0; n_b3 <= 0;
            n_glitch <= 0; n_hold <= 0;
            max_latency <= 0; n_rises <= 0;
            rising <= 1'b0; settled <= 1'b0; excursion <= 1'b0;
            timer <= 0;
         end else begin
            all_released_q <= all_released;
            line_q         <= line;

            // ── measuring a rise ────────────────────────────────────────────────
            if (rising) begin
               if (!all_released) begin
                  // Somebody pulled again before it finished. Not a measurement.
                  rising <= 1'b0;
               end else if (line) begin
                  rising  <= 1'b0;
                  settled <= 1'b1;
                  if (n_rises != CNT_MAX) n_rises <= n_rises + 1'b1;
                  if (timer > max_latency) max_latency <= timer;
                  if      (timer <= T0[CNT_W-1:0]) begin if (n_b0 != CNT_MAX) n_b0 <= n_b0 + 1'b1; end
                  else if (timer <= T1[CNT_W-1:0]) begin if (n_b1 != CNT_MAX) n_b1 <= n_b1 + 1'b1; end
                  else if (timer <= T2[CNT_W-1:0]) begin if (n_b2 != CNT_MAX) n_b2 <= n_b2 + 1'b1; end
                  else                             begin if (n_b3 != CNT_MAX) n_b3 <= n_b3 + 1'b1; end
               end else begin
                  timer <= timer + 1'b1;
               end

            // ── measuring a low excursion on a settled line ─────────────────────
            end else if (excursion) begin
               if (!all_released) begin
                  // A device started driving during the excursion, so whatever it
                  // was is now indistinguishable from an intentional pull. Abandon
                  // the measurement rather than attribute it.
                  excursion <= 1'b0;
                  settled   <= 1'b0;
               end else if (line) begin
                  excursion <= 1'b0;
                  if (timer <= GLITCH_MAX[CNT_W-1:0]) begin
                     if (n_glitch != CNT_MAX) n_glitch <= n_glitch + 1'b1;
                  end else begin
                     if (n_hold != CNT_MAX) n_hold <= n_hold + 1'b1;
                  end
               end else begin
                  timer <= timer + 1'b1;
               end

            // ── idle: watch for a release, or for a glitch ──────────────────────
            end else begin
               if (!all_released) begin
                  settled <= 1'b0;
               end else if (release_edge) begin
                  if (line) begin
                     // A rise with no measurable duration. Still a rise.
                     settled <= 1'b1;
                     if (n_rises != CNT_MAX) n_rises <= n_rises + 1'b1;
                     if (n_b0 != CNT_MAX)    n_b0    <= n_b0 + 1'b1;
                  end else begin
                     rising <= 1'b1;
                     timer  <= 0;
                  end
               end else if (settled && line_fall) begin
                  // The line had reached high with nobody driving, and has now gone
                  // low anyway. Nothing on this bus asked for that.
                  excursion <= 1'b1;
                  // Starts at ONE, not zero: the falling edge is itself a sample at
                  // which the line read low, so it counts. `timer` is then the
                  // number of samples the line spent low, which is the quantity
                  // tSP is expressed in and the one a reader will compare against
                  // an analyser's own pulse-width column.
                  timer     <= 1;
               end else if (line) begin
                  // A released line that reads HIGH is settled, whether or not this
                  // instrument watched it get there.
                  //
                  // The first version set `settled` only on a completed rise, which
                  // made the block blind to noise on a bus that had been idle since
                  // power-on -- precisely the bus you instrument when hunting noise.
                  // It reported zero glitches, correctly formatted, on a line being
                  // pulsed once every three clocks. A diagnostic that needs traffic
                  // before it will report a fault is the wrong shape for a fault
                  // that appears when there is no traffic.
                  settled <= 1'b1;
               end
            end
         end
      end

   endmodule

Two design decisions carry the chapter.

The buckets sum to the completed rises, and that sum is checked. A histogram that drops or double-counts is wrong in a way no individual bucket reveals — and the bucket a reader acts on is usually the smallest one.

An excursion begins only on a line that had settled high. Without that guard, a bus that is LOW at the moment reset is released produces a phantom event: the instrument's line_q register comes out of reset holding 1, so the first sample of a low line looks like a falling edge that nothing caused. A stuck bus at power-on would then be reported as noise, which sends a reader to the layout when the fault is a device still in reset.

4. Four Signatures

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_edge_profile_tb.sv — four signatures, the bucket edges, the tSP boundary, and the resolution limit
   // -----------------------------------------------------------------------------
   // i2c_edge_profile_tb.sv
   // The oracle, and Chapter 23.5's experiment: four electrical signatures produced
   // deliberately, profiled, and shown to be distinguishable from each other.
   //
   // The bench drives `line` and `all_released` directly rather than through a bus
   // model, and that is a deliberate choice. A bus model produces the behaviour it
   // was written to produce; the point here is to produce FOUR behaviours whose
   // only common property is that they all look like "the line was low when it
   // should have been high", and to show that the profile separates them.
   //
   // Bounded: every wait is a fixed number of edges, and a watchdog terminates the
   // run independently. Every stimulus assignment lands 1 ns after a clock edge, so
   // no DUT input ever changes at a sampling instant.
   // -----------------------------------------------------------------------------

   `timescale 1ns/1ps

   module i2c_edge_profile_tb;

      localparam integer T0 = 2, T1 = 5, T2 = 9, GLITCH_MAX = 2, CNT_W = 16;

      reg clk = 1'b0;
      reg rst_n = 1'b0;
      always #5 clk = ~clk;

      reg all_released = 1'b1;
      reg line = 1'b1;

      wire [CNT_W-1:0] n_b0, n_b1, n_b2, n_b3, n_glitch, n_hold, max_latency, n_rises;

      integer errors = 0, checks = 0, neg_detected = 0, negative_mode = 0;

      // A NARROW instance, so the saturation bound is reachable. At CNT_W = 16 the
      // bound is 65535 and no run comes near it, which leaves the saturation the
      // header claims entirely untested.
      reg  nq_rst_n = 1'b0, nq_all_released = 1'b1, nq_line = 1'b1;
      wire [3:0] nq_b0, nq_b3, nq_rises, nq_glitch;
      i2c_edge_profile #(.T0(T0), .T1(T1), .T2(T2), .GLITCH_MAX(GLITCH_MAX), .CNT_W(4))
      narrow (
         .clk(clk), .rst_n(nq_rst_n), .all_released(nq_all_released), .line(nq_line),
         .n_b0(nq_b0), .n_b1(), .n_b2(), .n_b3(nq_b3),
         .n_glitch(nq_glitch), .n_hold(), .max_latency(), .n_rises(nq_rises)
      );

      // How often the excursion guard is CONSUMED with `settled` low. The guard is
      // read only here, so this counter is what the equivalence argument for two of
      // the surviving mutations rests on -- see the chapter's mutation section.
      integer guard_low_consumed = 0;
      always @(posedge clk) if (rst_n)
         if (all_released && dut.line_q && !line && !dut.rising && !dut.excursion
             && !dut.settled)
            guard_low_consumed = guard_low_consumed + 1;

      i2c_edge_profile #(.T0(T0), .T1(T1), .T2(T2), .GLITCH_MAX(GLITCH_MAX), .CNT_W(CNT_W))
      dut (
         .clk(clk), .rst_n(rst_n), .all_released(all_released), .line(line),
         .n_b0(n_b0), .n_b1(n_b1), .n_b2(n_b2), .n_b3(n_b3),
         .n_glitch(n_glitch), .n_hold(n_hold),
         .max_latency(max_latency), .n_rises(n_rises)
      );

      // ------------------------------------------------------------------ helpers
      task step; begin @(posedge clk); #1; end endtask
      task tick (input integer n); integer k; begin for (k=0;k<n;k=k+1) step; end endtask

      task chk (input [255:0] name, input integer got, input integer exp);
         begin
            checks = checks + 1;
            if (got !== exp) begin
               if (negative_mode) neg_detected = neg_detected + 1;
               else begin
                  errors = errors + 1;
                  $display("  FAIL %0s: got %0d expected %0d", name, got, exp);
               end
            end else if (negative_mode) begin
               errors = errors + 1;
               $display("  FAIL negative proof did not fire: %0s", name);
            end
         end
      endtask

      task do_reset;
         begin rst_n = 1'b0; all_released = 1'b1; line = 1'b1; tick(3); rst_n = 1'b1; tick(2); end
      endtask

      // One release whose rise takes exactly `lat` sample clocks. A device pulls the
      // line low, lets go, and the line reads high `lat` edges after the release
      // edge -- so `lat` is the number the instrument must report.
      task release_with_latency (input integer lat); integer k;
         begin
            all_released = 1'b0; line = 1'b0; tick(2);
            all_released = 1'b1;
            step;                                 // the release edge
            for (k = 0; k < lat; k = k + 1) step; // lat edges with the line low
            line = 1'b1;
            tick(2);
         end
      endtask

      // A low pulse `w` SAMPLES wide on a line that has already settled high, with
      // nobody driving. Nothing on the bus asked for this.
      //
      // `w` is the number of sampling edges at which the line reads LOW, which is
      // exactly what the instrument reports -- so a failure here is a failure of the
      // instrument rather than of the helper's arithmetic. Getting that alignment
      // wrong is the same off-by-one that Chapter 23.3's release helper had, and it
      // presents the same way: several failures that all look like the design
      // miscounting by one.
      task inject_pulse (input integer w); integer k;
         begin
            all_released = 1'b1; line = 1'b1; tick(3);   // let it settle
            line = 1'b0;
            for (k = 0; k < w; k = k + 1) step;          // w samples with the line low
            line = 1'b1;
            tick(3);
         end
      endtask

      integer i;

      initial begin
         $display("i2c_edge_profile_tb");

         // ---------------------------------------------------------------------
         // T1 -- every bucket boundary, on both sides. A histogram whose edges are
         // never tested at the edge is a histogram whose numbers nobody has checked.
         // ---------------------------------------------------------------------
         $display("T1 bucket boundaries, at the edge and one beyond");
         do_reset;
         release_with_latency(T0);      chk("T1 exactly T0 is bucket 0", n_b0, 1);
         release_with_latency(T0 + 1);  chk("T1 one beyond T0 is bucket 1", n_b1, 1);
         release_with_latency(T1);      chk("T1 exactly T1 is bucket 1", n_b1, 2);
         release_with_latency(T1 + 1);  chk("T1 one beyond T1 is bucket 2", n_b2, 1);
         release_with_latency(T2);      chk("T1 exactly T2 is bucket 2", n_b2, 2);
         release_with_latency(T2 + 1);  chk("T1 one beyond T2 is bucket 3", n_b3, 1);
         chk("T1 six rises recorded", n_rises, 6);
         chk("T1 buckets sum to rises", n_b0 + n_b1 + n_b2 + n_b3, n_rises);
         chk("T1 max latency is the largest", max_latency, T2 + 1);
         chk("T1 no glitches from any of that", n_glitch, 0);
         chk("T1 no holds from any of that", n_hold, 0);

         // ---------------------------------------------------------------------
         // T2 -- SIGNATURE A: a uniformly slow bus. Every rise lands in the same
         // out-of-budget bucket. This is a resistor or a capacitance that applies
         // to the whole net, and it is present on the very first release.
         // ---------------------------------------------------------------------
         $display("T2 signature A -- uniformly slow");
         do_reset;
         for (i = 0; i < 30; i = i + 1) release_with_latency(T2 + 2);
         $display("      b0=%0d b1=%0d b2=%0d b3=%0d  glitch=%0d hold=%0d max=%0d",
                  n_b0, n_b1, n_b2, n_b3, n_glitch, n_hold, max_latency);
         chk("T2 every rise in the out-of-budget bucket", n_b3, 30);
         chk("T2 nothing anywhere else", n_b0 + n_b1 + n_b2, 0);
         chk("T2 no glitches", n_glitch, 0);

         // ---------------------------------------------------------------------
         // T3 -- SIGNATURE B: BIMODAL. Most rises fast, a minority slow, with an
         // empty bucket between them. Same max_latency as T2's uniform bus, and a
         // completely different cause: something intermittently loading the net.
         // ---------------------------------------------------------------------
         $display("T3 signature B -- bimodal");
         do_reset;
         for (i = 0; i < 30; i = i + 1)
            if (i % 6 == 0) release_with_latency(T2 + 2);   // 5 slow
            else            release_with_latency(T0);       // 25 fast
         $display("      b0=%0d b1=%0d b2=%0d b3=%0d  glitch=%0d hold=%0d max=%0d",
                  n_b0, n_b1, n_b2, n_b3, n_glitch, n_hold, max_latency);
         chk("T3 most rises are fast", n_b0, 25);
         chk("T3 a minority are out of budget", n_b3, 5);
         chk("T3 the buckets BETWEEN them are empty", n_b1 + n_b2, 0);
         // The finding that a maximum cannot produce: T2 and T3 have the SAME
         // max_latency and different distributions.
         chk("T3 max_latency is identical to the uniform bus", max_latency, T2 + 2);

         // ---------------------------------------------------------------------
         // T4 -- SIGNATURE C: noise. The line reaches high normally and is then
         // pulled low briefly by nothing. Narrow pulses are spikes the bus is
         // required to tolerate; wider ones are events.
         // ---------------------------------------------------------------------
         $display("T4 signature C -- noise, and the tSP boundary");
         do_reset;
         inject_pulse(1);             chk("T4 a 1-clock pulse is a glitch", n_glitch, 1);
         inject_pulse(GLITCH_MAX);    chk("T4 exactly tSP is still a glitch", n_glitch, 2);
         inject_pulse(GLITCH_MAX + 1);chk("T4 one beyond tSP is a HOLD", n_hold, 1);
         chk("T4 glitch count unchanged by the hold", n_glitch, 2);
         chk("T4 no rises were recorded", n_rises, 0);
         chk("T4 and no buckets filled", n_b0 + n_b1 + n_b2 + n_b3, 0);

         // ---------------------------------------------------------------------
         // T5 -- SIGNATURE D: a healthy bus. Everything in bucket 0, nothing
         // anywhere else, over a long window. The result worth wanting, and the one
         // that needs a long window to mean anything.
         // ---------------------------------------------------------------------
         $display("T5 signature D -- healthy, over a long window");
         do_reset;
         for (i = 0; i < 200; i = i + 1) release_with_latency(i % (T0 + 1));
         $display("      b0=%0d b1=%0d b2=%0d b3=%0d  glitch=%0d hold=%0d max=%0d over %0d rises",
                  n_b0, n_b1, n_b2, n_b3, n_glitch, n_hold, max_latency, n_rises);
         chk("T5 200 rises observed", n_rises, 200);
         chk("T5 all of them in bucket 0", n_b0, 200);
         chk("T5 none out of budget", n_b2 + n_b3, 0);
         chk("T5 no noise", n_glitch + n_hold, 0);

         // ---------------------------------------------------------------------
         // T6 -- the four signatures are DISTINGUISHABLE. A profile that produced
         // a plausible answer for each case separately would prove nothing; what
         // has to be shown is that no two of them produce the same profile.
         // ---------------------------------------------------------------------
         $display("T6 the four signatures differ from each other");
         // Re-measure A and B into locals so they can be compared directly.
         begin : compare
            integer a_b0, a_b3, a_gl, a_max;
            integer b_b0, b_b3, b_gl, b_max;
            integer c_b0, c_b3, c_gl, c_max, c_hold;
            do_reset;
            for (i = 0; i < 30; i = i + 1) release_with_latency(T2 + 2);
            a_b0 = n_b0; a_b3 = n_b3; a_gl = n_glitch; a_max = max_latency;
            do_reset;
            for (i = 0; i < 30; i = i + 1)
               if (i % 6 == 0) release_with_latency(T2 + 2); else release_with_latency(T0);
            b_b0 = n_b0; b_b3 = n_b3; b_gl = n_glitch; b_max = max_latency;
            do_reset;
            for (i = 0; i < 30; i = i + 1) begin
               release_with_latency(T0);
               if (i % 3 == 0) inject_pulse(1);
            end
            c_b0 = n_b0; c_b3 = n_b3; c_gl = n_glitch; c_max = max_latency; c_hold = n_hold;

            $display("      A uniform : b0=%0d b3=%0d glitch=%0d max=%0d", a_b0, a_b3, a_gl, a_max);
            $display("      B bimodal : b0=%0d b3=%0d glitch=%0d max=%0d", b_b0, b_b3, b_gl, b_max);
            $display("      C noisy   : b0=%0d b3=%0d glitch=%0d max=%0d hold=%0d",
                     c_b0, c_b3, c_gl, c_max, c_hold);

            chk("T6 A and B share a maximum", (a_max == b_max) ? 1 : 0, 1);
            chk("T6 and differ in bucket 0",  (a_b0 != b_b0) ? 1 : 0, 1);
            chk("T6 A and C differ in bucket 3", (a_b3 != c_b3) ? 1 : 0, 1);
            chk("T6 B and C differ in glitches", (b_gl != c_gl) ? 1 : 0, 1);
            chk("T6 C has noise and no slow rises", (c_gl > 0 && c_b3 == 0) ? 1 : 0, 1);
            chk("T6 A has slow rises and no noise", (a_b3 > 0 && a_gl == 0) ? 1 : 0, 1);
         end

         // ---------------------------------------------------------------------
         // T7 -- the resolution limit, measured rather than assumed. A pulse
         // narrower than one sample period is invisible to this instrument, exactly
         // as it is to an analyser at the same rate. Stating that in a comment is
         // cheap; showing it is what makes the zero in T5 honest.
         // ---------------------------------------------------------------------
         $display("T7 the resolution limit");
         do_reset;
         all_released = 1'b1; line = 1'b1; tick(4);
         // A low excursion shorter than the interval between two sampling edges.
         @(posedge clk); #1; line = 1'b0; #2; line = 1'b1;
         tick(4);
         chk("T7 a sub-sample pulse is NOT counted", n_glitch, 0);
         chk("T7 nor as a hold", n_hold, 0);
         // The control: the same instrument DOES see a pulse one sample wide, so
         // the zero above is a resolution limit and not a dead counter.
         inject_pulse(1);
         chk("T7 but a one-sample pulse IS counted", n_glitch, 1);

         // ---------------------------------------------------------------------
         // T9 -- reset released onto a LOW bus. The instrument's `line_q` register
         // comes out of reset holding 1, so the first sample of a low line looks
         // like a falling edge that nothing caused. Without the settled guard that
         // is counted as an excursion, and a bus that was stuck low at power-on is
         // reported as noise.
         //
         // This is not a contrived case: a device still in reset holding SDA down is
         // exactly how a board comes up, and it is Chapter 23.6's subject.
         // ---------------------------------------------------------------------
         $display("T9 reset released onto a bus that is already LOW");
         rst_n = 1'b0; all_released = 1'b1; line = 1'b0; tick(3);
         guard_low_consumed = 0;
         rst_n = 1'b1; tick(6);
         // Let the line rise BEFORE checking. An excursion that is still in flight
         // has not been binned yet, so a check taken while the line is still low
         // reads zero for a phantom that is about to be counted -- and passes. The
         // first version of this test did exactly that and let a mutation through.
         line = 1'b1; tick(4);
         chk("T9 no phantom glitch at reset", n_glitch, 0);
         chk("T9 no phantom hold at reset", n_hold, 0);
         chk("T9 the guard WAS consumed low exactly once", guard_low_consumed, 1);
         // And the instrument still works afterwards.
         inject_pulse(1);
         chk("T9 a real glitch after that IS counted", n_glitch, 1);

         // ---------------------------------------------------------------------
         // T10 -- an aborted rise. A device pulls the line low again before it has
         // risen, so the pull-up never finished and there is no latency to record.
         // Counting it would put a wrong number in a bucket.
         // ---------------------------------------------------------------------
         $display("T10 an interrupted rise is not a measurement");
         do_reset;
         all_released = 1'b0; line = 1'b0; tick(2);
         all_released = 1'b1; step; step; step;      // rising, line still low
         all_released = 1'b0;         tick(3);       // pulled again
         all_released = 1'b1; line = 1'b1; tick(3);  // and now released properly
         chk("T10 exactly one rise recorded", n_rises, 1);
         chk("T10 it is a zero-length rise", n_b0, 1);
         chk("T10 nothing landed in a slow bucket", n_b1 + n_b2 + n_b3, 0);
         chk("T10 and no glitch was manufactured", n_glitch + n_hold, 0);

         // ---------------------------------------------------------------------
         // T11 -- a release onto a line that is already high. Zero latency, which
         // is what an IDEAL bus model produces on every single release, and it must
         // be counted rather than silently dropped.
         // ---------------------------------------------------------------------
         $display("T11 a zero-length rise is still a rise");
         do_reset;
         all_released = 1'b0; line = 1'b1; tick(2);   // driving intent, ideal line
         all_released = 1'b1; tick(3);
         chk("T11 counted as a rise", n_rises, 1);
         chk("T11 landed in bucket 0", n_b0, 1);
         chk("T11 max latency is zero", max_latency, 0);

         // ---------------------------------------------------------------------
         // T12 -- the saturation bound, on the narrow instance.
         // ---------------------------------------------------------------------
         $display("T12 counter saturation at CNT_W=4 (max 15)");
         nq_rst_n = 1'b0; nq_all_released = 1'b1; nq_line = 1'b1; tick(3);
         nq_rst_n = 1'b1; tick(2);
         for (i = 0; i < 40; i = i + 1) begin
            nq_all_released = 1'b0; nq_line = 1'b0; tick(2);
            nq_all_released = 1'b1; step;
            nq_line = 1'b1; tick(2);
         end
         $display("      narrow instance: rises=%0d b0=%0d", nq_rises, nq_b0);
         chk("T12 rise counter saturated at 15", nq_rises, 15);
         chk("T12 bucket 0 saturated at 15", nq_b0, 15);
         chk("T12 neither wrapped", (nq_rises >= 15 && nq_b0 >= 15) ? 1 : 0, 1);
         chk("T12 the slow bucket stayed empty", nq_b3, 0);

         // ---------------------------------------------------------------------
         // T8 -- negative proof. Eight deliberately wrong expectations.
         // ---------------------------------------------------------------------
         $display("T8 negative proof -- ten deliberately wrong expectations");
         do_reset;
         release_with_latency(T0);
         release_with_latency(T2 + 4);
         inject_pulse(1);
         inject_pulse(GLITCH_MAX + 2);
         chk("T8 setup: one fast rise", n_b0, 1);
         chk("T8 setup: one slow rise", n_b3, 1);
         chk("T8 setup: one glitch", n_glitch, 1);
         chk("T8 setup: one hold", n_hold, 1);
         negative_mode = 1;
         chk("T8a wrong bucket-0 count", n_b0, 9);
         chk("T8b wrong bucket-3 count", n_b3, 0);
         chk("T8c wrong glitch count",   n_glitch, 0);
         chk("T8d wrong hold count",     n_hold, 0);
         chk("T8e wrong rise total",     n_rises, 99);
         chk("T8f wrong bucket sum",     n_b0 + n_b1 + n_b2 + n_b3, 0);
         chk("T8g wrong maximum",        max_latency, 0);
         chk("T8h wrong emptiness claim",n_b1 + n_b2, 7);
         chk("T8i wrong saturation bound", nq_rises, 40);
         chk("T8j wrong guard claim",      guard_low_consumed, 77);
         negative_mode = 0;
         chk("T8 all ten negatives detected", neg_detected, 10);

         $display("");
         $display("checks=%0d errors=%0d negatives_detected=%0d/10", checks, errors, neg_detected);
         if (errors == 0) $display("RESULT: PASS"); else $display("RESULT: FAIL");
         $finish;
      end

      initial begin
         #5000000;
         $display("RESULT: FAIL -- watchdog expired, the run did not terminate");
         $finish;
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
THE FOUR SIGNATURES, MEASURED
                b0    b1   b2   b3   glitch  hold   max
   A uniform     0     0    0   30      0      0     11
   B bimodal    25     0    0    5      0      0     11
   C noisy      30     0    0    0     10      0      2
   D healthy   200     0    0    0      0      0      2     (over 200 rises)

Read the first two rows together. A and B have the same maximum and completely different distributions. That is the whole argument for a histogram, stated as two numbers that agree and two columns that do not, and the bench asserts both halves: that the maxima are equal, and that bucket 0 differs.

Row C is the one a rise-time instrument alone would call healthy — every rise in bucket 0, maximum well inside budget — and it has ten spikes on a line nobody was driving.

Row D is the result worth wanting, and it needs the release count next to it. Two hundred rises, all in bucket 0, no spikes: that eliminates the electrical layer. Four rises, all in bucket 0, eliminates nothing.

5. The Resolution Limit, Measured

T7 injects a low pulse narrower than one sample period and asserts that the instrument does not count it. That is not a defect being papered over; it is the instrument's resolution, and it is the same limit an analyser at the same sample rate has.

The test matters because of what it protects. glitch = 0 is one of the results this chapter leans on, and it means "no spike wide enough for this instrument to see" — which is a different and weaker claim than "no spikes". Stating that in a comment is cheap; the test makes the zero honest, and it comes with a control: the same instrument does count a pulse one sample wide, so the zero is a resolution limit rather than a dead counter.

Azvya Education Pvt. Ltd.VLSI Mentor
T7 — WHAT A ZERO MEANS
   a pulse narrower than one sample period   ->  glitch = 0   (invisible)
   a pulse exactly one sample wide           ->  glitch = 1   (the control)

If spikes are suspected and the profiler reports none, the next instrument is a scope, not a faster count.

6. Mutation

Fourteen mutations, each written to its own path and that path hashed.

Azvya Education Pvt. Ltd.VLSI Mentor
MUTATION RESULTS
   FIRST CAMPAIGN              14 valid    8 KILLED   6 SURVIVED
   AFTER T9, T10, T11, T12     14 valid   12 KILLED   2 SURVIVED (both equivalent)

Four of the six survivors were stimulus gaps, and they are worth naming because none of them is exotic:

E06 — an excursion measured on a line that never settled. Killed by T9: reset released onto a bus that is already low. The first version of T9 did not kill it either, because it checked the hold count while the excursion was still in flight — an event that has not been binned yet reads as zero, and the check passed. Moving the check after the line rises fixed it.

E07 — a zero-length rise not counted. Killed by T11. A release onto a line that is already high is what an ideal bus model produces on every single release, so this is the common case in most simulations and the bench had no test for it.

E09 — an aborted rise counted anyway. Killed by T10. A device pulling the line low again before it has risen means the pull-up never finished, so there is no latency to record and counting it puts a wrong number in a bucket.

E14 — counters wrap instead of saturating. Killed by T12, a narrow instance at CNT_W = 4. The same parameter-boundary shape as every previous chapter in this module.

The two survivors, and why they are equivalent

E10 never clears settled when a device starts driving; E13 sets it even while the line is low. Both perturb settled — and settled is read in exactly one place, the guard settled && line_fall in the idle branch.

The argument is that both mutations only ever set settled earlier or keep it longer, never later or shorter, and that at every instant where the guard is consumed the baseline already has it set. The bench measures the exception rather than assuming there is none:

Azvya Education Pvt. Ltd.VLSI Mentor
THE GUARD, INSTRUMENTED
   guard_low_consumed counts the clocks at which the excursion guard is evaluated
   with `settled` LOW -- the only instants at which these two mutations could
   change an outcome.

   Over the whole run:  guard_low_consumed = 1

   That one instant is the reset-low case in T9, and at it the baseline has
   settled = 0 and so does each mutant: E10's branch never fires (all_released is
   high throughout), and E13's fires only after the guard has already been
   evaluated. So the single instant where the difference could matter is an
   instant where there is no difference.

7. What This Cannot Settle

It cannot distinguish Rp from Cb. tr = 0.8473 · Rp · Cb is one equation in two unknowns, and a slow rise is equally consistent with a resistor twice too large and a capacitance twice too large. Separating them takes a second measurement: change one and see whether tr moves proportionally, or count the devices and estimate Cb from their datasheets.

It cannot see anything analogue. There is no ramp here, no threshold and no curve — only "for this many samples, a sampler still read low". A rise that reaches 0.65 · VDD and stalls there is, to this instrument, a rise that has not finished, and to a scope it is a completely different and much more interesting picture.

It cannot see a spike narrower than one sample. §5 is that limit, and the answer to a suspected fast spike is a scope.

And a glitch count of zero is not "no noise". It is "no low excursion on a settled line, wider than one sample, that this instrument was running to see". Noise that couples into SDA while a device is driving it low is invisible here — the line is already low — and noise that couples into SCL during a high period may well corrupt a bit without ever bringing the line below a threshold.

8. Misconceptions

9. Debug Lab

One bus, one line slow, and a symptom that moved when a board was reseated

A signature that points at a component, and the second measurement that names it
Buggy Code
// A motherboard with an I2C bus running at 400 kHz to three daughter cards
// through a connector. Intermittent NACKs from the card in slot 2, perhaps one
// transfer in two hundred. Reseating the card changes the rate -- sometimes it
// gets better, once it got worse, and twice it cleared entirely for a few hours.
//
// "Reseating changes it" is a strong correlation and a weak cause. It is
// consistent with:
//
//   (a) a marginal connector contact on the bus lines themselves
//   (b) a marginal ground contact, so the card's reference moves
//   (c) the card's capacitance pushing an already-marginal bus over
//   (d) a cold solder joint on the card that moves with mechanical stress
//   (e) nothing to do with the connector: the reseat power-cycles the card, and
//       what actually changed was its internal state
//
// (e) deserves naming because it is the one people forget. Every reseat is also
// a power cycle, and a correlation with reseating is a correlation with two
// things at once.
Symptom

The profiler instantiated on BOTH lines, separately, with the bucket edges derived from Fast-mode's 300 ns limit at the sample clock in use: bucket 0 is inside 120 ns, bucket 1 inside 200 ns, bucket 2 inside 300 ns, bucket 3 is out of specification.

SCL, 40,000 releases: b0=39,996 b1=4 b2=0 b3=0 glitch=0 hold=0 max=138 ns

SDA, 40,000 releases: b0=31,010 b1=112 b2=39 b3=8,839 glitch=0 hold=0 max=690 ns

Three things are visible here that a single number would not have shown.

SCL and SDA are DIFFERENT. SCL is essentially all in bucket 0 with a maximum of 138 ns; SDA has nearly nine thousand releases out of specification. The two lines share a resistor value, a routing environment and a connector, so a fault on one and not the other rules out everything that applies to both -- including candidate (b), a ground contact, which would disturb both lines.

SDA is BIMODAL, not uniformly slow. 31,010 releases in bucket 0 and 8,839 in bucket 3, with only 151 in the two buckets between them. A resistor too large produces one bucket, not two with a gap; something is intermittently loading the net.

glitch = 0 on both lines, over 80,000 releases between them. Coupling and ground bounce are not the mechanism, which is what candidate (b) would have predicted from the other direction.

And one measurement that is not a count. Running the profile with the card in slot 2 REMOVED:

SDA, 40,000 releases: b0=39,982 b1=18 b2=0 b3=0 glitch=0 hold=0 max=131 ns

With the card out, SDA looks exactly like SCL.

Root Cause

The bimodal distribution on SDA alone, with SCL clean and no spikes anywhere, places the fault on the SDA net and identifies it as an intermittent load rather than a constant one. The card-out measurement puts it on the slot-2 card.

That is three candidates eliminated by two measurements, and it is worth being precise about which:

(b) a ground contact -- dead. It would disturb both lines, and SCL is clean. (c) capacitance from the card -- dead as stated. Added capacitance is CONSTANT while the card is fitted, and would move every SDA release into a slower bucket. The distribution is bimodal, not shifted. (e) the power cycle -- still live at this point, and not addressed by anything above.

Two candidates remain: a marginal connector contact on SDA specifically (a), and a cold joint on the card's SDA net (d). Both are intermittent, both are on SDA alone, and the bimodal signature fits either.

The discriminating measurement is which SIDE of the connector moves. With the card fitted, the profiler was run twice: once reading SDA at the motherboard end of the connector, once at the card's own pin, simultaneously.

motherboard side: b0=31,048 b3=8,801 max=688 ns card side: b0=39,977 b3=0 max=142 ns

The card's own SDA pin sees a clean, fast bus. The motherboard side does not. So the slow rises exist on the motherboard side of the contact and not on the card side -- which means the two are not the same net for part of the time, and the fault is IN the contact. Candidate (d), a joint on the card, would have made the card side the worse of the two, and it is the better one.

Candidate (e) dies here too: a card whose internal state was the problem would not produce a difference ACROSS its own connector pin.

Inspection found the slot-2 SDA contact had a fractured spring. The contact was resistive rather than open, which is why the symptom was a slow rise and not a dead bus: a resistance in series with the pull-up path raises the effective Rp for the motherboard side, exactly as a larger resistor would, and it moved with vibration -- which is the intermittency.

CORRECTION: replace the connector. And add the per-line profile to the board's production test, because the whole argument above rests on SCL and SDA being measured separately, and a combined measurement would have shown a bus that is 93 percent fine.

PROOF, in an order where each step is attributable:

1. before: SDA bimodal at the motherboard side, clean at the card side, SCL clean, no spikes 2. after replacing the connector: SDA at the motherboard side reads b0=39,988 b3=0 max=134 ns -- which is SCL's profile, on the same bus 3. and the two sides of the contact now AGREE, which is the specific thing that was wrong 4. and the NACKs stop

Step 3 is the one that proves the root cause rather than the symptom. Step 2 alone would be consistent with having disturbed something during the repair; step 3 says the measurement that identified the fault now reads the same on both sides of it. Step 4 is last, and on its own would have proved nothing -- two of the reseats had already cleared the symptom for hours at a time.

10. Reason It Through

11. Questions

12. What This Chapter Settled

The arithmetic that connects a symptom to a component, and the reason a single rise time cannot complete that connection: one equation, two unknowns. Every useful step is a difference — one line against the other, one point on a net against another — because a difference cancels what they share.

Two distinctions a number cannot make. Uniform against bimodal, which have the same maximum and different causes, and which need a distribution to separate. And a slow edge against a glitch, which are both "the line was low when it should have been high" and differ in whether the line had already reached high — with tSP supplying the boundary between a spike the bus must tolerate and an event that needs explaining.

A profiler that reports both, with sixty-seven checks, zero errors and ten negative proofs. Four signatures produced deliberately and shown to differ from each other, including two with identical maxima. A resolution limit measured rather than assumed, with a control that makes its zero honest.

Fourteen mutations: twelve killed, two equivalent under a guard whose single consumption instant is instrumented and counted. Four of the six first-campaign survivors were stimulus gaps, and the most serious was a profiler that could not see noise on an idle bus — a diagnostic whose preconditions excluded its own use case.

One symptom has been deferred twice now. A line that never rises is not a slow rise, and a device that has released but whose pad still pulls is not noise — both are something holding the bus, and neither is fixed by a resistor. Chapter 23.6 is that problem: how to find out who is holding the line, and what recovery actually works.

Continue learning

Related tutorials