I²C · Module 17
The SCL Timing Generator — Phases, Strobes and the Readback Rule
Where Table 10's microseconds become counts of system-clock cycles. Derives the period budget that must include rise and fall time, shows why rounding down is always illegal and rounding up always legal, and builds a generator that leaves its low phase only when the line actually reads back high — which implements clock stretching and clock synchronization with no extra logic.
This is the first block in the master that touches copper.
Its job sounds trivial — produce a clock — and it contains two arithmetic traps and one architectural decision that determines whether the master works on a real bus at all. All three are the kind that produce a design which passes every simulation and fails on hardware.
1. The One Thing a Generator Must Never Do
Start with the architectural decision, because everything else is detail by comparison.
So this generator does not advance on its own count. It counts the low phase from its own divider, then releases SCL and waits for the line to read back high before it begins counting the high phase at all.
That single structural choice is worth more than it looks, because those two quoted paragraphs describe the same mechanism from two sides:
| Seen from | The observation | The required response |
|---|---|---|
| §3.1.6 — a target stretching | released SCL reads low | wait |
| §3.1.7 — another master with a longer low period | released SCL reads low | wait |
A generator that waits for the readback implements both, with no extra logic. A generator that advances on its own count implements neither — and against a stretching target every subsequent bit is driven and sampled at the wrong instant, so the failure presents as data corruption rather than as a timing bug. That misdirection is why the defect survives so long: engineers go looking at the datapath.
Chapter 17.1 called this one of the four time bases, and named its event source as "an SCL edge read back from the bus". This is that wiring.
2. The Two Instants
Table 10 locates exactly two moments inside one bit that the datapath cares about, and this block exposes them as single-cycle strobes so that the bit engine downstream contains no timing of its own.
drive_point — inside the LOW phase, at least tSU;DAT before SCL is released, so a bit placed here is set up before the rising edge.
sample_point — inside the HIGH phase, after SDA has settled.
Placing the strobes here rather than in the bit engine is what keeps the two blocks independent: change the speed mode and only parameters move. Chapter 17.6 consumes these strobes and computes nothing.
A registered strobe arrives a cycle after the count it was derived from, and a second register in the consumer adds another. Derive drive_point from a registered comparison and the block delivers two cycles less set-up than its parameter claims — so N_SU = 3 produces one cycle of tSU;DAT.
For a timing parameter taken out of Table 10, a parameter that does not mean what it is named after is not a stylistic matter. The strobes are continuous assignments on the phase and the count:
drive_point = (phase == PH_LOW) && (cnt == DRIVE_AT)
sample_point = (phase == PH_HIGH) && (cnt == SAMPLE_AT)3. The Phases Are Not Symmetric
tLOW(min) is greater than tHIGH(min) in all three modes — 4.7 > 4.0, 1.3 > 0.6, 0.5 > 0.26 µs.
So N_LOW and N_HIGH are separate parameters, and a generator built from a symmetric divider is either illegal at its rated frequency or legal only by running slower than it needs to. There is no configuration in which the symmetric version is both legal and fast.
4. The Period Budget — the First Arithmetic Trap
Here is the identity Module 11 derived and verified against the specification's own table:
tLOW(min) + tHIGH(min) + tr(max) + tf(max) = 1 / fSCL(max) EXACTLY
Standard mode: 4.7 + 4.0 + 1.0 + 0.3 = 10.0 us = 1/100 kHz
Fast mode: 1.3 + 0.6 + 0.3 + 0.3 = 2.5 us = 1/400 kHz
Fast-mode Plus: 0.5 + 0.26 + 0.12 + 0.12 = 1.0 us = 1/1000 kHzThe edges are not slack in the budget — they are the budget. A generator that allocates only tLOW + tHIGH and ignores the rise and fall times produces a clock that is legal on paper and too fast on a real bus, because the physical edges then eat into the phases rather than sitting beside them.
So the period count is a maximum of two terms:
N_low = ceil(tLOW_min / T_sys) N_r = ceil(tr_max / T_sys)
N_high = ceil(tHIGH_min / T_sys) N_f = ceil(tf_max / T_sys)
N = max( ceil(f_sys / f_bus_max), N_low + N_high + N_r + N_f )The second term is the load-bearing one, and dropping it is the first way to get this wrong. (The frequency term is defensive: Chapter 17.13 §7 proves it is mathematically redundant given Module 11's identity, because a sum of ceilings can never be less than the ceiling of the sum. Keep it anyway — it is what catches a mode table whose four times do not satisfy that identity.) Computed for Fast mode, not estimated:
| f_sys | T_sys (ns) | N_low | N_high | N_r | N_f | ceil(f_sys/f_max) | sum | N | achieved |
|---|---|---|---|---|---|---|---|---|---|
| 100 MHz | 10.000 | 130 | 60 | 30 | 30 | 250 | 250 | 250 | 400.0 kHz |
| 50 MHz | 20.000 | 65 | 30 | 15 | 15 | 125 | 125 | 125 | 400.0 kHz |
| 48 MHz | 20.833 | 63 | 29 | 15 | 15 | 120 | 122 | 122 | 393.4 kHz |
| 33 MHz | 30.303 | 43 | 20 | 10 | 10 | 83 | 83 | 83 | 397.6 kHz |
| 12 MHz | 83.333 | 16 | 8 | 4 | 4 | 30 | 32 | 32 | 375.0 kHz |
At 48 MHz and 12 MHz the sum is the larger term, so the achievable frequency is below the 400 kHz requested. A design that used only ceil(f_sys/f_bus) would program 120 and 30 there and produce phases shorter than the specified minimums.
It is generally not the frequency that was asked for. A block that silently accepts a 400 kHz request and delivers 375 kHz is fine; one that lets an integrator believe it delivered 400 kHz is not. Chapter 17.13 makes the report an output.
5. Rounding — the Second Arithmetic Trap
The rule is asymmetric, and knowing which direction is safe removes the whole class:
Rounding a phase count down gives a phase shorter than a specified minimum. Illegal.
Rounding a period count down raises the frequency above fSCL(max). Illegal.
Rounding up only ever makes the bus slower than requested — and fSCL has no minimum. Table 10 gives 0 as its lower bound in every mode, so a slow I²C bus is always conforming.
Checked against the frequency limit:
| f_sys | N | achieved | N−1 | achieved at N−1 | legal? |
|---|---|---|---|---|---|
| 100 MHz | 250 | 400.0 kHz | 249 | 401.6 kHz | NO |
| 50 MHz | 125 | 400.0 kHz | 124 | 403.2 kHz | NO |
| 33 MHz | 83 | 397.6 kHz | 82 | 402.4 kHz | NO |
So: always ceiling, never floor. Chapter 11 taught this as a timing rule; here it is an arithmetic policy in a parameter file, and it is the one place where "close enough" produces a non-conforming device.
6. The Generator, Simulated
tLOW, a stretch absorbed in the wait phase, then tHIGH counted from the line
10 cyclesThree things in that capture are the whole chapter.
The release at cycle 3 does not end the low phase. The generator stopped driving; the bus is still low, and the generator knows the difference because it reads the line. scl_drive_low and scl_bus are different signals for as long as the stretch lasts, which is Chapter 17.1's drive-intent-versus-observed-bus distinction doing real work.
The stretch is counted, not assumed. stretching is an output, and the block accumulates a cycle count while it waits — so Chapter 17.11 can impose a timeout on evidence rather than on a guess.
The high phase is measured from the line, and the first high cycle is the one in which the line was observed high. Entering the high-phase counter at zero instead of at two would make the observed high time N_HIGH + 1 cycles — still legal, since tHIGH has only a minimum, but the parameter would no longer mean what it says and the period would run a cycle long. Mutation M6 below does precisely that.
6a. The generator
Two counters, a four-state phase register, and two continuous assignments. Nothing here is simulation-only.
// -----------------------------------------------------------------------------
// i2c_scl_gen.sv
// The SCL timing generator: phases, the two datapath instants, and the one thing a
// generator must never do.
//
// THE CENTRAL DECISION. This block does not advance on its own count. It counts the
// LOW phase from its own divider, then RELEASES SCL and waits for the line to READ
// BACK high before it starts counting the HIGH phase at all.
//
// UM10204 §3.1.6: "Clock stretching pauses a transaction by holding the SCL line LOW.
// The transaction cannot continue until the line is released HIGH again."
// UM10204 §3.1.7: "The SCL line is therefore held LOW by the master with the longest
// LOW period. Masters with shorter LOW periods enter a HIGH wait-state during this
// time." ... "The first master to complete its HIGH period pulls the SCL line LOW
// again."
//
// Those two sentences describe the same mechanism from two sides, and a generator
// that waits for the readback implements BOTH with no extra logic. A generator that
// advances on its own count implements NEITHER: against a stretching target every
// subsequent bit is driven and sampled at the wrong instant, and the failure looks
// like data corruption rather than like a timing bug.
//
// THE TWO INSTANTS. Table 10 locates exactly two moments the datapath cares about
// inside one bit, and this block exposes them as single-cycle strobes so that the bit
// engine downstream contains no timing of its own:
//
// drive_point -- inside the LOW phase, at least tSU;DAT before SCL is released,
// so a bit placed here is set up before the rising edge.
// sample_point -- inside the HIGH phase, after SDA has settled.
//
// Placing the strobes here rather than in the bit engine is what keeps the two blocks
// independent: change the mode and only the parameters move.
//
// THE PHASES ARE NOT SYMMETRIC. tLOW(min) > tHIGH(min) in every mode (4.7 > 4.0,
// 1.3 > 0.6, 0.5 > 0.26 us), so N_LOW and N_HIGH are separate parameters. A generator
// built from a symmetric divide-by-two is illegal at the frequency limit in every
// mode, and legal only if it is run slower than it needs to be.
// -----------------------------------------------------------------------------
module i2c_scl_gen #(
// Phase lengths in system-clock cycles. Chapter 17.13 computes these from a
// system frequency and a mode; here they are given, so this block can be tested
// at small values instead of at ten thousand cycles per bit.
parameter int N_LOW = 13, // tLOW, in system-clock cycles
parameter int N_HIGH = 6, // tHIGH, in system-clock cycles
// Where inside the LOW phase the new bit is placed. Counted from the END of the
// low phase, so N_SU cycles of set-up remain before SCL is released.
parameter int N_SU = 3, // tSU;DAT, in system-clock cycles
// Where inside the HIGH phase SDA is sampled, counted from the moment the line
// actually read back high.
parameter int N_SAMP = 2,
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
input logic enable, // run the clock; drop to park
// WHERE TO PARK. Dropping `enable` finishes the bit in flight and then stops -- but
// "stops" has two meanings and the master needs both.
//
// idle_low = 0 release SCL. The bus is being given up entirely.
// idle_low = 1 hold SCL LOW. The clock is being HANDED OVER, to the framer,
// which needs SCL low to build a STOP or a repeated START.
//
// This is not a convenience. If the generator released SCL while a transfer was
// open, the framer's next act -- pulling SDA low to begin a STOP sequence -- would
// happen with SCL HIGH, and SDA falling while SCL is high is a START (§3.1.1). The
// master would emit a START where it meant a STOP, and every device on the bus
// would believe a new transfer had begun.
input logic idle_low,
input logic scl_in, // THE LINE, read back. Not our own output.
output logic scl_drive_low,
// The two datapath strobes are COMBINATIONAL functions of the phase and the phase
// counter, not registered. A registered strobe is high one cycle after the
// comparison matches, and the value it causes to be placed on SDA is captured at
// the END of that cycle -- so the set-up time a registered strobe actually
// delivers is two cycles shorter than its parameter says. Deriving the strobes
// combinationally makes the parameter mean the thing it is named after, which for
// a timing parameter out of Table 10 is not optional.
output logic drive_point, // one cycle: place the next bit now
output logic sample_point, // one cycle: sample SDA now
output logic scl_rising, // one cycle: the line went high
output logic scl_falling, // one cycle: we pulled it low
output logic stretching, // released, and the line is still low
output logic [CNT_W-1:0] stretch_cycles, // total cycles spent waiting, all bits
output logic [CNT_W-1:0] bits_generated,
output logic [1:0] phase
);
localparam [1:0] PH_IDLE = 2'd0, // parked, SCL released
PH_LOW = 2'd1, // driving low, counting N_LOW
PH_WAIT = 2'd2, // released, waiting for the line to rise
PH_HIGH = 2'd3; // line is high, counting N_HIGH
logic [CNT_W-1:0] cnt;
logic scl_q; // the line, one cycle ago, for edge detection
// WHERE THE TWO STROBES SIT, derived rather than asserted.
//
// PH_LOW runs its counter over 0 .. N_LOW-1, so it lasts N_LOW cycles. A bit
// placed by the strobe is captured at the posedge that ENDS the strobe cycle, so
// SDA is valid from the following cycle onward. Putting the strobe at
// N_LOW-1-N_SU therefore leaves exactly N_SU cycles of SCL still low with the new
// bit valid on SDA -- which is Table 10's tSU;DAT, in system-clock cycles.
localparam integer DRIVE_AT = (N_LOW > N_SU) ? (N_LOW - 1 - N_SU) : 0;
// The HIGH phase is counted in LINE-high cycles, not in PH_HIGH cycles, because
// the cycle spent in PH_WAIT observing a high line is already high time. PH_HIGH
// is entered with the counter at 2 -- the WAIT cycle was line-high cycle 1 -- and
// the counter's value IS the index of the current line-high cycle. So sampling on
// the N_SAMP-th high cycle is simply cnt == N_SAMP.
//
// N_SAMP must therefore be at least 2. That is not a limitation worth removing:
// line-high cycle 1 is the cycle in which the line was first observed high, which
// on a real bus is when tr is still settling, and it is the worst possible instant
// at which to sample SDA.
localparam integer SAMPLE_AT = (N_SAMP >= 2) ? N_SAMP : 2;
assign drive_point = (phase == PH_LOW) && (cnt == DRIVE_AT[CNT_W-1:0]);
assign sample_point = (phase == PH_HIGH) && (cnt == SAMPLE_AT[CNT_W-1:0]);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase <= PH_IDLE;
scl_drive_low <= 1'b0; // released: a reset master must not hold SCL
cnt <= {CNT_W{1'b0}};
scl_rising <= 1'b0;
scl_falling <= 1'b0;
stretching <= 1'b0;
stretch_cycles <= {CNT_W{1'b0}};
bits_generated <= {CNT_W{1'b0}};
scl_q <= 1'b1;
end else begin
// The edge flags are single-cycle by construction. The two datapath strobes
// are combinational and need no default.
scl_rising <= 1'b0;
scl_falling <= 1'b0;
stretching <= 1'b0;
// Edge detection on the LINE, not on our own output. On a bus with a
// stretching target those are different signals for as long as the stretch
// lasts, and every downstream block is timed off the line.
scl_q <= scl_in;
if (scl_in && !scl_q) scl_rising <= 1'b1;
case (phase)
PH_IDLE: begin
scl_drive_low <= idle_low;
if (enable) begin
// Begin the first low phase. A master starting a transfer pulls SCL
// low itself; the framing that precedes it is Chapter 17.5's job.
scl_drive_low <= 1'b1;
scl_falling <= 1'b1;
cnt <= {CNT_W{1'b0}};
phase <= PH_LOW;
end
end
PH_LOW: begin
scl_drive_low <= 1'b1;
if (cnt + 1 >= N_LOW) begin
// Release. The line does NOT necessarily go high here -- that is
// the whole point of the next state.
scl_drive_low <= 1'b0;
cnt <= {CNT_W{1'b0}};
phase <= PH_WAIT;
end else begin
cnt <= cnt + 1'b1;
end
end
PH_WAIT: begin
// Released, and waiting. If the line is already high this costs one
// cycle; if a target is stretching it costs as long as the target
// takes, and that time is counted rather than assumed.
scl_drive_low <= 1'b0;
if (scl_in) begin
// The line is high NOW, so THIS cycle is line-high cycle 1 and the
// first cycle of PH_HIGH is line-high cycle 2. Entering with the
// counter at zero instead would make the observed high time
// N_HIGH + 1 cycles -- legal, since tHIGH has only a minimum, but
// the parameter would not mean what it says and the period would be
// a cycle longer than N_LOW + N_HIGH.
cnt <= {{(CNT_W-2){1'b0}}, 2'd2};
phase <= PH_HIGH;
end else begin
stretching <= 1'b1;
stretch_cycles <= stretch_cycles + 1'b1;
end
end
PH_HIGH: begin
scl_drive_low <= 1'b0;
// A target may pull SCL low again mid-high-phase. That is legal and it
// shortens this master's high phase -- §3.1.7's "first master to
// complete its HIGH period pulls the SCL line LOW again", seen from
// the losing side. The bit is already sampled, so the phase simply ends.
if (!scl_in) begin
bits_generated <= bits_generated + 1'b1;
cnt <= {CNT_W{1'b0}};
phase <= enable ? PH_LOW : PH_IDLE;
scl_drive_low <= enable;
end else if (cnt >= N_HIGH[CNT_W-1:0]) begin
bits_generated <= bits_generated + 1'b1;
if (enable) begin
scl_drive_low <= 1'b1;
scl_falling <= 1'b1;
cnt <= {CNT_W{1'b0}};
phase <= PH_LOW;
end else begin
// Parking, in whichever of the two senses the master asked for.
// Note that parking LOW leaves the bus held, which is legal only
// because a STOP is coming: a master that parked low and then did
// nothing would be stretching the bus indefinitely, and §3.1.16
// offers no protocol remedy for a held SCL.
scl_drive_low <= idle_low;
phase <= PH_IDLE;
end
end else begin
cnt <= cnt + 1'b1;
end
end
default: phase <= PH_IDLE;
endcase
end
end
endmodule // -----------------------------------------------------------------------------
// i2c_scl_gen.sv
// The SCL timing generator: phases, the two datapath instants, and the one thing a
// generator must never do.
//
// THE CENTRAL DECISION. This block does not advance on its own count. It counts the
// LOW phase from its own divider, then RELEASES SCL and waits for the line to READ
// BACK high before it starts counting the HIGH phase at all.
//
// UM10204 §3.1.6: "Clock stretching pauses a transaction by holding the SCL line LOW.
// The transaction cannot continue until the line is released HIGH again."
// UM10204 §3.1.7: "The SCL line is therefore held LOW by the master with the longest
// LOW period. Masters with shorter LOW periods enter a HIGH wait-state during this
// time." ... "The first master to complete its HIGH period pulls the SCL line LOW
// again."
//
// Those two sentences describe the same mechanism from two sides, and a generator
// that waits for the readback implements BOTH with no extra logic. A generator that
// advances on its own count implements NEITHER: against a stretching target every
// subsequent bit is driven and sampled at the wrong instant, and the failure looks
// like data corruption rather than like a timing bug.
//
// THE TWO INSTANTS. Table 10 locates exactly two moments the datapath cares about
// inside one bit, and this block exposes them as single-cycle strobes so that the bit
// engine downstream contains no timing of its own:
//
// drive_point -- inside the LOW phase, at least tSU;DAT before SCL is released,
// so a bit placed here is set up before the rising edge.
// sample_point -- inside the HIGH phase, after SDA has settled.
//
// Placing the strobes here rather than in the bit engine is what keeps the two blocks
// independent: change the mode and only the parameters move.
//
// THE PHASES ARE NOT SYMMETRIC. tLOW(min) > tHIGH(min) in every mode (4.7 > 4.0,
// 1.3 > 0.6, 0.5 > 0.26 us), so N_LOW and N_HIGH are separate parameters. A generator
// built from a symmetric divide-by-two is illegal at the frequency limit in every
// mode, and legal only if it is run slower than it needs to be.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_scl_gen #(
// Phase lengths in system-clock cycles. Chapter 17.13 computes these from a
// system frequency and a mode; here they are given, so this block can be tested
// at small values instead of at ten thousand cycles per bit.
parameter N_LOW = 13, // tLOW, in system-clock cycles
parameter N_HIGH = 6, // tHIGH, in system-clock cycles
// Where inside the LOW phase the new bit is placed. Counted from the END of the
// low phase, so N_SU cycles of set-up remain before SCL is released.
parameter N_SU = 3, // tSU;DAT, in system-clock cycles
// Where inside the HIGH phase SDA is sampled, counted from the moment the line
// actually read back high.
parameter N_SAMP = 2,
parameter CNT_W = 16
) (
input wire clk,
input wire rst_n,
input wire enable, // run the clock; drop to park
// WHERE TO PARK. Dropping `enable` finishes the bit in flight and then stops -- but
// "stops" has two meanings and the master needs both.
//
// idle_low = 0 release SCL. The bus is being given up entirely.
// idle_low = 1 hold SCL LOW. The clock is being HANDED OVER, to the framer,
// which needs SCL low to build a STOP or a repeated START.
//
// This is not a convenience. If the generator released SCL while a transfer was
// open, the framer's next act -- pulling SDA low to begin a STOP sequence -- would
// happen with SCL HIGH, and SDA falling while SCL is high is a START (§3.1.1). The
// master would emit a START where it meant a STOP, and every device on the bus
// would believe a new transfer had begun.
input wire idle_low,
input wire scl_in, // THE LINE, read back. Not our own output.
output reg scl_drive_low,
// The two datapath strobes are COMBINATIONAL functions of the phase and the phase
// counter, not registered. A registered strobe is high one cycle after the
// comparison matches, and the value it causes to be placed on SDA is captured at
// the END of that cycle -- so the set-up time a registered strobe actually
// delivers is two cycles shorter than its parameter says. Deriving the strobes
// combinationally makes the parameter mean the thing it is named after, which for
// a timing parameter out of Table 10 is not optional.
output wire drive_point, // one cycle: place the next bit now
output wire sample_point, // one cycle: sample SDA now
output reg scl_rising, // one cycle: the line went high
output reg scl_falling, // one cycle: we pulled it low
output reg stretching, // released, and the line is still low
output reg [CNT_W-1:0] stretch_cycles, // total cycles spent waiting, all bits
output reg [CNT_W-1:0] bits_generated,
output reg [1:0] phase
);
localparam [1:0] PH_IDLE = 2'd0, // parked, SCL released
PH_LOW = 2'd1, // driving low, counting N_LOW
PH_WAIT = 2'd2, // released, waiting for the line to rise
PH_HIGH = 2'd3; // line is high, counting N_HIGH
reg [CNT_W-1:0] cnt;
reg scl_q; // the line, one cycle ago, for edge detection
// WHERE THE TWO STROBES SIT, derived rather than asserted.
//
// PH_LOW runs its counter over 0 .. N_LOW-1, so it lasts N_LOW cycles. A bit
// placed by the strobe is captured at the posedge that ENDS the strobe cycle, so
// SDA is valid from the following cycle onward. Putting the strobe at
// N_LOW-1-N_SU therefore leaves exactly N_SU cycles of SCL still low with the new
// bit valid on SDA -- which is Table 10's tSU;DAT, in system-clock cycles.
localparam integer DRIVE_AT = (N_LOW > N_SU) ? (N_LOW - 1 - N_SU) : 0;
// The HIGH phase is counted in LINE-high cycles, not in PH_HIGH cycles, because
// the cycle spent in PH_WAIT observing a high line is already high time. PH_HIGH
// is entered with the counter at 2 -- the WAIT cycle was line-high cycle 1 -- and
// the counter's value IS the index of the current line-high cycle. So sampling on
// the N_SAMP-th high cycle is simply cnt == N_SAMP.
//
// N_SAMP must therefore be at least 2. That is not a limitation worth removing:
// line-high cycle 1 is the cycle in which the line was first observed high, which
// on a real bus is when tr is still settling, and it is the worst possible instant
// at which to sample SDA.
localparam integer SAMPLE_AT = (N_SAMP >= 2) ? N_SAMP : 2;
assign drive_point = (phase == PH_LOW) && (cnt == DRIVE_AT[CNT_W-1:0]);
assign sample_point = (phase == PH_HIGH) && (cnt == SAMPLE_AT[CNT_W-1:0]);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
phase <= PH_IDLE;
scl_drive_low <= 1'b0; // released: a reset master must not hold SCL
cnt <= {CNT_W{1'b0}};
scl_rising <= 1'b0;
scl_falling <= 1'b0;
stretching <= 1'b0;
stretch_cycles <= {CNT_W{1'b0}};
bits_generated <= {CNT_W{1'b0}};
scl_q <= 1'b1;
end else begin
// The edge flags are single-cycle by construction. The two datapath strobes
// are combinational and need no default.
scl_rising <= 1'b0;
scl_falling <= 1'b0;
stretching <= 1'b0;
// Edge detection on the LINE, not on our own output. On a bus with a
// stretching target those are different signals for as long as the stretch
// lasts, and every downstream block is timed off the line.
scl_q <= scl_in;
if (scl_in && !scl_q) scl_rising <= 1'b1;
case (phase)
PH_IDLE: begin
scl_drive_low <= idle_low;
if (enable) begin
// Begin the first low phase. A master starting a transfer pulls SCL
// low itself; the framing that precedes it is Chapter 17.5's job.
scl_drive_low <= 1'b1;
scl_falling <= 1'b1;
cnt <= {CNT_W{1'b0}};
phase <= PH_LOW;
end
end
PH_LOW: begin
scl_drive_low <= 1'b1;
if (cnt + 1 >= N_LOW) begin
// Release. The line does NOT necessarily go high here -- that is
// the whole point of the next state.
scl_drive_low <= 1'b0;
cnt <= {CNT_W{1'b0}};
phase <= PH_WAIT;
end else begin
cnt <= cnt + 1'b1;
end
end
PH_WAIT: begin
// Released, and waiting. If the line is already high this costs one
// cycle; if a target is stretching it costs as long as the target
// takes, and that time is counted rather than assumed.
scl_drive_low <= 1'b0;
if (scl_in) begin
// The line is high NOW, so THIS cycle is line-high cycle 1 and the
// first cycle of PH_HIGH is line-high cycle 2. Entering with the
// counter at zero instead would make the observed high time
// N_HIGH + 1 cycles -- legal, since tHIGH has only a minimum, but
// the parameter would not mean what it says and the period would be
// a cycle longer than N_LOW + N_HIGH.
cnt <= {{(CNT_W-2){1'b0}}, 2'd2};
phase <= PH_HIGH;
end else begin
stretching <= 1'b1;
stretch_cycles <= stretch_cycles + 1'b1;
end
end
PH_HIGH: begin
scl_drive_low <= 1'b0;
// A target may pull SCL low again mid-high-phase. That is legal and it
// shortens this master's high phase -- §3.1.7's "first master to
// complete its HIGH period pulls the SCL line LOW again", seen from
// the losing side. The bit is already sampled, so the phase simply ends.
if (!scl_in) begin
bits_generated <= bits_generated + 1'b1;
cnt <= {CNT_W{1'b0}};
phase <= enable ? PH_LOW : PH_IDLE;
scl_drive_low <= enable;
end else if (cnt >= N_HIGH[CNT_W-1:0]) begin
bits_generated <= bits_generated + 1'b1;
if (enable) begin
scl_drive_low <= 1'b1;
scl_falling <= 1'b1;
cnt <= {CNT_W{1'b0}};
phase <= PH_LOW;
end else begin
// Parking, in whichever of the two senses the master asked for.
// Note that parking LOW leaves the bus held, which is legal only
// because a STOP is coming: a master that parked low and then did
// nothing would be stretching the bus indefinitely, and §3.1.16
// offers no protocol remedy for a held SCL.
scl_drive_low <= idle_low;
phase <= PH_IDLE;
end
end else begin
cnt <= cnt + 1'b1;
end
end
default: phase <= PH_IDLE;
endcase
end
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_scl_gen.vhd
-- The SCL timing generator. Behavioural twin of i2c_scl_gen.sv / .v.
--
-- THE CENTRAL DECISION. This block does not advance on its own count. It counts the LOW
-- phase from its own divider, then RELEASES SCL and waits for the line to READ BACK high
-- before it starts counting the HIGH phase at all.
--
-- §3.1.6: "Clock stretching pauses a transaction by holding the SCL line LOW. The
-- transaction cannot continue until the line is released HIGH again."
-- §3.1.7: "The SCL line is therefore held LOW by the master with the longest LOW period.
-- Masters with shorter LOW periods enter a HIGH wait-state during this time." ... "The
-- first master to complete its HIGH period pulls the SCL line LOW again."
--
-- Those two sentences describe the same mechanism from two sides, and waiting for the
-- readback implements BOTH with no extra logic. A generator that advances on its own count
-- implements NEITHER: against a stretching target every subsequent bit is driven and
-- sampled at the wrong instant, and the failure looks like data corruption rather than
-- like a timing bug.
--
-- THE TWO INSTANTS are exposed as COMBINATIONAL strobes, not registered ones. A registered
-- strobe is high one cycle after its comparison matches, and the value it causes to be
-- placed on SDA is captured at the END of that cycle -- so the set-up time a registered
-- strobe actually delivers is two cycles shorter than its parameter says. For a number out
-- of Table 10 that is not acceptable.
--
-- AND THE PHASES ARE NOT SYMMETRIC: tLOW(min) > tHIGH(min) in every mode, so N_LOW and
-- N_HIGH are separate generics. A symmetric divide-by-two is illegal at the frequency
-- limit in all three modes.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_scl_gen is
generic (
N_LOW : integer := 13;
N_HIGH : integer := 6;
N_SU : integer := 3;
N_SAMP : integer := 2;
CNT_W : integer := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
enable : in std_logic;
-- WHERE TO PARK. Dropping `enable` finishes the bit in flight and then stops, and
-- "stops" has two meanings the master needs both of:
-- '0' release SCL. The bus is being given up entirely.
-- '1' hold SCL LOW. The clock is being HANDED OVER to the framer, which needs
-- SCL low to build a STOP or a repeated START.
-- If the generator released SCL while a transfer was open, the framer's next act --
-- pulling SDA low to begin a STOP -- would happen with SCL HIGH, and SDA falling
-- while SCL is high is a START (§3.1.1). The master would emit a START where it
-- meant a STOP, and every device on the bus would believe a new transfer had begun.
idle_low : in std_logic;
scl_in : in std_logic; -- THE LINE, read back. Not our own output.
scl_drive_low : out std_logic;
drive_point : out std_logic; -- combinational: place the next bit now
sample_point : out std_logic; -- combinational: sample SDA now
scl_rising : out std_logic;
scl_falling : out std_logic;
stretching : out std_logic;
stretch_cycles : out unsigned(CNT_W-1 downto 0);
bits_generated : out unsigned(CNT_W-1 downto 0);
phase : out unsigned(1 downto 0)
);
end entity i2c_scl_gen;
architecture rtl of i2c_scl_gen is
constant PH_IDLE : integer := 0; -- parked
constant PH_LOW : integer := 1; -- driving low, counting N_LOW
constant PH_WAIT : integer := 2; -- released, waiting for the line to rise
constant PH_HIGH : integer := 3; -- line is high, counting N_HIGH
-- WHERE THE TWO STROBES SIT, derived rather than asserted.
--
-- PH_LOW runs its counter over 0 .. N_LOW-1, so it lasts N_LOW cycles. A bit placed by
-- the strobe is captured at the clock edge that ENDS the strobe cycle, so SDA is valid
-- from the following cycle. Putting the strobe at N_LOW-1-N_SU therefore leaves exactly
-- N_SU cycles of SCL still low with the new bit valid -- Table 10's tSU;DAT, in cycles.
function calc_drive_at return integer is
begin
if N_LOW > N_SU then return N_LOW - 1 - N_SU; else return 0; end if;
end function;
-- The HIGH phase is counted in LINE-high cycles, not in PH_HIGH cycles: the cycle spent
-- in PH_WAIT observing a high line is already high time. PH_HIGH is entered with the
-- counter at 2 -- the WAIT cycle was line-high cycle 1 -- so the counter's value IS the
-- index of the current line-high cycle, and sampling on the N_SAMP-th high cycle is
-- simply cnt = N_SAMP. N_SAMP must therefore be at least 2, which is no limitation:
-- line-high cycle 1 is where tr is still settling on a real bus.
function calc_sample_at return integer is
begin
if N_SAMP >= 2 then return N_SAMP; else return 2; end if;
end function;
-- Named calc_* rather than reusing the constant's name, because VHDL identifiers are
-- CASE-INSENSITIVE: a function `drive_at` and a constant `DRIVE_AT` are the same name,
-- and the error points at the function twenty lines above the constant that caused it.
constant DRIVE_AT : integer := calc_drive_at;
constant SAMPLE_AT : integer := calc_sample_at;
signal ph : integer range 0 to 3 := PH_IDLE;
signal cnt : integer range 0 to 65535 := 0;
signal scl_q : std_logic := '1';
signal drv_low : std_logic := '0';
signal n_str : unsigned(CNT_W-1 downto 0) := (others => '0');
signal n_bits : unsigned(CNT_W-1 downto 0) := (others => '0');
begin
scl_drive_low <= drv_low;
stretch_cycles <= n_str;
bits_generated <= n_bits;
phase <= to_unsigned(ph, 2);
-- The two datapath strobes, combinational.
drive_point <= '1' when (ph = PH_LOW and cnt = DRIVE_AT) else '0';
sample_point <= '1' when (ph = PH_HIGH and cnt = SAMPLE_AT) else '0';
process (clk, rst_n)
begin
if rst_n = '0' then
ph <= PH_IDLE;
drv_low <= '0'; -- released: a reset master must not hold SCL
cnt <= 0;
scl_rising <= '0';
scl_falling <= '0';
stretching <= '0';
n_str <= (others => '0');
n_bits <= (others => '0');
scl_q <= '1';
elsif rising_edge(clk) then
scl_rising <= '0';
scl_falling <= '0';
stretching <= '0';
-- Edge detection on the LINE, not on our own output. On a bus with a stretching
-- target those are different signals for as long as the stretch lasts, and every
-- downstream block is timed off the line.
scl_q <= scl_in;
if scl_in = '1' and scl_q = '0' then scl_rising <= '1'; end if;
case ph is
when PH_IDLE =>
drv_low <= idle_low;
if enable = '1' then
drv_low <= '1';
scl_falling <= '1';
cnt <= 0;
ph <= PH_LOW;
end if;
when PH_LOW =>
drv_low <= '1';
if cnt + 1 >= N_LOW then
-- Release. The line does NOT necessarily go high here, which is the
-- whole point of the next state.
drv_low <= '0';
cnt <= 0;
ph <= PH_WAIT;
else
cnt <= cnt + 1;
end if;
when PH_WAIT =>
-- Released, and waiting. If the line is already high this costs one cycle;
-- if a target is stretching it costs as long as the target takes, and that
-- time is counted rather than assumed.
drv_low <= '0';
if scl_in = '1' then
cnt <= 2; -- THIS cycle was line-high cycle 1
ph <= PH_HIGH;
else
stretching <= '1';
n_str <= n_str + 1;
end if;
when PH_HIGH =>
drv_low <= '0';
-- A target may pull SCL low again mid-high-phase. That is legal and it
-- shortens this master's high phase -- §3.1.7's "first master to complete
-- its HIGH period pulls the SCL line LOW again", from the losing side. The
-- bit is already sampled, so the phase simply ends.
if scl_in = '0' then
n_bits <= n_bits + 1;
cnt <= 0;
if enable = '1' then
ph <= PH_LOW;
drv_low <= '1';
else
ph <= PH_IDLE;
drv_low <= idle_low;
end if;
elsif cnt >= N_HIGH then
n_bits <= n_bits + 1;
if enable = '1' then
drv_low <= '1';
scl_falling <= '1';
cnt <= 0;
ph <= PH_LOW;
else
-- Parking, in whichever of the two senses the master asked for.
-- Parking LOW leaves the bus held, which is legal only because a STOP
-- is coming: a master that parked low and then did nothing would be
-- stretching the bus indefinitely, and §3.1.16 offers no protocol
-- remedy for a held SCL.
drv_low <= idle_low;
ph <= PH_IDLE;
end if;
else
cnt <= cnt + 1;
end if;
end case;
end if;
end process;
end architecture rtl;6b. The testbenches
Twelve tests. Every wait on the bus is bounded — the benches time out rather than hang, because a generator that stops clocking is one of the failure modes under test and an unbounded wait would turn that defect into a hung regression instead of a reported failure.
| # | Test | Property |
|---|---|---|
| T1 | a reset generator releases SCL | anything else is a master holding the bus down |
| T2 | disabled means silent | cycles pass, nothing is driven |
| T3 | the phase lengths, measured from the line | not from the generator's own count |
| T4 | the drive point sits N_SU cycles before the low phase ends | tSU;DAT, in cycles |
| T5 | the sample point sits inside the high phase | never in the low phase |
| T6 | the central test — a target holds SCL low across the release | the generator waits |
| T7 | releasing the stretch resumes the clock | and the wait was counted |
| T8 | the stretch is absorbed in the wait phase | the generator's own low phase is unchanged |
| T9 | a target pulling SCL low mid-high-phase ends it early | §3.1.7 from the losing side |
| T10 | parking finishes the bit in progress | and leaves SCL released |
| T11 | every bit produces exactly one drive and one sample point | over many bits |
| T12 | edges are reported from the line | not from the output register |
`timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_scl_gen_tb.sv
// Independent oracle for i2c_scl_gen.
//
// The generator drives the bus through a wired-AND line model, and the bench is the
// SECOND device on that bus. So a stretch is injected the way a real target stretches
// -- by pulling SCL low -- rather than by poking the generator's input, and the
// generator has no way to tell the difference. That is the only way to test the
// readback behaviour honestly: a bench that drove `scl_in` directly would also be
// driving a signal the generator is supposed to be observing.
//
// The bench measures phase lengths by counting system-clock cycles between edges of
// the LINE, which is what a scope would see. It does not read the generator's counter.
// -----------------------------------------------------------------------------
module i2c_scl_gen_tb;
localparam integer NL = 13; // N_LOW
localparam integer NH = 6; // N_HIGH
localparam integer NSU = 3; // N_SU
localparam integer NSMP = 2; // N_SAMP
localparam [1:0] PH_IDLE = 2'd0, PH_LOW = 2'd1, PH_WAIT = 2'd2, PH_HIGH = 2'd3;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic enable = 1'b0;
logic idle_low = 1'b0;
// Device 0 is the generator; device 1 is the bench, acting as a target.
logic gen_scl_low;
logic tgt_scl_low = 1'b0;
logic scl, sda;
logic [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
logic [7:0] scl_holders, sda_holders;
logic drive_point, sample_point, scl_rising, scl_falling, stretching;
logic [15:0] stretch_cycles, bits_generated;
logic [1:0] phase;
i2c_line_model #(.N_DEV(2)) bus (
.scl_drive_low({tgt_scl_low, gen_scl_low}),
.sda_drive_low(2'b00),
.scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
.scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
.scl_holders(scl_holders), .sda_holders(sda_holders));
i2c_scl_gen #(.N_LOW(NL), .N_HIGH(NH), .N_SU(NSU), .N_SAMP(NSMP), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n), .enable(enable), .idle_low(idle_low),
.scl_in(scl_in[0]),
.scl_drive_low(gen_scl_low),
.drive_point(drive_point), .sample_point(sample_point),
.scl_rising(scl_rising), .scl_falling(scl_falling),
.stretching(stretching), .stretch_cycles(stretch_cycles),
.bits_generated(bits_generated), .phase(phase));
always #5 clk = ~clk;
integer errors = 0;
integer n, k;
// ---- measurement -------------------------------------------------------
// Two independent measurements, because they answer different questions.
//
// From the LINE: how long the line was low and high. This is what a scope sees,
// and on a stretched bus the line's low time is LONGER than the generator's low
// phase -- that is what stretching means.
//
// From the generator's PHASE output: how many cycles it spent in each phase. This
// is what says whether the generator's own timing was disturbed, which is the
// property a stretch must NOT change. Asserting only the line cannot separate
// "the generator counted N_LOW and then waited" from "the generator counted for
// longer", and those are a correct design and a broken one.
integer low_len, high_len;
integer meas_low, meas_high;
integer cyc_low, cyc_wait, cyc_high; // accumulating, current occurrence
integer last_low, last_wait, last_high; // the most recent complete occurrence
integer drive_at_from_end, samp_at_from_start;
integer n_drive, n_sample;
// Direct measurements, in system-clock cycles:
// meas_su -- cycles of SCL still LOW after the drive strobe (the set-up)
// meas_smp -- cycles of SCL already HIGH when the sample strobe fires
integer su_run, meas_su, arm_su;
integer smp_run, meas_smp;
logic scl_prev;
logic [1:0] ph_prev;
always @(negedge clk) begin
if (rst_n) begin
// --- line-based
if (!scl) low_len = low_len + 1; else high_len = high_len + 1;
if (drive_point) begin n_drive = n_drive + 1; drive_at_from_end = low_len; end
if (sample_point) begin n_sample = n_sample + 1; samp_at_from_start = high_len; end
// set-up: start counting at the drive strobe, stop when the line rises
if (drive_point) begin arm_su = 1; su_run = 0; end
else if (arm_su && !scl) su_run = su_run + 1;
if (arm_su && scl) begin meas_su = su_run; arm_su = 0; end
// sample: how long the line has been high when the strobe fires
if (scl) smp_run = smp_run + 1; else smp_run = 0;
if (sample_point) meas_smp = smp_run;
if (scl && !scl_prev) begin
meas_low = low_len;
drive_at_from_end = meas_low - drive_at_from_end;
low_len = 0; high_len = 1;
end
if (!scl && scl_prev) begin
meas_high = high_len;
high_len = 0; low_len = 1;
end
scl_prev = scl;
// --- phase-based
if (phase != ph_prev) begin
case (ph_prev)
PH_LOW: last_low = cyc_low;
PH_WAIT: last_wait = cyc_wait;
PH_HIGH: last_high = cyc_high;
default: ;
endcase
cyc_low = 0; cyc_wait = 0; cyc_high = 0;
end
case (phase)
PH_LOW: cyc_low = cyc_low + 1;
PH_WAIT: cyc_wait = cyc_wait + 1;
PH_HIGH: cyc_high = cyc_high + 1;
default: ;
endcase
ph_prev = phase;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; enable = 1'b0; idle_low = 1'b0; tgt_scl_low = 1'b0;
low_len = 0; high_len = 0; meas_low = 0; meas_high = 0;
n_drive = 0; n_sample = 0; scl_prev = 1'b1;
drive_at_from_end = 0; samp_at_from_start = 0;
cyc_low = 0; cyc_wait = 0; cyc_high = 0;
last_low = 0; last_wait = 0; last_high = 0; ph_prev = PH_IDLE;
su_run = 0; meas_su = 0; arm_su = 0; smp_run = 0; meas_smp = 0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
// Run until the line has produced `want` complete low->high->low cycles, bounded.
task run_bits (input integer want, input integer max_cycles);
begin
n = 0;
while (bits_generated < want && n < max_cycles) begin step; n = n + 1; end
if (n >= max_cycles) begin
$display(" FAIL run_bits: only %0d of %0d bits after %0d cycles",
bits_generated, want, max_cycles);
errors = errors + 1;
end
end
endtask
task ck_int (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input g, input e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0b expected %0b", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_scl_gen: a generator that does not trust its own clock ===");
// ----------------------------------------------------------------
// T1. A reset generator releases SCL. Anything else is a master holding the
// bus down before it has been asked to do anything, and §3.1.16 shows
// there is no protocol remedy for a held SCL.
// ----------------------------------------------------------------
do_reset;
$display("T1 a reset generator releases SCL and drives nothing");
ck_bit("T1 SCL released", gen_scl_low, 1'b0);
ck_bit("T1 the line is high", scl, 1'b1);
ck_int("T1 parked in IDLE", phase, PH_IDLE);
ck_int("T1 no bits yet", bits_generated, 0);
// ----------------------------------------------------------------
// T2. Disabled means silent. Cycles pass and nothing is driven.
// ----------------------------------------------------------------
for (k = 0; k < 40; k = k + 1) begin
step;
ck_bit("T2 nothing driven while disabled", gen_scl_low, 1'b0);
end
$display("T2 a disabled generator drives nothing at all");
ck_int("T2 still no bits", bits_generated, 0);
// ----------------------------------------------------------------
// T3. The phase lengths, measured from the LINE. This is the test that a
// symmetric divider fails: the two phases are different numbers.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
run_bits(3, 400);
$display("T3 the low and high phases are the lengths they were told to be");
ck_int("T3 low phase is N_LOW", meas_low, NL);
ck_int("T3 high phase is N_HIGH", meas_high, NH);
if (meas_low == meas_high) begin
$display(" FAIL T3 the phases are equal, which no legal mode allows");
errors = errors + 1;
end
// ----------------------------------------------------------------
// T4. The drive point sits N_SU cycles before the end of the LOW phase, so a
// bit placed there is set up before SCL rises. Table 10's tSU;DAT.
// ----------------------------------------------------------------
$display("T4 the drive point leaves exactly tSU;DAT of set-up");
$display(" [measured] meas_su=%0d meas_smp=%0d meas_low=%0d meas_high=%0d last_high=%0d",
meas_su, meas_smp, meas_low, meas_high, last_high);
ck_int("T4 set-up cycles remaining after the drive point", meas_su, NSU);
ck_int("T4 one drive point per bit", n_drive, bits_generated);
// ----------------------------------------------------------------
// T5. The sample point sits inside the HIGH phase. Sampling in the low phase
// reads a bit that is allowed to be changing -- §3.1.2's data rule applies
// to the transmitter, and the receiver's obligation is its mirror image.
// ----------------------------------------------------------------
$display("T5 the sample point sits inside the HIGH phase");
ck_int("T5 the line had been high this many cycles at the sample", meas_smp, NSMP);
ck_int("T5 one sample point per bit", n_sample, bits_generated);
if (samp_at_from_start > meas_high) begin
$display(" FAIL T5 the sample point fell outside the high phase");
errors = errors + 1;
end
// ----------------------------------------------------------------
// T6. THE CENTRAL TEST. A target holds SCL low across the generator's release,
// so the line does not rise when the generator lets go. The generator must
// WAIT -- not advance, not sample, not count a bit.
//
// Note WHERE the hold has to start. Seizing SCL during the generator's HIGH
// phase is a different event entirely -- it ends the high phase early, which
// is §3.1.7's "first master to complete its HIGH period pulls the SCL line
// LOW again" and is T9. A stretch is a hold that survives the release.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
n = 0;
while (phase != PH_LOW && n < 200) begin step; n = n + 1; end
@(negedge clk); tgt_scl_low = 1'b1; // hold it from inside the low phase
k = bits_generated;
n = 0;
while (phase != PH_WAIT && n < 200) begin step; n = n + 1; end
$display("T6 a target holding SCL across the release stops the generator");
ck_int("T6 the generator released and is waiting", phase, PH_WAIT);
ck_bit("T6 the generator has let go of SCL", gen_scl_low, 1'b0);
ck_bit("T6 the line is low because the target holds it", scl, 1'b0);
ck_int("T6 exactly one device holds SCL", scl_holders, 1);
step; // `stretching` is asserted by the first WAIT cycle that sees a low line
ck_bit("T6 and it reports that it is stretching", stretching, 1'b1);
// And it stays there, for as long as the target likes.
for (n = 0; n < 50; n = n + 1) begin
step;
ck_int("T6 still waiting", phase, PH_WAIT);
ck_bit("T6 still released", gen_scl_low, 1'b0);
ck_bit("T6 no bit completed", sample_point, 1'b0);
end
ck_int("T6 no bit was generated during the stretch", bits_generated, k);
// ----------------------------------------------------------------
// T7. Releasing the stretch resumes the clock, and the wait was counted rather
// than assumed. The count is what distinguishes a bus that occasionally
// stretches from one that stretches on every byte.
// ----------------------------------------------------------------
k = stretch_cycles;
@(negedge clk); tgt_scl_low = 1'b0;
run_bits(bits_generated + 2, 600);
$display("T7 releasing the stretch resumes the clock, and the wait was counted");
ck_bit("T7 no longer stretching", stretching, 1'b0);
if (k < 50) begin
$display(" FAIL T7 stretch_cycles was %0d after a 50-cycle hold", k);
errors = errors + 1;
end
ck_int("T7 the high time after a stretch is still N_HIGH", last_wait + last_high, NH);
// ----------------------------------------------------------------
// T8. The stretch is absorbed in the WAIT phase, so the generator's own LOW
// phase is untouched. Measured from the generator's phase rather than from
// the line, because the LINE's low time is genuinely longer -- that is what
// stretching is -- while the generator's count must not change.
//
// A generator that lengthened its low phase instead would be slowing the
// bus of its own accord rather than waiting for the target, and the two are
// indistinguishable on the line.
// ----------------------------------------------------------------
$display("T8 a stretch lengthens the WAIT, not the generator's low phase");
ck_int("T8 the generator's low phase is still N_LOW", last_low, NL);
// ----------------------------------------------------------------
// T9. A target pulling SCL low during the HIGH phase ends it early, and the
// generator must not fight that. §3.1.7 again, from the losing side: the
// bit has already been sampled, so the phase simply ends and the generator
// joins the low phase it was going to drive anyway.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
n = 0;
while (phase != PH_HIGH && n < 200) begin step; n = n + 1; end
k = bits_generated;
// We are at a negedge with the generator in its first PH_HIGH cycle, which is
// line-high cycle 2. Seizing SCL here without waiting another cycle makes the
// measurement deterministic: the generator sees the low line at the next
// posedge and ends the phase, so the high time is exactly 2 cycles -- the WAIT
// cycle and this one -- against a configured N_HIGH of 6.
tgt_scl_low = 1'b1;
n = 0;
while (phase != PH_LOW && n < 200) begin step; n = n + 1; end
$display("T9 another device ending the high phase early is not fought");
ck_int("T9 the high time was cut short to two cycles", last_wait + last_high, 2);
if (last_wait + last_high >= NH) begin
$display(" FAIL T9 the high phase was not actually shortened");
errors = errors + 1;
end
ck_int("T9 the bit still counted", bits_generated, k + 1);
@(negedge clk); tgt_scl_low = 1'b0;
run_bits(bits_generated + 2, 600);
ck_int("T9 and the next low phase is still N_LOW", last_low, NL);
// ----------------------------------------------------------------
// T10. Parking. Dropping enable finishes the bit in progress and leaves SCL
// RELEASED, not held low. A master that parks holding SCL is stretching
// the bus indefinitely.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
run_bits(2, 400);
@(negedge clk); enable = 1'b0;
n = 0;
while (phase != PH_IDLE && n < 200) begin step; n = n + 1; end
$display("T10 parking leaves SCL released, not held low");
ck_int("T10 parked", phase, PH_IDLE);
ck_bit("T10 SCL released", gen_scl_low, 1'b0);
ck_bit("T10 the line is high", scl, 1'b1);
for (k = 0; k < 30; k = k + 1) begin
step;
ck_bit("T10 and it stays released", gen_scl_low, 1'b0);
end
// The other park, which is the one a transfer in progress needs: hold SCL LOW so
// the framer can build a STOP from it. Releasing here would make the framer's
// first act -- pulling SDA low -- a START instead.
@(negedge clk); idle_low = 1'b1;
step;
ck_bit("T10 parking low holds SCL for the framer", gen_scl_low, 1'b1);
ck_bit("T10 and the line is low", scl, 1'b0);
@(negedge clk); idle_low = 1'b0;
step;
ck_bit("T10 and releasing it lets the line go", scl, 1'b1);
// ----------------------------------------------------------------
// T11. Every bit produces exactly one drive point and one sample point, over a
// long run. A generator that emitted two of either would place a bit
// twice, or sample twice, and the second would win.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
run_bits(8, 800);
$display("T11 one drive point and one sample point per bit, over eight bits");
ck_int("T11 eight bits", bits_generated, 8);
ck_int("T11 eight drive points", n_drive, 8);
ck_int("T11 eight sample points", n_sample, 8);
// ----------------------------------------------------------------
// T12. Edges are reported from the LINE. scl_rising must fire when the line
// rises, which on a stretched bus is later than the release.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
// Hold SCL low from the start, so the generator's release and the line's rise
// are separated by a known number of cycles.
@(negedge clk); tgt_scl_low = 1'b1;
n = 0;
while (phase != PH_WAIT && n < 200) begin step; n = n + 1; end
ck_int("T12 waiting, not counting a high phase", phase, PH_WAIT);
// No rising edge can have been reported yet: the line never went high.
ck_bit("T12 no rising edge while the line is held", scl_rising, 1'b0);
@(negedge clk); tgt_scl_low = 1'b0;
@(posedge clk);
$display("T12 the rising edge is reported from the line, not from the release");
@(negedge clk);
ck_bit("T12 the rising edge is reported once the line rises", scl_rising, 1'b1);
if (errors == 0)
$display("=== i2c_scl_gen: ALL CHECKS PASSED ===");
else
$display("=== i2c_scl_gen: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_scl_gen_tb.sv
// Independent oracle for i2c_scl_gen.
//
// The generator drives the bus through a wired-AND line model, and the bench is the
// SECOND device on that bus. So a stretch is injected the way a real target stretches
// -- by pulling SCL low -- rather than by poking the generator's input, and the
// generator has no way to tell the difference. That is the only way to test the
// readback behaviour honestly: a bench that drove `scl_in` directly would also be
// driving a signal the generator is supposed to be observing.
//
// The bench measures phase lengths by counting system-clock cycles between edges of
// the LINE, which is what a scope would see. It does not read the generator's counter.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_scl_gen_tb;
localparam integer NL = 13; // N_LOW
localparam integer NH = 6; // N_HIGH
localparam integer NSU = 3; // N_SU
localparam integer NSMP = 2; // N_SAMP
localparam [1:0] PH_IDLE = 2'd0, PH_LOW = 2'd1, PH_WAIT = 2'd2, PH_HIGH = 2'd3;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg enable = 1'b0;
reg idle_low = 1'b0;
// Device 0 is the generator; device 1 is the bench, acting as a target.
wire gen_scl_low;
reg tgt_scl_low = 1'b0;
wire scl, sda;
wire [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
wire [7:0] scl_holders, sda_holders;
wire drive_point, sample_point, scl_rising, scl_falling, stretching;
wire [15:0] stretch_cycles, bits_generated;
wire [1:0] phase;
i2c_line_model #(.N_DEV(2)) bus (
.scl_drive_low({tgt_scl_low, gen_scl_low}),
.sda_drive_low(2'b00),
.scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
.scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
.scl_holders(scl_holders), .sda_holders(sda_holders));
i2c_scl_gen #(.N_LOW(NL), .N_HIGH(NH), .N_SU(NSU), .N_SAMP(NSMP), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n), .enable(enable), .idle_low(idle_low),
.scl_in(scl_in[0]),
.scl_drive_low(gen_scl_low),
.drive_point(drive_point), .sample_point(sample_point),
.scl_rising(scl_rising), .scl_falling(scl_falling),
.stretching(stretching), .stretch_cycles(stretch_cycles),
.bits_generated(bits_generated), .phase(phase));
always #5 clk = ~clk;
integer errors = 0;
integer n, k;
// ---- measurement -------------------------------------------------------
// Two independent measurements, because they answer different questions.
//
// From the LINE: how long the line was low and high. This is what a scope sees,
// and on a stretched bus the line's low time is LONGER than the generator's low
// phase -- that is what stretching means.
//
// From the generator's PHASE output: how many cycles it spent in each phase. This
// is what says whether the generator's own timing was disturbed, which is the
// property a stretch must NOT change. Asserting only the line cannot separate
// "the generator counted N_LOW and then waited" from "the generator counted for
// longer", and those are a correct design and a broken one.
integer low_len, high_len;
integer meas_low, meas_high;
integer cyc_low, cyc_wait, cyc_high; // accumulating, current occurrence
integer last_low, last_wait, last_high; // the most recent complete occurrence
integer drive_at_from_end, samp_at_from_start;
integer n_drive, n_sample;
// Direct measurements, in system-clock cycles:
// meas_su -- cycles of SCL still LOW after the drive strobe (the set-up)
// meas_smp -- cycles of SCL already HIGH when the sample strobe fires
integer su_run, meas_su, arm_su;
integer smp_run, meas_smp;
reg scl_prev;
reg [1:0] ph_prev;
always @(negedge clk) begin
if (rst_n) begin
// --- line-based
if (!scl) low_len = low_len + 1; else high_len = high_len + 1;
if (drive_point) begin n_drive = n_drive + 1; drive_at_from_end = low_len; end
if (sample_point) begin n_sample = n_sample + 1; samp_at_from_start = high_len; end
// set-up: start counting at the drive strobe, stop when the line rises
if (drive_point) begin arm_su = 1; su_run = 0; end
else if (arm_su && !scl) su_run = su_run + 1;
if (arm_su && scl) begin meas_su = su_run; arm_su = 0; end
// sample: how long the line has been high when the strobe fires
if (scl) smp_run = smp_run + 1; else smp_run = 0;
if (sample_point) meas_smp = smp_run;
if (scl && !scl_prev) begin
meas_low = low_len;
drive_at_from_end = meas_low - drive_at_from_end;
low_len = 0; high_len = 1;
end
if (!scl && scl_prev) begin
meas_high = high_len;
high_len = 0; low_len = 1;
end
scl_prev = scl;
// --- phase-based
if (phase != ph_prev) begin
case (ph_prev)
PH_LOW: last_low = cyc_low;
PH_WAIT: last_wait = cyc_wait;
PH_HIGH: last_high = cyc_high;
default: ;
endcase
cyc_low = 0; cyc_wait = 0; cyc_high = 0;
end
case (phase)
PH_LOW: cyc_low = cyc_low + 1;
PH_WAIT: cyc_wait = cyc_wait + 1;
PH_HIGH: cyc_high = cyc_high + 1;
default: ;
endcase
ph_prev = phase;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; enable = 1'b0; idle_low = 1'b0; tgt_scl_low = 1'b0;
low_len = 0; high_len = 0; meas_low = 0; meas_high = 0;
n_drive = 0; n_sample = 0; scl_prev = 1'b1;
drive_at_from_end = 0; samp_at_from_start = 0;
cyc_low = 0; cyc_wait = 0; cyc_high = 0;
last_low = 0; last_wait = 0; last_high = 0; ph_prev = PH_IDLE;
su_run = 0; meas_su = 0; arm_su = 0; smp_run = 0; meas_smp = 0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
// Run until the line has produced `want` complete low->high->low cycles, bounded.
task run_bits (input integer want, input integer max_cycles);
begin
n = 0;
while (bits_generated < want && n < max_cycles) begin step; n = n + 1; end
if (n >= max_cycles) begin
$display(" FAIL run_bits: only %0d of %0d bits after %0d cycles",
bits_generated, want, max_cycles);
errors = errors + 1;
end
end
endtask
task ck_int (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input g, input e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0b expected %0b", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_scl_gen: a generator that does not trust its own clock ===");
// ----------------------------------------------------------------
// T1. A reset generator releases SCL. Anything else is a master holding the
// bus down before it has been asked to do anything, and §3.1.16 shows
// there is no protocol remedy for a held SCL.
// ----------------------------------------------------------------
do_reset;
$display("T1 a reset generator releases SCL and drives nothing");
ck_bit("T1 SCL released", gen_scl_low, 1'b0);
ck_bit("T1 the line is high", scl, 1'b1);
ck_int("T1 parked in IDLE", phase, PH_IDLE);
ck_int("T1 no bits yet", bits_generated, 0);
// ----------------------------------------------------------------
// T2. Disabled means silent. Cycles pass and nothing is driven.
// ----------------------------------------------------------------
for (k = 0; k < 40; k = k + 1) begin
step;
ck_bit("T2 nothing driven while disabled", gen_scl_low, 1'b0);
end
$display("T2 a disabled generator drives nothing at all");
ck_int("T2 still no bits", bits_generated, 0);
// ----------------------------------------------------------------
// T3. The phase lengths, measured from the LINE. This is the test that a
// symmetric divider fails: the two phases are different numbers.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
run_bits(3, 400);
$display("T3 the low and high phases are the lengths they were told to be");
ck_int("T3 low phase is N_LOW", meas_low, NL);
ck_int("T3 high phase is N_HIGH", meas_high, NH);
if (meas_low == meas_high) begin
$display(" FAIL T3 the phases are equal, which no legal mode allows");
errors = errors + 1;
end
// ----------------------------------------------------------------
// T4. The drive point sits N_SU cycles before the end of the LOW phase, so a
// bit placed there is set up before SCL rises. Table 10's tSU;DAT.
// ----------------------------------------------------------------
$display("T4 the drive point leaves exactly tSU;DAT of set-up");
$display(" [measured] meas_su=%0d meas_smp=%0d meas_low=%0d meas_high=%0d last_high=%0d",
meas_su, meas_smp, meas_low, meas_high, last_high);
ck_int("T4 set-up cycles remaining after the drive point", meas_su, NSU);
ck_int("T4 one drive point per bit", n_drive, bits_generated);
// ----------------------------------------------------------------
// T5. The sample point sits inside the HIGH phase. Sampling in the low phase
// reads a bit that is allowed to be changing -- §3.1.2's data rule applies
// to the transmitter, and the receiver's obligation is its mirror image.
// ----------------------------------------------------------------
$display("T5 the sample point sits inside the HIGH phase");
ck_int("T5 the line had been high this many cycles at the sample", meas_smp, NSMP);
ck_int("T5 one sample point per bit", n_sample, bits_generated);
if (samp_at_from_start > meas_high) begin
$display(" FAIL T5 the sample point fell outside the high phase");
errors = errors + 1;
end
// ----------------------------------------------------------------
// T6. THE CENTRAL TEST. A target holds SCL low across the generator's release,
// so the line does not rise when the generator lets go. The generator must
// WAIT -- not advance, not sample, not count a bit.
//
// Note WHERE the hold has to start. Seizing SCL during the generator's HIGH
// phase is a different event entirely -- it ends the high phase early, which
// is §3.1.7's "first master to complete its HIGH period pulls the SCL line
// LOW again" and is T9. A stretch is a hold that survives the release.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
n = 0;
while (phase != PH_LOW && n < 200) begin step; n = n + 1; end
@(negedge clk); tgt_scl_low = 1'b1; // hold it from inside the low phase
k = bits_generated;
n = 0;
while (phase != PH_WAIT && n < 200) begin step; n = n + 1; end
$display("T6 a target holding SCL across the release stops the generator");
ck_int("T6 the generator released and is waiting", phase, PH_WAIT);
ck_bit("T6 the generator has let go of SCL", gen_scl_low, 1'b0);
ck_bit("T6 the line is low because the target holds it", scl, 1'b0);
ck_int("T6 exactly one device holds SCL", scl_holders, 1);
step; // `stretching` is asserted by the first WAIT cycle that sees a low line
ck_bit("T6 and it reports that it is stretching", stretching, 1'b1);
// And it stays there, for as long as the target likes.
for (n = 0; n < 50; n = n + 1) begin
step;
ck_int("T6 still waiting", phase, PH_WAIT);
ck_bit("T6 still released", gen_scl_low, 1'b0);
ck_bit("T6 no bit completed", sample_point, 1'b0);
end
ck_int("T6 no bit was generated during the stretch", bits_generated, k);
// ----------------------------------------------------------------
// T7. Releasing the stretch resumes the clock, and the wait was counted rather
// than assumed. The count is what distinguishes a bus that occasionally
// stretches from one that stretches on every byte.
// ----------------------------------------------------------------
k = stretch_cycles;
@(negedge clk); tgt_scl_low = 1'b0;
run_bits(bits_generated + 2, 600);
$display("T7 releasing the stretch resumes the clock, and the wait was counted");
ck_bit("T7 no longer stretching", stretching, 1'b0);
if (k < 50) begin
$display(" FAIL T7 stretch_cycles was %0d after a 50-cycle hold", k);
errors = errors + 1;
end
ck_int("T7 the high time after a stretch is still N_HIGH", last_wait + last_high, NH);
// ----------------------------------------------------------------
// T8. The stretch is absorbed in the WAIT phase, so the generator's own LOW
// phase is untouched. Measured from the generator's phase rather than from
// the line, because the LINE's low time is genuinely longer -- that is what
// stretching is -- while the generator's count must not change.
//
// A generator that lengthened its low phase instead would be slowing the
// bus of its own accord rather than waiting for the target, and the two are
// indistinguishable on the line.
// ----------------------------------------------------------------
$display("T8 a stretch lengthens the WAIT, not the generator's low phase");
ck_int("T8 the generator's low phase is still N_LOW", last_low, NL);
// ----------------------------------------------------------------
// T9. A target pulling SCL low during the HIGH phase ends it early, and the
// generator must not fight that. §3.1.7 again, from the losing side: the
// bit has already been sampled, so the phase simply ends and the generator
// joins the low phase it was going to drive anyway.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
n = 0;
while (phase != PH_HIGH && n < 200) begin step; n = n + 1; end
k = bits_generated;
// We are at a negedge with the generator in its first PH_HIGH cycle, which is
// line-high cycle 2. Seizing SCL here without waiting another cycle makes the
// measurement deterministic: the generator sees the low line at the next
// posedge and ends the phase, so the high time is exactly 2 cycles -- the WAIT
// cycle and this one -- against a configured N_HIGH of 6.
tgt_scl_low = 1'b1;
n = 0;
while (phase != PH_LOW && n < 200) begin step; n = n + 1; end
$display("T9 another device ending the high phase early is not fought");
ck_int("T9 the high time was cut short to two cycles", last_wait + last_high, 2);
if (last_wait + last_high >= NH) begin
$display(" FAIL T9 the high phase was not actually shortened");
errors = errors + 1;
end
ck_int("T9 the bit still counted", bits_generated, k + 1);
@(negedge clk); tgt_scl_low = 1'b0;
run_bits(bits_generated + 2, 600);
ck_int("T9 and the next low phase is still N_LOW", last_low, NL);
// ----------------------------------------------------------------
// T10. Parking. Dropping enable finishes the bit in progress and leaves SCL
// RELEASED, not held low. A master that parks holding SCL is stretching
// the bus indefinitely.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
run_bits(2, 400);
@(negedge clk); enable = 1'b0;
n = 0;
while (phase != PH_IDLE && n < 200) begin step; n = n + 1; end
$display("T10 parking leaves SCL released, not held low");
ck_int("T10 parked", phase, PH_IDLE);
ck_bit("T10 SCL released", gen_scl_low, 1'b0);
ck_bit("T10 the line is high", scl, 1'b1);
for (k = 0; k < 30; k = k + 1) begin
step;
ck_bit("T10 and it stays released", gen_scl_low, 1'b0);
end
// The other park, which is the one a transfer in progress needs: hold SCL LOW so
// the framer can build a STOP from it. Releasing here would make the framer's
// first act -- pulling SDA low -- a START instead.
@(negedge clk); idle_low = 1'b1;
step;
ck_bit("T10 parking low holds SCL for the framer", gen_scl_low, 1'b1);
ck_bit("T10 and the line is low", scl, 1'b0);
@(negedge clk); idle_low = 1'b0;
step;
ck_bit("T10 and releasing it lets the line go", scl, 1'b1);
// ----------------------------------------------------------------
// T11. Every bit produces exactly one drive point and one sample point, over a
// long run. A generator that emitted two of either would place a bit
// twice, or sample twice, and the second would win.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
run_bits(8, 800);
$display("T11 one drive point and one sample point per bit, over eight bits");
ck_int("T11 eight bits", bits_generated, 8);
ck_int("T11 eight drive points", n_drive, 8);
ck_int("T11 eight sample points", n_sample, 8);
// ----------------------------------------------------------------
// T12. Edges are reported from the LINE. scl_rising must fire when the line
// rises, which on a stretched bus is later than the release.
// ----------------------------------------------------------------
do_reset;
@(negedge clk); enable = 1'b1;
// Hold SCL low from the start, so the generator's release and the line's rise
// are separated by a known number of cycles.
@(negedge clk); tgt_scl_low = 1'b1;
n = 0;
while (phase != PH_WAIT && n < 200) begin step; n = n + 1; end
ck_int("T12 waiting, not counting a high phase", phase, PH_WAIT);
// No rising edge can have been reported yet: the line never went high.
ck_bit("T12 no rising edge while the line is held", scl_rising, 1'b0);
@(negedge clk); tgt_scl_low = 1'b0;
@(posedge clk);
$display("T12 the rising edge is reported from the line, not from the release");
@(negedge clk);
ck_bit("T12 the rising edge is reported once the line rises", scl_rising, 1'b1);
if (errors == 0)
$display("=== i2c_scl_gen: ALL CHECKS PASSED ===");
else
$display("=== i2c_scl_gen: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_scl_gen_tb.vhd
-- Independent oracle for i2c_scl_gen. Behavioural twin of the SV and Verilog benches.
--
-- The generator drives the bus through a wired-AND line model, and the bench is the SECOND
-- device on that bus. So a stretch is injected the way a real target stretches -- by
-- pulling SCL low -- rather than by poking the generator's input, and the generator has no
-- way to tell the difference.
--
-- Two independent measurements are taken, because they answer different questions. From
-- the LINE: how long it was low and high, which is what a scope sees. From the generator's
-- PHASE output: how many cycles it spent in each phase, which is what says whether its own
-- timing was disturbed. On a stretched bus the line's low time is LONGER than the
-- generator's low phase -- that is what stretching means -- so asserting only the line
-- cannot separate a correct design from a broken one.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_scl_gen_tb is
end entity i2c_scl_gen_tb;
architecture sim of i2c_scl_gen_tb is
constant TCLK : time := 10 ns;
constant NL : integer := 13;
constant NH : integer := 6;
constant NSU : integer := 3;
constant NSMP : integer := 2;
constant PH_IDLE : integer := 0;
constant PH_LOW : integer := 1;
constant PH_WAIT : integer := 2;
constant PH_HIGH : integer := 3;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal enable : std_logic := '0';
signal idle_low : std_logic := '0';
signal gen_scl_low : std_logic;
signal tgt_scl_low : std_logic := '0';
signal drv : std_logic_vector(1 downto 0);
signal scl, sda : std_logic;
signal scl_in, sda_in, scl_rbl, sda_rbl : std_logic_vector(1 downto 0);
signal scl_h, sda_h : unsigned(7 downto 0);
signal drive_point, sample_point, scl_rising, scl_falling, stretching : std_logic;
signal stretch_cycles, bits_generated : unsigned(15 downto 0);
signal ph : unsigned(1 downto 0);
signal halt : boolean := false;
-- measurements. Only the CAPTURED values are signals, because only those are read by
-- the stimulus process. Every running counter is a VARIABLE inside the measuring
-- process, and that distinction is the whole reason this bench works.
--
-- A VHDL signal assignment does not take effect until the process suspends, so
-- `smp_run <= smp_run + 1; ... meas_smp <= smp_run;` captures the value from BEFORE the
-- increment -- one less than the SystemVerilog bench, which uses blocking assignments
-- and sees the new value immediately. Three measurements in the first version of this
-- bench were off by exactly one for that reason, and the design was not involved.
signal meas_low, meas_high : integer := 0;
signal last_low, last_wait, last_high : integer := 0;
signal n_drive, n_sample : integer := 0;
signal meas_su, meas_smp : integer := 0;
begin
drv <= tgt_scl_low & gen_scl_low;
bus_m : entity work.i2c_line_model
generic map (N_DEV => 2)
port map (scl_drive_low => drv, sda_drive_low => "00",
scl => scl, sda => sda, scl_in => scl_in, sda_in => sda_in,
scl_released_but_low => scl_rbl, sda_released_but_low => sda_rbl,
scl_holders => scl_h, sda_holders => sda_h);
dut : entity work.i2c_scl_gen
generic map (N_LOW => NL, N_HIGH => NH, N_SU => NSU, N_SAMP => NSMP, CNT_W => 16)
port map (clk => clk, rst_n => rst_n, enable => enable, idle_low => idle_low,
scl_in => scl_in(0), scl_drive_low => gen_scl_low,
drive_point => drive_point, sample_point => sample_point,
scl_rising => scl_rising, scl_falling => scl_falling,
stretching => stretching, stretch_cycles => stretch_cycles,
bits_generated => bits_generated, phase => ph);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for TCLK/2;
clk <= '1'; wait for TCLK/2;
end loop;
wait;
end process;
meas : process (clk, rst_n)
variable low_len, high_len : integer := 0;
variable cyc_low, cyc_wait, cyc_high : integer := 0;
variable su_run, smp_run : integer := 0;
variable arm_su : std_logic := '0';
variable scl_l : std_logic := '1';
variable ph_l : integer := 0;
begin
if rst_n = '0' then
-- Cleared on reset, so measurements do not accumulate across tests. The
-- SystemVerilog bench clears them in its reset task; here the measuring process
-- owns them, so it has to.
low_len := 0; high_len := 0; cyc_low := 0; cyc_wait := 0; cyc_high := 0;
su_run := 0; smp_run := 0; arm_su := '0'; scl_l := '1'; ph_l := 0;
meas_low <= 0; meas_high <= 0;
last_low <= 0; last_wait <= 0; last_high <= 0;
n_drive <= 0; n_sample <= 0; meas_su <= 0; meas_smp <= 0;
elsif falling_edge(clk) then
-- line-based
if scl = '0' then low_len := low_len + 1; else high_len := high_len + 1; end if;
if drive_point = '1' then n_drive <= n_drive + 1; end if;
if sample_point = '1' then n_sample <= n_sample + 1; end if;
if scl = '1' and scl_l = '0' then
meas_low <= low_len; low_len := 0; high_len := 1;
end if;
if scl = '0' and scl_l = '1' then
meas_high <= high_len; high_len := 0; low_len := 1;
end if;
-- set-up: from the drive strobe until the line rises
if drive_point = '1' then
arm_su := '1'; su_run := 0;
elsif arm_su = '1' and scl = '0' then
su_run := su_run + 1;
end if;
if arm_su = '1' and scl = '1' then meas_su <= su_run; arm_su := '0'; end if;
-- sample: how long the line has been high when the strobe fires
if scl = '1' then smp_run := smp_run + 1; else smp_run := 0; end if;
if sample_point = '1' then meas_smp <= smp_run; end if;
-- phase-based
if to_integer(ph) /= ph_l then
case ph_l is
when PH_LOW => last_low <= cyc_low;
when PH_WAIT => last_wait <= cyc_wait;
when PH_HIGH => last_high <= cyc_high;
when others => null;
end case;
cyc_low := 0; cyc_wait := 0; cyc_high := 0;
end if;
case to_integer(ph) is
when PH_LOW => cyc_low := cyc_low + 1;
when PH_WAIT => cyc_wait := cyc_wait + 1;
when PH_HIGH => cyc_high := cyc_high + 1;
when others => null;
end case;
ph_l := to_integer(ph);
scl_l := scl;
end if;
end process;
stim : process
variable err : integer := 0;
variable n, k : integer;
procedure ck_int (what : string; g : integer; e : integer) is
begin
if g /= e then
report " FAIL " & what & ": got " & integer'image(g)
& " expected " & integer'image(e) severity note;
err := err + 1;
end if;
end procedure;
procedure ck_bit (what : string; g : std_logic; e : std_logic) is
begin
if g /= e then
report " FAIL " & what & ": got " & std_logic'image(g)
& " expected " & std_logic'image(e) severity note;
err := err + 1;
end if;
end procedure;
procedure step is
begin
wait until rising_edge(clk); wait until falling_edge(clk);
end procedure;
procedure do_reset is
begin
wait until falling_edge(clk);
rst_n <= '0'; enable <= '0'; idle_low <= '0'; tgt_scl_low <= '0';
for i in 0 to 2 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk); rst_n <= '1';
step;
end procedure;
procedure run_bits (want : integer; max_cycles : integer) is
begin
n := 0;
while to_integer(bits_generated) < want and n < max_cycles loop
step; n := n + 1;
end loop;
if n >= max_cycles then
report " FAIL run_bits: only " & integer'image(to_integer(bits_generated))
& " of " & integer'image(want) & " bits" severity note;
err := err + 1;
end if;
end procedure;
begin
report "=== i2c_scl_gen: a generator that does not trust its own clock ==="
severity note;
-- T1. A reset generator releases SCL. Anything else is a master holding the bus
-- down before it has been asked to do anything, and §3.1.16 has no remedy.
do_reset;
report "T1 a reset generator releases SCL and drives nothing" severity note;
ck_bit("T1 SCL released", gen_scl_low, '0');
ck_bit("T1 the line is high", scl, '1');
ck_int("T1 parked in IDLE", to_integer(ph), PH_IDLE);
ck_int("T1 no bits yet", to_integer(bits_generated), 0);
-- T2. Disabled means silent.
for j in 0 to 39 loop
step;
ck_bit("T2 nothing driven while disabled", gen_scl_low, '0');
end loop;
report "T2 a disabled generator drives nothing at all" severity note;
ck_int("T2 still no bits", to_integer(bits_generated), 0);
-- T3. The phase lengths, measured from the LINE. A symmetric divider fails this.
do_reset;
wait until falling_edge(clk); enable <= '1';
run_bits(3, 400);
report "T3 the low and high phases are the lengths they were told to be"
severity note;
ck_int("T3 low phase is N_LOW", meas_low, NL);
ck_int("T3 high phase is N_HIGH", meas_high, NH);
if meas_low = meas_high then
report " FAIL T3 the phases are equal, which no legal mode allows" severity note;
err := err + 1;
end if;
-- T4. The drive point leaves exactly tSU;DAT of set-up.
report "T4 the drive point leaves exactly tSU;DAT of set-up" severity note;
report " [measured] meas_su=" & integer'image(meas_su)
& " meas_smp=" & integer'image(meas_smp)
& " meas_low=" & integer'image(meas_low)
& " meas_high=" & integer'image(meas_high)
& " last_high=" & integer'image(last_high) severity note;
ck_int("T4 set-up cycles remaining after the drive point", meas_su, NSU);
ck_int("T4 one drive point per bit", n_drive, to_integer(bits_generated));
-- T5. The sample point sits inside the HIGH phase.
report "T5 the sample point sits inside the HIGH phase" severity note;
ck_int("T5 the line had been high this many cycles at the sample", meas_smp, NSMP);
ck_int("T5 one sample point per bit", n_sample, to_integer(bits_generated));
if meas_smp > meas_high then
report " FAIL T5 the sample point fell outside the high phase" severity note;
err := err + 1;
end if;
-- T6. THE CENTRAL TEST. A target holds SCL low across the generator's release, so
-- the line does not rise when the generator lets go. Note WHERE the hold has to
-- start: seizing SCL during the HIGH phase is a different event -- it ends the
-- high phase early, which is §3.1.7 and is T9.
do_reset;
wait until falling_edge(clk); enable <= '1';
n := 0;
while to_integer(ph) /= PH_LOW and n < 200 loop step; n := n + 1; end loop;
wait until falling_edge(clk); tgt_scl_low <= '1';
k := to_integer(bits_generated);
n := 0;
while to_integer(ph) /= PH_WAIT and n < 200 loop step; n := n + 1; end loop;
report "T6 a target holding SCL across the release stops the generator"
severity note;
ck_int("T6 the generator released and is waiting", to_integer(ph), PH_WAIT);
ck_bit("T6 the generator has let go of SCL", gen_scl_low, '0');
ck_bit("T6 the line is low because the target holds it", scl, '0');
ck_int("T6 exactly one device holds SCL", to_integer(scl_h), 1);
step;
ck_bit("T6 and it reports that it is stretching", stretching, '1');
for i in 0 to 49 loop
step;
ck_int("T6 still waiting", to_integer(ph), PH_WAIT);
ck_bit("T6 still released", gen_scl_low, '0');
ck_bit("T6 no bit completed", sample_point, '0');
end loop;
ck_int("T6 no bit was generated during the stretch",
to_integer(bits_generated), k);
-- T7. Releasing the stretch resumes the clock, and the wait was counted.
k := to_integer(stretch_cycles);
wait until falling_edge(clk); tgt_scl_low <= '0';
run_bits(to_integer(bits_generated) + 2, 600);
report "T7 releasing the stretch resumes the clock, and the wait was counted"
severity note;
ck_bit("T7 no longer stretching", stretching, '0');
if k < 50 then
report " FAIL T7 stretch_cycles was " & integer'image(k)
& " after a 50-cycle hold" severity note;
err := err + 1;
end if;
ck_int("T7 the high time after a stretch is still N_HIGH",
last_wait + last_high, NH);
-- T8. The stretch is absorbed in the WAIT phase, so the generator's own LOW phase is
-- untouched. Measured from the phase rather than from the line, because the
-- LINE's low time is genuinely longer -- that is what stretching is.
report "T8 a stretch lengthens the WAIT, not the generator's low phase"
severity note;
ck_int("T8 the generator's low phase is still N_LOW", last_low, NL);
-- T9. A target pulling SCL low during the HIGH phase ends it early, and the
-- generator must not fight that. The bit is already sampled, so the phase simply
-- ends and the generator joins the low phase it was going to drive anyway.
do_reset;
wait until falling_edge(clk); enable <= '1';
n := 0;
while to_integer(ph) /= PH_HIGH and n < 200 loop step; n := n + 1; end loop;
k := to_integer(bits_generated);
-- We are at a falling edge in the first PH_HIGH cycle, which is line-high cycle 2.
-- Seizing SCL here without waiting another cycle makes the measurement
-- deterministic: the high time is exactly 2 cycles against a configured N_HIGH of 6.
tgt_scl_low <= '1';
n := 0;
while to_integer(ph) /= PH_LOW and n < 200 loop step; n := n + 1; end loop;
-- One more step before reading the captured phase lengths. `last_high` is assigned
-- by the measuring process at the very falling edge this loop exits on, and a VHDL
-- signal read in the same delta it is assigned still holds its OLD value. The
-- SystemVerilog bench does not need this because its measuring block uses blocking
-- assignments, which are visible immediately within the time step.
step;
report "T9 another device ending the high phase early is not fought" severity note;
ck_int("T9 the high time was cut short to two cycles", last_wait + last_high, 2);
if last_wait + last_high >= NH then
report " FAIL T9 the high phase was not actually shortened" severity note;
err := err + 1;
end if;
ck_int("T9 the bit still counted", to_integer(bits_generated), k + 1);
wait until falling_edge(clk); tgt_scl_low <= '0';
run_bits(to_integer(bits_generated) + 2, 600);
ck_int("T9 and the next low phase is still N_LOW", last_low, NL);
-- T10. Parking, in both senses.
do_reset;
wait until falling_edge(clk); enable <= '1';
run_bits(2, 400);
wait until falling_edge(clk); enable <= '0';
n := 0;
while to_integer(ph) /= PH_IDLE and n < 200 loop step; n := n + 1; end loop;
report "T10 parking leaves SCL released, not held low" severity note;
ck_int("T10 parked", to_integer(ph), PH_IDLE);
ck_bit("T10 SCL released", gen_scl_low, '0');
ck_bit("T10 the line is high", scl, '1');
for j in 0 to 29 loop
step;
ck_bit("T10 and it stays released", gen_scl_low, '0');
end loop;
-- The other park, which a transfer in progress needs: hold SCL LOW so the framer can
-- build a STOP from it. Releasing here would make the framer's first act -- pulling
-- SDA low -- a START instead.
wait until falling_edge(clk); idle_low <= '1';
step;
ck_bit("T10 parking low holds SCL for the framer", gen_scl_low, '1');
ck_bit("T10 and the line is low", scl, '0');
wait until falling_edge(clk); idle_low <= '0';
step;
ck_bit("T10 and releasing it lets the line go", scl, '1');
-- T11. One drive point and one sample point per bit, over a long run.
do_reset;
wait until falling_edge(clk); enable <= '1';
run_bits(8, 800);
report "T11 one drive point and one sample point per bit, over eight bits"
severity note;
ck_int("T11 eight bits", to_integer(bits_generated), 8);
ck_int("T11 eight drive points", n_drive, 8);
ck_int("T11 eight sample points", n_sample, 8);
-- T12. Edges are reported from the LINE, which on a stretched bus is later than the
-- release.
do_reset;
wait until falling_edge(clk); enable <= '1';
wait until falling_edge(clk); tgt_scl_low <= '1';
n := 0;
while to_integer(ph) /= PH_WAIT and n < 200 loop step; n := n + 1; end loop;
ck_int("T12 waiting, not counting a high phase", to_integer(ph), PH_WAIT);
ck_bit("T12 no rising edge while the line is held", scl_rising, '0');
wait until falling_edge(clk); tgt_scl_low <= '0';
wait until rising_edge(clk);
report "T12 the rising edge is reported from the line, not from the release"
severity note;
wait until falling_edge(clk);
ck_bit("T12 the rising edge is reported once the line rises", scl_rising, '1');
if err = 0 then
report "=== i2c_scl_gen: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_scl_gen: " & integer'image(err)
& " CHECK(S) FAILED ===" severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;6c. Execution
| Design | SystemVerilog | Verilog-2001 | VHDL | Finish |
|---|---|---|---|---|
i2c_scl_gen | PASS 12/12 | PASS 12/12 | PASS 12/12 | 5450 ns, all three |
7. Mutation Testing
Eight defects, chosen to attack each claim in this chapter separately.
| # | Injected defect | Expected detection | Result |
|---|---|---|---|
| M1 | advance on our own count — go straight from LOW to HIGH, never waiting for readback | T3, T6, T7, T8 | KILLED (109 failures) |
| M2 | symmetric clock — count the high phase for N_LOW | T3 | KILLED (4) |
| M3 | drive the next bit in the HIGH phase | T4, T5 | KILLED (4) |
| M4 | sample SDA in the LOW phase | T5 | KILLED (2) |
| M5 | drop the tSU;DAT margin — place the bit on the last low cycle | T4 | KILLED (2) |
| M6 | count high time from PH_HIGH only, forgetting the WAIT cycle was high | T3 | KILLED (4) |
| M7 | ignore a target pulling SCL low mid-high-phase | T9 | KILLED (3) |
| M8 | report stretching from our own output rather than from the line | T6, T12 | KILLED (98) |
baseline: PASS (verified before injecting anything)
killed: 8 survived: 0 score: 8/8
restored: PASSThe two failure counts worth reading are M1's 109 and M8's 98, against two and four for the others.
M1 and M8 are the architectural mutations, and they fail almost every check in the suite because they break the generator's relationship with the bus rather than one arithmetic detail. That is the signature of a structural defect: it is not localised, and it does not present as one wrong number. A design review looking for an off-by-one would not find either.
M5 killed with two failure lines. Only T4 stands between the design and a generator with no data set-up time at all — and the resulting hardware would work perfectly against any target whose input sampling is faster than its specification requires, which is most of them, most of the time. That is a defect that ships and then fails on one part in a new production run.
8. Verification Connection — Checking Time Without Trusting the DUT
Two assertions that encode the invariants of §1 and §2. Not synthesizable; Icarus does not accept SVA, so these are stated for use under a simulator that does.
// 1. THE READBACK RULE. The generator may never be in its HIGH phase while the
// line reads low. This is the whole of §3.1.6 and §3.1.7 as one property, and
// it is the assertion mutation M1 would trip on its first bit.
//
// Note what it does NOT say: nothing about how long the wait lasts. A stretch
// has no specified maximum, so any assertion with a cycle bound here would be
// encoding a policy rather than the protocol. The timeout belongs to 17.11,
// where it is declared as a choice.
property p_no_high_phase_while_line_low;
@(posedge clk) disable iff (!rst_n)
(phase == PH_HIGH) |-> scl_in;
endproperty
a_readback: assert property (p_no_high_phase_while_line_low);
// 2. DATA STABILITY IS THE BIT ENGINE'S OBLIGATION, BUT THE STROBE PLACEMENT IS
// OURS. A drive point must never occur while the line is high -- if it does,
// the bit engine will change SDA during the high phase and manufacture a
// framing condition out of a data bit. Mutation M3 is exactly this.
property p_drive_point_only_while_low;
@(posedge clk) disable iff (!rst_n)
drive_point |-> !scl_in;
endproperty
a_drive_low: assert property (p_drive_point_only_while_low);
// WHY A COVERAGE POINT HERE IS NOT OPTIONAL. The interesting states of this block
// are reached only when another device interferes, and a directed suite reaches
// them because it was written to. On a real regression the question is whether
// anything ever stretched at all:
//
// cover property (@(posedge clk) stretching); // did it happen
// cover property (@(posedge clk) $rose(stretching) ##[1:$] !scl_in[->1]);
//
// A regression in which the stretch cover point is never hit has tested a
// generator that was never asked to wait -- which is the environment failure of
// Chapter 17.1 §10, caught by coverage instead of by a board.9. FPGA and ASIC Implications
On an FPGA, scl_in is asynchronous to clk and must be synchronised — two flops, and Module 2 established that the bus has no clock of its own. That synchroniser has a real consequence here: it delays the readback by two cycles, so the generator sees a stretch release two cycles late and the high phase begins two cycles later than the line actually rose. Since tHIGH has only a minimum, late is safe — the observed high time is longer than N_HIGH, never shorter. This is a case where the asymmetry of §5 pays off twice: the same rounding-up logic that keeps the phases legal also makes the synchroniser's latency harmless.
The counters are the other synthesis question. At 100 MHz and Standard mode, N_LOW is 470, so CNT_W must be at least 9 bits — and a CNT_W chosen for Fast mode at a low system clock will silently truncate at Standard mode. That is a parameter interaction rather than a bug in either value, and it is why the block takes CNT_W as a parameter rather than deriving it locally.
On an ASIC, the pad's input filter — Table 10's tSP, the 50 ns spike that must be suppressed — sits in series with the readback and adds to the synchroniser's delay. Same conclusion, same direction: the high phase gets longer, which is legal. The filter also means a genuine stretch shorter than tSP is invisible, which is not a problem because a stretch that short would be shorter than the rise time.
Neither platform changes the block's logic. Both change N_*, which is the point of taking them as parameters.
10. Debugging — The Generator That Was Fast Enough to Be Illegal
A Fast-mode master is qualified at 100 MHz system clock and passes every bench and board test, including against a scope-verified 400 kHz SCL. The same RTL is reused on a 48 MHz part for a cost-reduced board. It still reports 400 kHz in its configuration register, the bus still works with the two sensors on the prototype, and a third device -- a different manufacturer's EEPROM -- returns corrupt data on roughly one read in three. Slowing the requested frequency to 100 kHz makes the corruption stop.
The period budget omitted rise and fall time, so N was computed as ceil(f_sys/f_bus) alone rather than as the max of that and N_low + N_high + N_r + N_f. At 100 MHz the two terms are equal -- 250 and 250 -- so the defect was latent and could not be observed at any frequency on that part. At 48 MHz the sum is larger, 122 against 120, and the shortfall lands in the low phase. The bus is not merely fast; it is non-conforming, with tLOW 50 ns below its minimum. Nothing was wrong with the EEPROM, the pull-ups or the new silicon: the master had been programming an illegal clock since the first board that did not divide evenly.
Compute N as the maximum of both terms, and derive the phase counts by ceiling from the mode's minimums rather than by splitting a period in a fixed ratio. That yields 122 at 48 MHz and a legal 393.4 kHz. Then make the block REPORT its achieved frequency instead of echoing the request, because 393.4 kHz is not 400 kHz and an integrator has no other way to find out. The regression that would have caught it is a parameter sweep, not a new stimulus: elaborate the generator at a list of system frequencies and assert at each one that the observed low and high phases meet the mode's minimums. That test fails on the original design at 48 MHz and passes at 100 MHz, which is precisely the discrimination that was missing.Three generalisations, and the first is the uncomfortable one.
The 100 MHz qualification was not weak testing — it was a coincidence. At 100 MHz the two terms of the max are both 250, so no amount of stimulus on that part could distinguish the correct formula from the incorrect one. The defect was unobservable at the tested configuration, which is the same class of finding as Chapter 17.2's surviving mutant M6, and it has the same remedy: sweep the parameter, because no stimulus substitutes for a configuration.
"It measures 400 kHz" is compatible with being illegal. Frequency is the one thing that was right. The violation is in the division of the period, and a frequency measurement cannot see it — which is why the scope confirmed the design for months.
The first two devices tolerating it is the normal case, not luck. Most targets sample well inside their specified window, so a master that is marginally out of specification works with most parts. The device that fails is not the broken one; it is the one holding the master to the standard.
11. Common Misconceptions
"A master generates SCL, so it knows what SCL is doing." It knows what it is driving. The line is the wired-AND of every device, and the only authority on the current phase is the readback. §1.
"Clock stretching and clock synchronization need two mechanisms." They are the same observation — released SCL reads low — and a generator that waits for the readback implements both for free. §1.
"SCL is a 50% duty-cycle clock." tLOW(min) exceeds tHIGH(min) in every mode, so a symmetric divider is illegal at the rated frequency in all three. §3.
"The rise and fall times are slack in the period." They are the period: tLOW + tHIGH + tr + tf equals 1/fSCL(max) exactly, in all three modes. Omit them and the phases are short. §4.
"N is the system clock divided by the bus frequency." It is the maximum of that and the sum of the four phase counts. At 48 MHz and 12 MHz in Fast mode, the sum wins. §4.
"If it runs at the requested frequency, it conforms." A 400 kHz clock whose low phase is 1.25 µs violates tLOW while having exactly the right frequency. §4 and §10.
"Rounding to the nearest cycle is fine." Rounding a phase down breaks a minimum; rounding a period down exceeds fSCL(max). Only ceiling is safe, and fSCL has no lower bound to violate. §5.
"The generator should report the frequency it was asked for." It must report the frequency it achieved, which is often lower — 393.4 kHz for a 400 kHz request at 48 MHz. §4.
"Registered strobes are cleaner." They deliver two cycles less set-up than the parameter claims, so N_SU = 3 yields one cycle of tSU;DAT. For a Table 10 parameter that is a correctness issue. §2.
"The synchroniser's latency is a timing risk." It delays the observed rise, which makes the high phase longer than N_HIGH. tHIGH has only a minimum, so late is safe. §9.
"Passing at one system frequency verifies the divider." At 100 MHz the two terms of the max coincide, so the formula cannot be tested there at all. §10.
12. Reason It Through
Why does a generator that advances on its own count fail in a way that looks like data corruption?
Because once the bus is stretched and the generator has moved on, every subsequent bit is driven and sampled at the wrong instant relative to the real clock. The symptom is wrong data, so the investigation starts in the datapath — while the defect is that one of the four time bases is clocked from the wrong event. §1.
A master requests 400 kHz on a 48 MHz system clock. What frequency should it report, and why is reporting 400 kHz a defect?
393.4 kHz. N is max(120, 122) = 122, so the achieved frequency is below the request. Reporting 400 kHz tells an integrator the bus is something it is not, and there is no other way for them to discover the difference — the request is not a measurement. §4.
Why is rounding up always legal and rounding down never?
Because every Table 10 phase is a minimum and fSCL is a maximum with no lower bound. Longer phases and a slower bus are always conforming; shorter phases and a faster bus are never. §5.
The generator's high-phase counter is entered at 2 rather than 0. What breaks if you change it to 0, and is the result illegal?
The observed high time becomes N_HIGH + 1 cycles, and the period runs one cycle long. It is legal — tHIGH has only a minimum — but the parameter no longer means what it is named after, so a mode table computed from Table 10 produces a bus that is slower than the table says. Mutation M6. §6.
Mutations M1 and M8 produced 109 and 98 failure lines; M4 and M5 produced two each. What does the spread tell you?
That M1 and M8 are structural and the others are local. A mutation that breaks the block's relationship with the bus fails nearly everything; one that misplaces a strobe by a cycle fails only the test written for it. The low counts are the ones to worry about in review, because a single test is the only thing protecting that property. §7.
Why can no stimulus at 100 MHz distinguish the correct period formula from the incorrect one?
Because at 100 MHz, ceil(f_sys/f_bus) is 250 and the phase sum is also 250, so both formulas return the same N. The difference is a property of the configuration, not of the stimulus, and only a parameter sweep exposes it. §10.
An FPGA synchroniser delays the SCL readback by two cycles. Which Table 10 parameter could that violate, and does it?
It could only affect tHIGH, since the high phase is what gets measured from the readback — and it makes it longer. tHIGH has only a minimum, so nothing is violated. The same latency on a parameter with a maximum would be a different matter. §9.
13. Understanding Check
14. Summary
The generator does not advance on its own count. It counts the low phase from its divider, releases SCL, and begins the high phase only when the line reads back high — because §3.1.6 says a stretched transaction cannot continue until the line is released high.
That one decision implements clock stretching and clock synchronization together. Both are the same observation, released SCL reading low, and both require the same response. A generator that advances on its own count implements neither, and fails in a way that looks like data corruption.
Two instants matter inside a bit, and the generator exposes both as strobes so that every block downstream contains no timing: the drive point inside the low phase with tSU;DAT remaining, and the sample point inside the high phase.
The strobes are combinational. Registered ones deliver two cycles less set-up than the parameter promises, which makes a Table 10 parameter mean something other than its name.
The phases are not symmetric. tLOW(min) exceeds tHIGH(min) in every mode, so a divide-by-two is illegal at the rated frequency in all three.
The period budget includes the edges. tLOW + tHIGH + tr + tf = 1/fSCL(max), exactly, in all three modes — so the rise and fall times are the budget rather than slack in it.
N is the maximum of two terms, and the phase sum is the one that decides it: at 48 MHz and 12 MHz in Fast mode the sum exceeds the frequency division, and the achievable bus is slower than requested. (17.13 §7 shows the sum always dominates, so the frequency term is defensive rather than load-bearing.)
So the achieved frequency must be reported rather than assumed. A 400 kHz request at 48 MHz yields 393.4 kHz, and the integrator has no other way to learn it.
Rounding is asymmetric: always ceiling. Every phase is a minimum and fSCL is a maximum with no lower bound, so up is always legal and down never is.
Eight mutants, eight killed — and the two structural ones failed 109 and 98 checks against two to four for the arithmetic ones, which is how a review can tell a broken relationship from a wrong number.
A qualification at one system frequency can be a coincidence. At 100 MHz both terms of the max are 250, so the period formula cannot be tested there at all, and the fix is a parameter sweep rather than more stimulus.
15. What Comes Next
SCL now has legal phases and announces the two instants the datapath needs. Nothing yet puts anything on SDA.
Chapter 17.4 builds the other line, and it is a different kind of problem. SCL has one owner — this master, always. SDA changes hands once per byte, at the acknowledge pulse, and changes back at the start of the next one. So ownership is a first-class signal in the datapath rather than something implied by the state encoding, and the chapter's job is to make "who is driving SDA right now" a question the hardware answers explicitly.
It is also where the sign error of Chapter 17.1 §4 becomes executable: the transmitted bit is the inverse of the drive enable, and a released line is how a one is sent.
Continue learning
Related tutorials
- Related topic
The Bit Engine — Driving and Sampling One Bit
One bit, and deliberately no more. Shows how making the drive point the only place SDA is written turns the data-valid rule from a property to be checked into one that cannot be violated, why transmit and receive are the same logic differing by one bit, and why an engine needs two different kinds of release because two normative rules are in tension.
- Related topic
Bus Feedback — Clock Stretching and Arbitration From One Comparison
Clock stretching and arbitration loss are not two features. They are one comparison — a line this master released that reads back low — applied to two wires, differing only in a timing qualifier and an intent gate. Builds both from a single comparator and shows why a master can only ever lose by trying to send a one.
- Related topic
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.
- Related topic
The I²C Stretching Mechanism — Holding SCL Low
Stretching needed no new mechanism: the specification already described it for multi-master synchronization. One sentence decides whether a master survives it — and getting it wrong collapses the high phase on the bit a stretch ended.
