I²C · Module 12
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.
Chapter 12.1 established what stretching is for. This chapter asks how it works, and the answer is more interesting than "the slave holds SCL low": the mechanism already existed, for something else entirely.
Clock stretching is not a feature the bus implements. It is a feature the bus cannot avoid implementing, given wired-AND.
That changes the design question. It is not "how do I add stretching support" — there is nothing to add. It is "is my clock generator built the way the specification already describes", and one sentence decides it.
1. The Mechanism Was Already There
Section 3.1.7 of UM10204 is titled Clock synchronization. It is about two masters, not about stretching at all, and it was written to explain how simultaneous masters converge on one clock during arbitration (Module 13).
Now perform one substitution. Replace "the master with the longest LOW period" with "a slave that is not ready", and read it again:
The SCL line is held LOW by the slave that is not ready. Masters with shorter LOW periods enter a HIGH wait-state during this time. When the slave is ready, the clock line is released and goes HIGH … and all the masters start counting their HIGH periods.
That is clock stretching, word for word, in a paragraph that never mentions it. §3.1.9 adds no mechanism to the bus — it adds permission for a slave to participate in one that already existed.
Which is why stretching is free to implement and expensive to get wrong. There is no stretching logic to write. There is only the question of whether your clock generator obeys the paragraph above, and a generator that does obeys it for masters and slaves alike without knowing the difference.
2. Release Is a Request; Pull-Down Is a Command
The asymmetry that makes all of this work comes from open-drain (Chapter 2.3) and the wired-AND it produces (Chapter 2.5).
| a device… | effect on the line | authority |
|---|---|---|
| pulls LOW | the line is low | absolute — no other device can override it |
| releases | the line may rise | conditional — only if every device has released |
Driving low is a command. Releasing is a request.
Every consequence in this module follows from that single sentence.
A stretch is a slave exercising the command while the master has only made a request. The master cannot lose this argument, because there is no argument: the wired-AND resolves it electrically, and a pull-down always wins.
A generator must therefore treat its own release as provisional. It knows exactly when the line went low, because it put it there. It does not know when the line will go high, because that depends on everyone else. Any logic that assumes otherwise is assuming its request was a command.
And this is why the hazard is one-sided. A generator that counts its LOW phase from its own intent is right, because intent and line agree on the way down. A generator that counts its HIGH phase from its own intent is wrong, because intent and line can disagree on the way up — for an unbounded time.
The edge from sin back to mgen is the entire subject of this chapter. Without it the master is an open-loop clock source, and an open-loop clock source cannot be stretched — it can only be fought.
3. The One Sentence That Decides It
Return to §3.1.7 and read the clause about the high phase very precisely:
"When all masters concerned have counted off their LOW period, the clock line is released and goes HIGH. There is then no difference between the master clocks and the state of the SCL line, and all the masters start counting their HIGH periods."
The high count begins when the line goes high. Not when this master released it. The specification even explains why it puts the two events in that order: "there is then no difference between the master clocks and the state of the SCL line" — the counting may only start once intent and observation have converged.
Now consider a generator that starts its high count at its own release instead. During a stretch:
| time | the line | a correct generator | a generator counting from its release |
|---|---|---|---|
| release | still LOW | waits | starts counting tHIGH |
| +100 ns | still LOW | waits | counting… |
| +600 ns | still LOW | waits | tHIGH expired — drives SCL low |
| slave releases | rises | starts counting tHIGH | line rises and is immediately pulled low again |
The high phase collapses towards zero on the very bit the stretch ended. And the bit that is lost is the one the slave just spent all that time preparing — so the failure lands precisely on the data the stretch existed to protect.
This is mutation K1 in §7, and it is the most valuable mutation in the module.
4. The Collapse, Drawn
One stretch, two generators: the high phase survives or collapses
10 cyclesRead the third row against the first. The broken generator returns to driving low at interval 6 — the exact instant the line rises — so the observed high phase would be a sliver rather than the three intervals the correct generator produces. The receiver samples on the rising edge and needs the level stable for tHIGH around it (Chapter 11.1 §1, question 3); a sliver satisfies nothing.
5. What the Generator Must Not Do Either
Two further requirements fall out of §3.1.7, and both are in §6's design.
A low line during the master's own high phase is not the master's fault. §3.1.7's last sentence — "the first master to complete its HIGH period pulls the SCL line LOW again" — means another master may legitimately end a high phase early. The generator must abandon that period rather than log it with a short high time, because logging it would report a tHIGH violation the generator did not cause. §6a's fifth point, and it is the one place this chapter touches Module 13.
Disabling the generator must release SCL, not leave it low. A generator that stops mid-low-phase holds the clock down forever, which is the stuck-SCL condition Chapter 12.4 shows has no protocol-level recovery. §6's test 10 asserts it, and it is the cheapest possible insurance against the worst possible failure.
6. The Synchronizing Generator in Three Languages
// THE STRETCHING MECHANISM, AS A MASTER'S CLOCK GENERATOR. Stretching needs no protocol support
// whatsoever -- UM10204 section 3.1.7 already describes the machinery, for a different purpose:
//
// "Clock synchronization is performed using the wired-AND connection of I2C interfaces to the
// SCL line. ... if another clock is still within its LOW period, the LOW to HIGH transition of
// this clock may not change the state of the SCL line. 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. When all masters concerned have counted off their LOW period,
// the clock line is released and goes HIGH. ... all the masters start counting their HIGH
// periods."
//
// Replace "the master with the longest LOW period" with "a slave that is not ready" and that
// paragraph IS clock stretching. Section 3.1.9 adds no mechanism; it adds permission for a slave
// to participate in one that already existed for multi-master arbitration.
//
// So the design question is not "how do I implement stretching" -- it is "is my clock generator
// built the way section 3.1.7 describes". There is exactly one sentence that decides it:
//
// "all the masters start counting their HIGH periods" -- WHEN? When "the clock line is released
// and goes HIGH". Not when this master released it. When the LINE went high.
//
// A generator that counts its HIGH phase from its own release instead of from the observed rise
// is broken in a way that only appears when something stretches, and then it violates tHIGH on
// the very bit the stretch ended: the slave holds the line down for the whole high count, the
// line finally rises, and the master immediately pulls it low again because its counter has
// already expired. The high phase collapses towards zero and the receiver never samples the bit.
// That is mutation B1, and it is the most valuable mutation in this module.
//
// The same asymmetry holds for the LOW phase, and there it is benign: counting from the observed
// fall is correct, and counting from the intent is very nearly the same thing because nothing can
// stop a line going low -- the wired-AND makes a pull-down authoritative. Stretching is an
// asymmetric hazard because RELEASING is a request and DRIVING LOW is a command.
module i2c_scl_sync_generator #(
parameter int TICK_W = 24,
parameter int T_LOW = 130, // the master's intended LOW count, Fast-mode 1.3 us
parameter int T_HIGH = 60 // the master's intended HIGH count, Fast-mode 0.6 us
)(
input logic clk,
input logic rst_n,
input logic enable, // run the clock
// The observed line. Everything this block decides is a function of THIS, not of its own
// output -- which is the whole point.
input logic scl_in,
// ---- the master's intent ----
output logic scl_drive_low, // 1 = pull low, 0 = release
// ---- what the generator knows about being stretched ----
output logic stalled, // released, waiting for the line to rise
output logic [TICK_W-1:0] stall_ticks,
// ---- the phases as they ACTUALLY occurred on the wire ----
output logic phase_valid, // pulse at the end of each period
output logic [TICK_W-1:0] t_low_actual,
output logic [TICK_W-1:0] t_high_actual,
// ---- and the evidence that stretching did not damage them. A MINIMUM tracker, so it
// starts at all-ones (Chapter 11.2 section 6a).
output logic [TICK_W-1:0] n_periods,
output logic [TICK_W-1:0] min_high_seen,
output logic [TICK_W-1:0] min_low_seen,
output logic [TICK_W-1:0] n_stalls
);
typedef enum logic [1:0] { S_IDLE, S_LOW, S_WAIT_HIGH, S_HIGH } state_t;
state_t state;
logic scl_q;
wire scl_rise = scl_in && !scl_q;
wire scl_fall = !scl_in && scl_q;
// Sized ONCE rather than part-selected at each use: a part-select of a parameter is not
// portable across the three toolchains this module is built for.
localparam logic [TICK_W-1:0] T_LOW_W = T_LOW;
localparam logic [TICK_W-1:0] T_HIGH_W = T_HIGH;
logic [TICK_W-1:0] cnt;
logic [TICK_W-1:0] low_meas, high_meas;
wire [TICK_W-1:0] cnt_now = cnt + 1'b1; // "including this cycle"
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= S_IDLE;
scl_q <= 1'b1;
scl_drive_low <= 1'b0;
stalled <= 1'b0;
stall_ticks <= '0;
cnt <= '0;
low_meas <= '0;
high_meas <= '0;
phase_valid <= 1'b0;
t_low_actual <= '0;
t_high_actual <= '0;
n_periods <= '0;
n_stalls <= '0;
min_high_seen <= {TICK_W{1'b1}};
min_low_seen <= {TICK_W{1'b1}};
end else begin
scl_q <= scl_in;
phase_valid <= 1'b0;
case (state)
S_IDLE: begin
scl_drive_low <= 1'b0;
stalled <= 1'b0;
if (enable) begin
scl_drive_low <= 1'b1;
cnt <= '0;
state <= S_LOW;
end
end
// Counting off the LOW period. Section 3.1.7 starts this count at the HIGH-to-LOW
// transition of the LINE; since this master is the one pulling it down, the two
// coincide. A pull-down is authoritative, so there is no hazard on this side.
S_LOW: begin
if (cnt_now >= T_LOW_W) begin
low_meas <= cnt_now;
scl_drive_low <= 1'b0; // release -- a REQUEST, not a command
cnt <= '0;
// Clear the stall count on ENTRY to the wait state. Without this the
// previous stretch's total is still standing, and the exit path adds it to
// a low phase that was never stretched -- so every period after a stretch
// reports the stretched length. Invisible to a suite that only checks the
// stretched periods, which is exactly how it survived the first two
// language ports. Test 4b is the regression.
stall_ticks <= '0;
state <= S_WAIT_HIGH;
end else begin
cnt <= cnt_now;
end
end
// The wait-state section 3.1.7 names. The master has let go and the line has not
// risen, so somebody else is still counting off THEIR low period. Nothing here is
// stretch-specific logic: it is the generator refusing to start its high count
// until the line is high, which is what the specification says to do.
S_WAIT_HIGH: begin
if (scl_in) begin
stalled <= 1'b0;
cnt <= '0;
// The low phase as it ACTUALLY occurred includes whatever the stretch
// added, so the measurement is taken here rather than at the release.
low_meas <= low_meas + stall_ticks;
state <= S_HIGH;
end else begin
if (!stalled) begin
stalled <= 1'b1;
// "Including this cycle": the first stalled sample IS one tick of
// stretch, so the count starts at 1. Starting at 0 makes every
// reported stretch one sample period short -- invisible except when
// the number is compared against the length actually driven, which
// is exactly what this module's testbenches do.
stall_ticks <= {{(TICK_W-1){1'b0}}, 1'b1};
n_stalls <= n_stalls + 1'b1;
end else begin
stall_ticks <= stall_ticks + 1'b1;
end
end
end
// Counting off the HIGH period, started by the observed RISE and not by the
// release. This single choice is what makes stretching harmless.
S_HIGH: begin
if (cnt_now >= T_HIGH_W) begin
high_meas <= cnt_now;
t_low_actual <= low_meas;
t_high_actual <= cnt_now;
phase_valid <= 1'b1;
n_periods <= n_periods + 1'b1;
if (cnt_now < min_high_seen) min_high_seen <= cnt_now;
if (low_meas < min_low_seen) min_low_seen <= low_meas;
if (enable) begin
scl_drive_low <= 1'b1;
cnt <= '0;
state <= S_LOW;
end else begin
state <= S_IDLE;
end
end else if (!scl_in) begin
// Somebody pulled the line low during our HIGH phase. On a compliant bus
// that is another master's clock (Module 13); either way this period is
// not ours to report, so it is abandoned rather than logged with a short
// high time that would look like a tHIGH violation we caused.
cnt <= '0;
state <= S_LOW;
end else begin
cnt <= cnt_now;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule `timescale 1ns/1ps
// 100 MHz sample clock. T_LOW = 130 and T_HIGH = 60 are Fast-mode's 1.3 us and 0.6 us.
//
// The testbench owns the WIRED-AND: the observed line is the generator's release ANDed with a
// slave's hold. That is the mechanism of section 3.1.7 used as a stimulus, and it is the only
// honest way to test a generator whose correctness is defined against the observed line.
module i2c_scl_sync_generator_tb;
localparam int TICK_W = 24;
localparam int T_LOW = 130;
localparam int T_HIGH = 60;
logic clk = 1'b0, rst_n = 1'b0, enable = 1'b0;
always #5 clk = ~clk;
logic s_hold = 1'b0; // a slave pulling SCL low
logic scl_drive_low;
wire scl_line = !scl_drive_low && !s_hold; // the wired-AND
logic stalled, phase_valid;
logic [TICK_W-1:0] stall_ticks, t_low_actual, t_high_actual;
logic [TICK_W-1:0] n_periods, min_high_seen, min_low_seen, n_stalls;
i2c_scl_sync_generator #(.TICK_W(TICK_W), .T_LOW(T_LOW), .T_HIGH(T_HIGH)) dut (
.clk(clk), .rst_n(rst_n), .enable(enable), .scl_in(scl_line),
.scl_drive_low(scl_drive_low),
.stalled(stalled), .stall_ticks(stall_ticks),
.phase_valid(phase_valid), .t_low_actual(t_low_actual), .t_high_actual(t_high_actual),
.n_periods(n_periods), .min_high_seen(min_high_seen), .min_low_seen(min_low_seen),
.n_stalls(n_stalls)
);
int errors = 0;
int n_log, lowlog[0:63], highlog[0:63];
always @(posedge clk) if (rst_n && phase_valid && n_log < 64) begin
lowlog[n_log] = t_low_actual; highlog[n_log] = t_high_actual; n_log++;
end
task automatic tick(input int n); begin repeat (n) @(negedge clk); end endtask
// Wait for the generator to reach its release point, then have a slave hold the line for
// `len` ticks. Driving on the negedge throughout.
task automatic stretch_next_low(input int len);
begin
@(negedge clk);
while (scl_drive_low !== 1'b0) @(negedge clk); // wait for the release
s_hold = 1'b1;
tick(len);
s_hold = 1'b0;
end
endtask
// Wait for `n` reported periods AND let the logger's write land. The logging process and this
// one both trigger on the same posedge, so returning the instant phase_valid is seen leaves
// `n_log` possibly not yet incremented -- and a read of lowlog[n_log-1] then silently picks up
// the PREVIOUS period. One negedge of settling removes the ordering dependency entirely.
task automatic wait_periods(input int n);
int seen;
begin
seen = 0;
while (seen < n) begin @(posedge clk); if (phase_valid) seen++; end
@(negedge clk);
end
endtask
initial begin
tick(4); rst_n = 1'b1; tick(4);
// ---- 1. reset: MINIMUM trackers start at their maximum ------------------------------
if (min_high_seen !== {TICK_W{1'b1}} || min_low_seen !== {TICK_W{1'b1}}) begin
$display("FAIL: the minimum trackers did not start at their maximum"); errors++; end
if (n_periods !== '0 || n_stalls !== '0) begin
$display("FAIL: counters nonzero out of reset"); errors++; end
// ---- 2. a free-running clock hits its intended phases exactly ------------------------
enable = 1'b1;
wait_periods(4);
if (lowlog[0] != T_LOW || highlog[0] != T_HIGH) begin
$display("FAIL: unstretched period measured low=%0d high=%0d, expected %0d/%0d",
lowlog[0], highlog[0], T_LOW, T_HIGH); errors++; end
if (n_stalls !== '0) begin
$display("FAIL: an unstretched clock reported %0d stalls", n_stalls); errors++; end
if (min_high_seen != T_HIGH) begin
$display("FAIL: min_high_seen = %0d on a clean clock, expected %0d",
min_high_seen, T_HIGH); errors++; end
// ---- 3. THE test of this chapter: a stretch must not erode the HIGH phase ------------
// A generator that started its high count at its own release would report a high phase
// near zero here, because the 300-tick hold would consume the whole count before the line
// ever rose. The high phase must still be exactly T_HIGH.
begin
int before_p; before_p = n_periods;
stretch_next_low(300);
wait_periods(1);
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: after a 300-tick stretch the HIGH phase measured %0d, expected %0d -- the generator counted its high phase from its own RELEASE, not from the observed rise",
highlog[n_log-1], T_HIGH); errors++; end
if (lowlog[n_log-1] != T_LOW + 300) begin
$display("FAIL: the stretched LOW phase measured %0d, expected %0d",
lowlog[n_log-1], T_LOW + 300); errors++; end
if (n_periods == before_p) begin
$display("FAIL: the stretched period was never reported"); errors++; end
end
// ---- 4. the stall is reported, with its duration -------------------------------------
begin
int before_s; before_s = n_stalls;
stretch_next_low(150);
wait_periods(1);
if (n_stalls != before_s + 1) begin
$display("FAIL: a 150-tick stretch produced %0d stalls", n_stalls - before_s);
errors++; end
if (lowlog[n_log-1] != T_LOW + 150) begin
$display("FAIL: low phase %0d after a 150-tick stretch, expected %0d",
lowlog[n_log-1], T_LOW + 150); errors++; end
end
// ---- 4b. the period AFTER a stretch is back to nominal -------------------------------
// The regression for a stale stall count. Every earlier check looks at the STRETCHED
// period, so a design that carried the stretch total forward reported the stretched length
// on every following period and no test noticed. This one runs a clean period immediately
// after a stretched one and requires the nominal low phase back.
begin
wait_periods(1);
if (lowlog[n_log-1] != T_LOW) begin
$display("FAIL: the period after a stretch measured low=%0d, expected the nominal %0d -- a stale stall count was carried forward",
lowlog[n_log-1], T_LOW); errors++; end
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: the period after a stretch measured high=%0d, expected %0d",
highlog[n_log-1], T_HIGH); errors++; end
end
// ---- 5. the HIGH minimum survives every stretch so far ------------------------------
// The whole compliance claim of the chapter in one assertion: tHIGH is untouched.
if (min_high_seen != T_HIGH) begin
$display("FAIL: min_high_seen = %0d after stretching, expected %0d -- stretching eroded the high phase",
min_high_seen, T_HIGH); errors++; end
// ---- 6. a very long stretch -- section 4.2.2 puts no limit on it ---------------------
begin
stretch_next_low(4000);
wait_periods(1);
if (lowlog[n_log-1] != T_LOW + 4000) begin
$display("FAIL: a 4000-tick stretch gave a low phase of %0d, expected %0d",
lowlog[n_log-1], T_LOW + 4000); errors++; end
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: the high phase after a 4000-tick stretch measured %0d",
highlog[n_log-1]); errors++; end
end
// ---- 7. back-to-back stretches on consecutive periods -------------------------------
// Section 3.1.9's bit-level case: "extending each clock LOW period". Every period is
// stretched, and every high phase must still be exact.
begin
int k;
for (k = 0; k < 3; k++) begin
stretch_next_low(80);
wait_periods(1);
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: bit-level stretching eroded the high phase to %0d on period %0d",
highlog[n_log-1], k); errors++; end
if (lowlog[n_log-1] != T_LOW + 80) begin
$display("FAIL: bit-level stretch %0d gave low=%0d, expected %0d",
k, lowlog[n_log-1], T_LOW + 80); errors++; end
end
end
// ---- 8. min_low_seen tracks the SHORTEST low phase, which is the unstretched one -----
// A stretch only ever makes the low phase longer, so it can never reduce min_low_seen.
// That is the formal reason stretching cannot violate tLOW: it moves the measurement in
// the compliant direction (Chapter 11.2 section 1 -- tLOW has no maximum).
if (min_low_seen != T_LOW) begin
$display("FAIL: min_low_seen = %0d, expected the unstretched %0d -- a stretch must only LENGTHEN a low phase",
min_low_seen, T_LOW); errors++; end
// ---- 9. the stall ends on the rise, not on the release -------------------------------
begin
stretch_next_low(60);
// during the hold the generator must be released AND stalled
if (scl_drive_low !== 1'b0) begin
$display("FAIL: the generator was still driving low during a stretch"); errors++; end
wait_periods(1);
if (stalled !== 1'b0) begin
$display("FAIL: stalled still set after the line rose"); errors++; end
end
// ---- 10. disabling stops the clock cleanly ------------------------------------------
enable = 1'b0;
tick(400);
if (scl_drive_low !== 1'b0) begin
$display("FAIL: SCL left driven low after disable -- that is a stuck bus"); errors++; end
if (errors == 0)
$display("PASS: the high phase is counted from the observed rise so stretching cannot erode tHIGH, a stretch only lengthens the low phase, bit-level and byte-level stretching both work with no stretch-specific logic");
else
$display("FAIL: %0d error(s)", errors);
$finish;
end
initial begin
#8000000;
$display("FAIL: watchdog expired");
$finish;
end
endmodule // THE STRETCHING MECHANISM, AS A MASTER'S CLOCK GENERATOR. Stretching needs no protocol support
// whatsoever -- UM10204 section 3.1.7 already describes the machinery, for a different purpose:
//
// "Clock synchronization is performed using the wired-AND connection of I2C interfaces to the
// SCL line. ... if another clock is still within its LOW period, the LOW to HIGH transition of
// this clock may not change the state of the SCL line. 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. When all masters concerned have counted off their LOW period,
// the clock line is released and goes HIGH. ... all the masters start counting their HIGH
// periods."
//
// Replace "the master with the longest LOW period" with "a slave that is not ready" and that
// paragraph IS clock stretching. Section 3.1.9 adds no mechanism; it adds permission for a slave
// to participate in one that already existed for multi-master arbitration.
//
// So the design question is not "how do I implement stretching" -- it is "is my clock generator
// built the way section 3.1.7 describes". There is exactly one sentence that decides it:
//
// "all the masters start counting their HIGH periods" -- WHEN? When "the clock line is released
// and goes HIGH". Not when this master released it. When the LINE went high.
//
// A generator that counts its HIGH phase from its own release instead of from the observed rise
// is broken in a way that only appears when something stretches, and then it violates tHIGH on
// the very bit the stretch ended: the slave holds the line down for the whole high count, the
// line finally rises, and the master immediately pulls it low again because its counter has
// already expired. The high phase collapses towards zero and the receiver never samples the bit.
// That is mutation B1, and it is the most valuable mutation in this module.
//
// The same asymmetry holds for the LOW phase, and there it is benign: counting from the observed
// fall is correct, and counting from the intent is very nearly the same thing because nothing can
// stop a line going low -- the wired-AND makes a pull-down authoritative. Stretching is an
// asymmetric hazard because RELEASING is a request and DRIVING LOW is a command.
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_scl_sync_generator #(
parameter TICK_W = 24,
parameter T_LOW = 130, // the master's intended LOW count, Fast-mode 1.3 us
parameter T_HIGH = 60 // the master's intended HIGH count, Fast-mode 0.6 us
)(
input wire clk,
input wire rst_n,
input wire enable, // run the clock
// The observed line. Everything this block decides is a function of THIS, not of its own
// output -- which is the whole point.
input wire scl_in,
// ---- the master's intent ----
output reg scl_drive_low, // 1 = pull low, 0 = release
// ---- what the generator knows about being stretched ----
output reg stalled, // released, waiting for the line to rise
output reg [TICK_W-1:0] stall_ticks,
// ---- the phases as they ACTUALLY occurred on the wire ----
output reg phase_valid, // pulse at the end of each period
output reg [TICK_W-1:0] t_low_actual,
output reg [TICK_W-1:0] t_high_actual,
// ---- and the evidence that stretching did not damage them. A MINIMUM tracker, so it
// starts at all-ones (Chapter 11.2 section 6a).
output reg [TICK_W-1:0] n_periods,
output reg [TICK_W-1:0] min_high_seen,
output reg [TICK_W-1:0] min_low_seen,
output reg [TICK_W-1:0] n_stalls
);
// Verilog-2001 has no enum: the four states become localparams and an explicit reg.
localparam [1:0] S_IDLE = 2'd0, S_LOW = 2'd1, S_WAIT_HIGH = 2'd2, S_HIGH = 2'd3;
reg [1:0] state;
reg scl_q;
wire scl_rise = scl_in && !scl_q;
wire scl_fall = !scl_in && scl_q;
// Sized ONCE rather than part-selected at each use: a part-select of a parameter is not
// portable across the three toolchains this module is built for.
localparam [TICK_W-1:0] T_LOW_W = T_LOW;
localparam [TICK_W-1:0] T_HIGH_W = T_HIGH;
reg [TICK_W-1:0] cnt;
reg [TICK_W-1:0] low_meas, high_meas;
wire [TICK_W-1:0] cnt_now = cnt + 1'b1; // "including this cycle"
always @(posedge clk) begin
if (!rst_n) begin
state <= S_IDLE;
scl_q <= 1'b1;
scl_drive_low <= 1'b0;
stalled <= 1'b0;
stall_ticks <= {TICK_W{1'b0}};
cnt <= {TICK_W{1'b0}};
low_meas <= {TICK_W{1'b0}};
high_meas <= {TICK_W{1'b0}};
phase_valid <= 1'b0;
t_low_actual <= {TICK_W{1'b0}};
t_high_actual <= {TICK_W{1'b0}};
n_periods <= {TICK_W{1'b0}};
n_stalls <= {TICK_W{1'b0}};
min_high_seen <= {TICK_W{1'b1}};
min_low_seen <= {TICK_W{1'b1}};
end else begin
scl_q <= scl_in;
phase_valid <= 1'b0;
case (state)
S_IDLE: begin
scl_drive_low <= 1'b0;
stalled <= 1'b0;
if (enable) begin
scl_drive_low <= 1'b1;
cnt <= {TICK_W{1'b0}};
state <= S_LOW;
end
end
// Counting off the LOW period. Section 3.1.7 starts this count at the HIGH-to-LOW
// transition of the LINE; since this master is the one pulling it down, the two
// coincide. A pull-down is authoritative, so there is no hazard on this side.
S_LOW: begin
if (cnt_now >= T_LOW_W) begin
low_meas <= cnt_now;
scl_drive_low <= 1'b0; // release -- a REQUEST, not a command
cnt <= {TICK_W{1'b0}};
// Clear the stall count on ENTRY to the wait state. Without this the
// previous stretch's total is still standing, and the exit path adds it to
// a low phase that was never stretched -- so every period after a stretch
// reports the stretched length. Invisible to a suite that only checks the
// stretched periods, which is exactly how it survived the first two
// language ports. Test 4b is the regression.
stall_ticks <= {TICK_W{1'b0}};
state <= S_WAIT_HIGH;
end else begin
cnt <= cnt_now;
end
end
// The wait-state section 3.1.7 names. The master has let go and the line has not
// risen, so somebody else is still counting off THEIR low period. Nothing here is
// stretch-specific logic: it is the generator refusing to start its high count
// until the line is high, which is what the specification says to do.
S_WAIT_HIGH: begin
if (scl_in) begin
stalled <= 1'b0;
cnt <= {TICK_W{1'b0}};
// The low phase as it ACTUALLY occurred includes whatever the stretch
// added, so the measurement is taken here rather than at the release.
low_meas <= low_meas + stall_ticks;
state <= S_HIGH;
end else begin
if (!stalled) begin
stalled <= 1'b1;
// "Including this cycle": the first stalled sample IS one tick of
// stretch, so the count starts at 1. Starting at 0 makes every
// reported stretch one sample period short -- invisible except when
// the number is compared against the length actually driven, which
// is exactly what this module's testbenches do.
stall_ticks <= {{(TICK_W-1){1'b0}}, 1'b1};
n_stalls <= n_stalls + 1'b1;
end else begin
stall_ticks <= stall_ticks + 1'b1;
end
end
end
// Counting off the HIGH period, started by the observed RISE and not by the
// release. This single choice is what makes stretching harmless.
S_HIGH: begin
if (cnt_now >= T_HIGH_W) begin
high_meas <= cnt_now;
t_low_actual <= low_meas;
t_high_actual <= cnt_now;
phase_valid <= 1'b1;
n_periods <= n_periods + 1'b1;
if (cnt_now < min_high_seen) min_high_seen <= cnt_now;
if (low_meas < min_low_seen) min_low_seen <= low_meas;
if (enable) begin
scl_drive_low <= 1'b1;
cnt <= {TICK_W{1'b0}};
state <= S_LOW;
end else begin
state <= S_IDLE;
end
end else if (!scl_in) begin
// Somebody pulled the line low during our HIGH phase. On a compliant bus
// that is another master's clock (Module 13); either way this period is
// not ours to report, so it is abandoned rather than logged with a short
// high time that would look like a tHIGH violation we caused.
cnt <= {TICK_W{1'b0}};
state <= S_LOW;
end else begin
cnt <= cnt_now;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule `timescale 1ns/1ps
// 100 MHz sample clock. T_LOW = 130 and T_HIGH = 60 are Fast-mode's 1.3 us and 0.6 us.
//
// The testbench owns the WIRED-AND: the observed line is the generator's release ANDed with a
// slave's hold. That is the mechanism of section 3.1.7 used as a stimulus, and it is the only
// honest way to test a generator whose correctness is defined against the observed line.
// (Verilog-2001 testbench -- same stimulus, same checks.)
module i2c_scl_sync_generator_tb;
localparam TICK_W = 24;
localparam T_LOW = 130;
localparam T_HIGH = 60;
reg clk = 1'b0, rst_n = 1'b0, enable = 1'b0;
always #5 clk = ~clk;
reg s_hold = 1'b0; // a slave pulling SCL low
wire scl_drive_low;
wire scl_line = !scl_drive_low && !s_hold; // the wired-AND
wire stalled, phase_valid;
wire [TICK_W-1:0] stall_ticks, t_low_actual, t_high_actual;
wire [TICK_W-1:0] min_high_seen;
wire [TICK_W-1:0] min_low_seen;
wire [TICK_W-1:0] n_periods, n_stalls;
i2c_scl_sync_generator #(.TICK_W(TICK_W), .T_LOW(T_LOW), .T_HIGH(T_HIGH)) dut (
.clk(clk), .rst_n(rst_n), .enable(enable), .scl_in(scl_line),
.scl_drive_low(scl_drive_low),
.stalled(stalled), .stall_ticks(stall_ticks),
.phase_valid(phase_valid), .t_low_actual(t_low_actual), .t_high_actual(t_high_actual),
.n_periods(n_periods), .min_high_seen(min_high_seen), .min_low_seen(min_low_seen),
.n_stalls(n_stalls)
);
integer errors = 0;
// Hoisted to module scope: Verilog-2001 permits a variable declaration only at
// module level or in a NAMED block, and every call site below is sequential.
integer before_p = 0;
integer before_s = 0;
integer seen = 0;
integer k = 0;
integer n_log = 0;
integer lowlog[0:63];
integer highlog[0:63];
always @(posedge clk) if (rst_n && phase_valid && n_log < 64) begin
lowlog[n_log] = t_low_actual; highlog[n_log] = t_high_actual; n_log = n_log + 1;
end
task tick(input integer n); begin repeat (n) @(negedge clk); end endtask
// Wait for the generator to reach its release point, then have a slave hold the line for
// `len` ticks. Driving on the negedge throughout.
task stretch_next_low(input integer len);
begin
@(negedge clk);
while (scl_drive_low !== 1'b0) @(negedge clk); // wait for the release
s_hold = 1'b1;
tick(len);
s_hold = 1'b0;
end
endtask
// Wait for `n` reported periods AND let the logger's write land. The logging process and this
// one both trigger on the same posedge, so returning the instant phase_valid is seen leaves
// `n_log` possibly not yet incremented -- and a read of lowlog[n_log-1] then silently picks up
// the PREVIOUS period. One negedge of settling removes the ordering dependency entirely.
task wait_periods(input integer n);
begin
seen = 0;
while (seen < n) begin @(posedge clk); if (phase_valid) seen = seen + 1; end
@(negedge clk);
end
endtask
initial begin
tick(4); rst_n = 1'b1; tick(4);
// ---- 1. reset: MINIMUM trackers start at their maximum ------------------------------
if (min_high_seen !== {TICK_W{1'b1}} || min_low_seen !== {TICK_W{1'b1}}) begin
$display("FAIL: the minimum trackers did not start at their maximum"); errors = errors + 1; end
if (n_periods !== {TICK_W{1'b0}} || n_stalls !== {TICK_W{1'b0}}) begin
$display("FAIL: counters nonzero out of reset"); errors = errors + 1; end
// ---- 2. a free-running clock hits its intended phases exactly ------------------------
enable = 1'b1;
wait_periods(4);
if (lowlog[0] != T_LOW || highlog[0] != T_HIGH) begin
$display("FAIL: unstretched period measured low=%0d high=%0d, expected %0d/%0d",
lowlog[0], highlog[0], T_LOW, T_HIGH); errors = errors + 1; end
if (n_stalls !== {TICK_W{1'b0}}) begin
$display("FAIL: an unstretched clock reported %0d stalls", n_stalls); errors = errors + 1; end
if (min_high_seen != T_HIGH) begin
$display("FAIL: min_high_seen = %0d on a clean clock, expected %0d",
min_high_seen, T_HIGH); errors = errors + 1; end
// ---- 3. THE test of this chapter: a stretch must not erode the HIGH phase ------------
// A generator that started its high count at its own release would report a high phase
// near zero here, because the 300-tick hold would consume the whole count before the line
// ever rose. The high phase must still be exactly T_HIGH.
begin
before_p = n_periods;
stretch_next_low(300);
wait_periods(1);
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: after a 300-tick stretch the HIGH phase measured %0d, expected %0d -- the generator counted its high phase from its own RELEASE, not from the observed rise",
highlog[n_log-1], T_HIGH); errors = errors + 1; end
if (lowlog[n_log-1] != T_LOW + 300) begin
$display("FAIL: the stretched LOW phase measured %0d, expected %0d",
lowlog[n_log-1], T_LOW + 300); errors = errors + 1; end
if (n_periods == before_p) begin
$display("FAIL: the stretched period was never reported"); errors = errors + 1; end
end
// ---- 4. the stall is reported, with its duration -------------------------------------
begin
before_s = n_stalls;
stretch_next_low(150);
wait_periods(1);
if (n_stalls != before_s + 1) begin
$display("FAIL: a 150-tick stretch produced %0d stalls", n_stalls - before_s);
errors = errors + 1; end
if (lowlog[n_log-1] != T_LOW + 150) begin
$display("FAIL: low phase %0d after a 150-tick stretch, expected %0d",
lowlog[n_log-1], T_LOW + 150); errors = errors + 1; end
end
// ---- 4b. the period AFTER a stretch is back to nominal -------------------------------
// The regression for a stale stall count. Every earlier check looks at the STRETCHED
// period, so a design that carried the stretch total forward reported the stretched length
// on every following period and no test noticed. This one runs a clean period immediately
// after a stretched one and requires the nominal low phase back.
begin
wait_periods(1);
if (lowlog[n_log-1] != T_LOW) begin
$display("FAIL: the period after a stretch measured low=%0d, expected the nominal %0d -- a stale stall count was carried forward",
lowlog[n_log-1], T_LOW); errors = errors + 1; end
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: the period after a stretch measured high=%0d, expected %0d",
highlog[n_log-1], T_HIGH); errors = errors + 1; end
end
// ---- 5. the HIGH minimum survives every stretch so far ------------------------------
// The whole compliance claim of the chapter in one assertion: tHIGH is untouched.
if (min_high_seen != T_HIGH) begin
$display("FAIL: min_high_seen = %0d after stretching, expected %0d -- stretching eroded the high phase",
min_high_seen, T_HIGH); errors = errors + 1; end
// ---- 6. a very long stretch -- section 4.2.2 puts no limit on it ---------------------
begin
stretch_next_low(4000);
wait_periods(1);
if (lowlog[n_log-1] != T_LOW + 4000) begin
$display("FAIL: a 4000-tick stretch gave a low phase of %0d, expected %0d",
lowlog[n_log-1], T_LOW + 4000); errors = errors + 1; end
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: the high phase after a 4000-tick stretch measured %0d",
highlog[n_log-1]); errors = errors + 1; end
end
// ---- 7. back-to-back stretches on consecutive periods -------------------------------
// Section 3.1.9's bit-level case: "extending each clock LOW period". Every period is
// stretched, and every high phase must still be exact.
begin
for (k = 0; k < 3; k = k + 1) begin
stretch_next_low(80);
wait_periods(1);
if (highlog[n_log-1] != T_HIGH) begin
$display("FAIL: bit-level stretching eroded the high phase to %0d on period %0d",
highlog[n_log-1], k); errors = errors + 1; end
if (lowlog[n_log-1] != T_LOW + 80) begin
$display("FAIL: bit-level stretch %0d gave low=%0d, expected %0d",
k, lowlog[n_log-1], T_LOW + 80); errors = errors + 1; end
end
end
// ---- 8. min_low_seen tracks the SHORTEST low phase, which is the unstretched one -----
// A stretch only ever makes the low phase longer, so it can never reduce min_low_seen.
// That is the formal reason stretching cannot violate tLOW: it moves the measurement in
// the compliant direction (Chapter 11.2 section 1 -- tLOW has no maximum).
if (min_low_seen != T_LOW) begin
$display("FAIL: min_low_seen = %0d, expected the unstretched %0d -- a stretch must only LENGTHEN a low phase",
min_low_seen, T_LOW); errors = errors + 1; end
// ---- 9. the stall ends on the rise, not on the release -------------------------------
begin
stretch_next_low(60);
// during the hold the generator must be released AND stalled
if (scl_drive_low !== 1'b0) begin
$display("FAIL: the generator was still driving low during a stretch"); errors = errors + 1; end
wait_periods(1);
if (stalled !== 1'b0) begin
$display("FAIL: stalled still set after the line rose"); errors = errors + 1; end
end
// ---- 10. disabling stops the clock cleanly ------------------------------------------
enable = 1'b0;
tick(400);
if (scl_drive_low !== 1'b0) begin
$display("FAIL: SCL left driven low after disable -- that is a stuck bus"); errors = errors + 1; end
if (errors == 0)
$display("PASS: the high phase is counted from the observed rise so stretching cannot erode tHIGH, a stretch only lengthens the low phase, bit-level and byte-level stretching both work with no stretch-specific logic");
else
$display("FAIL: %0d error(s)", errors);
$finish;
end
initial begin
#8000000;
$display("FAIL: watchdog expired");
$finish;
end
endmodule -- THE STRETCHING MECHANISM AS A MASTER'S CLOCK GENERATOR -- the VHDL form. Same four states, same
-- single decision that makes stretching harmless:
--
-- UM10204 section 3.1.7: "When all masters concerned have counted off their LOW period, the clock
-- line is released and goes HIGH. ... all the masters start counting their HIGH periods."
--
-- The HIGH count begins when the LINE goes high, not when this master released it. A generator
-- that starts its high count at its own release collapses the high phase to nothing on the very
-- bit a stretch ended, and the receiver never samples that bit.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_scl_sync_generator is
generic (
TICK_W : natural := 24;
T_LOW : natural := 130; -- the master's intended LOW count, Fast-mode 1.3 us
T_HIGH : natural := 60 -- the master's intended HIGH count, Fast-mode 0.6 us
);
port (
clk : in std_logic;
rst_n : in std_logic;
enable : in std_logic;
scl_in : in std_logic; -- the OBSERVED line: everything here is a function of it
scl_drive_low : out std_logic;
stalled : out std_logic;
stall_ticks : out unsigned(TICK_W-1 downto 0);
phase_valid : out std_logic;
t_low_actual : out unsigned(TICK_W-1 downto 0);
t_high_actual : out unsigned(TICK_W-1 downto 0);
n_periods : out unsigned(TICK_W-1 downto 0);
min_high_seen : out unsigned(TICK_W-1 downto 0);
min_low_seen : out unsigned(TICK_W-1 downto 0);
n_stalls : out unsigned(TICK_W-1 downto 0)
);
end entity;
architecture rtl of i2c_scl_sync_generator is
type state_t is (S_IDLE, S_LOW, S_WAIT_HIGH, S_HIGH);
signal state : state_t := S_IDLE;
signal scl_q : std_logic := '1';
signal cnt : unsigned(TICK_W-1 downto 0) := (others => '0');
signal low_meas : unsigned(TICK_W-1 downto 0) := (others => '0');
signal s_stalled : std_logic := '0';
signal s_stall : unsigned(TICK_W-1 downto 0) := (others => '0');
signal r_periods : unsigned(TICK_W-1 downto 0) := (others => '0');
signal r_minhigh : unsigned(TICK_W-1 downto 0) := (others => '1');
signal r_minlow : unsigned(TICK_W-1 downto 0) := (others => '1');
signal r_nstalls : unsigned(TICK_W-1 downto 0) := (others => '0');
signal cnt_now : unsigned(TICK_W-1 downto 0);
-- Sized once, so no comparison has to convert on the fly.
constant T_LOW_U : unsigned(TICK_W-1 downto 0) := to_unsigned(T_LOW, TICK_W);
constant T_HIGH_U : unsigned(TICK_W-1 downto 0) := to_unsigned(T_HIGH, TICK_W);
begin
cnt_now <= cnt + 1;
stalled <= s_stalled;
stall_ticks <= s_stall;
n_periods <= r_periods;
min_high_seen <= r_minhigh;
min_low_seen <= r_minlow;
n_stalls <= r_nstalls;
process (clk) is
begin
if rising_edge(clk) then
if rst_n = '0' then
state <= S_IDLE;
scl_q <= '1';
scl_drive_low <= '0';
s_stalled <= '0';
s_stall <= (others => '0');
cnt <= (others => '0');
low_meas <= (others => '0');
phase_valid <= '0';
t_low_actual <= (others => '0');
t_high_actual <= (others => '0');
r_periods <= (others => '0');
r_nstalls <= (others => '0');
-- MINIMUM trackers start at their maximum, the mirror of the detector's zero.
r_minhigh <= (others => '1');
r_minlow <= (others => '1');
else
scl_q <= scl_in;
phase_valid <= '0';
case state is
when S_IDLE =>
scl_drive_low <= '0';
s_stalled <= '0';
if enable = '1' then
scl_drive_low <= '1';
cnt <= (others => '0');
state <= S_LOW;
end if;
-- Counting off the LOW period. A pull-down is authoritative, so the intent and
-- the line agree here and there is no hazard on this side.
when S_LOW =>
if cnt_now >= T_LOW_U then
low_meas <= cnt_now;
scl_drive_low <= '0'; -- release: a REQUEST, not a command
cnt <= (others => '0');
-- Clear the stall count on ENTRY. Leaving the previous stretch's total standing
-- makes the exit path add it to a low phase that was never stretched.
s_stall <= (others => '0');
state <= S_WAIT_HIGH;
else
cnt <= cnt_now;
end if;
-- The wait-state of section 3.1.7. Nothing here is stretch-specific: the
-- generator simply refuses to begin its high count until the line is high.
when S_WAIT_HIGH =>
if scl_in = '1' then
s_stalled <= '0';
cnt <= (others => '0');
-- the low phase AS IT OCCURRED includes whatever the stretch added
low_meas <= low_meas + s_stall;
state <= S_HIGH;
else
if s_stalled = '0' then
s_stalled <= '1';
-- "Including this cycle": the first stalled sample IS one tick.
s_stall <= to_unsigned(1, TICK_W);
r_nstalls <= r_nstalls + 1;
else
s_stall <= s_stall + 1;
end if;
end if;
-- Counting off the HIGH period, started by the observed RISE.
when S_HIGH =>
if cnt_now >= T_HIGH_U then
t_low_actual <= low_meas;
t_high_actual <= cnt_now;
phase_valid <= '1';
r_periods <= r_periods + 1;
if cnt_now < r_minhigh then
r_minhigh <= cnt_now;
end if;
if low_meas < r_minlow then
r_minlow <= low_meas;
end if;
if enable = '1' then
scl_drive_low <= '1';
cnt <= (others => '0');
state <= S_LOW;
else
state <= S_IDLE;
end if;
elsif scl_in = '0' then
-- Somebody pulled the line low during our HIGH phase. The period is
-- abandoned rather than logged with a short high time we did not cause.
cnt <= (others => '0');
state <= S_LOW;
else
cnt <= cnt_now;
end if;
end case;
end if;
end if;
end process;
end architecture; -- The VHDL testbench for the synchronizing clock generator. The testbench owns the WIRED-AND: the
-- observed line is the generator's release ANDed with a slave's hold. That is the only honest way
-- to test a generator whose correctness is defined against the observed line rather than its own
-- output.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_scl_sync_generator_tb is
end entity;
architecture tb of i2c_scl_sync_generator_tb is
constant TICK_W : natural := 24;
constant T_LOW : natural := 130;
constant T_HIGH : natural := 60;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal enable : std_logic := '0';
signal s_hold : std_logic := '0';
signal scl_drive_low : std_logic;
signal scl_line : std_logic;
signal stalled, phase_valid : std_logic;
signal stall_ticks, t_low_actual, t_high_actual : unsigned(TICK_W-1 downto 0);
signal n_periods, min_high_seen, min_low_seen, n_stalls : unsigned(TICK_W-1 downto 0);
signal done : boolean := false;
signal errors : integer := 0;
type int_arr is array (0 to 63) of integer;
signal lowlog : int_arr := (others => 0);
signal highlog : int_arr := (others => 0);
signal n_log : integer := 0;
begin
scl_line <= (not scl_drive_low) and (not s_hold);
clk_gen : process is
begin
while not done loop
clk <= '0'; wait for 5 ns;
clk <= '1'; wait for 5 ns;
end loop;
wait;
end process;
dut : entity work.i2c_scl_sync_generator
generic map (TICK_W => TICK_W, T_LOW => T_LOW, T_HIGH => T_HIGH)
port map (
clk => clk, rst_n => rst_n, enable => enable, scl_in => scl_line,
scl_drive_low => scl_drive_low,
stalled => stalled, stall_ticks => stall_ticks,
phase_valid => phase_valid,
t_low_actual => t_low_actual, t_high_actual => t_high_actual,
n_periods => n_periods, min_high_seen => min_high_seen,
min_low_seen => min_low_seen, n_stalls => n_stalls);
logger : process (clk) is
begin
if rising_edge(clk) then
if rst_n = '1' and phase_valid = '1' and n_log < 64 then
lowlog(n_log) <= to_integer(t_low_actual);
highlog(n_log) <= to_integer(t_high_actual);
n_log <= n_log + 1;
end if;
end if;
end process;
stim : process is
procedure tick(n : in integer) is
begin
for i in 1 to n loop
wait until falling_edge(clk);
end loop;
end procedure;
-- Wait for the generator's release point, then hold the line down for `len` ticks.
procedure stretch_next_low(len : in integer) is
begin
wait until falling_edge(clk);
while scl_drive_low /= '0' loop
wait until falling_edge(clk);
end loop;
s_hold <= '1';
tick(len);
s_hold <= '0';
end procedure;
-- Wait for `n` reported periods AND let the logger's write land. Both processes run on the
-- same rising edge, so returning immediately can leave n_log not yet incremented and a
-- read of lowlog(n_log-1) would pick up the PREVIOUS period.
procedure wait_periods(n : in integer) is
variable seen : integer := 0;
begin
seen := 0;
while seen < n loop
wait until rising_edge(clk);
if phase_valid = '1' then seen := seen + 1; end if;
end loop;
wait until falling_edge(clk);
end procedure;
procedure chk(cond : in boolean; msg : in string) is
begin
if not cond then
report "FAIL: " & msg severity error;
errors <= errors + 1;
wait for 0 ns;
end if;
end procedure;
begin
tick(4); rst_n <= '1'; tick(4);
-- 1. reset: MINIMUM trackers start at their maximum
chk(min_high_seen = (min_high_seen'range => '1') and min_low_seen = (min_low_seen'range => '1'),
"the minimum trackers did not start at their maximum");
chk(n_periods = 0 and n_stalls = 0, "counters nonzero out of reset");
-- 2. a free-running clock hits its intended phases exactly
enable <= '1';
wait_periods(4);
chk(lowlog(0) = T_LOW and highlog(0) = T_HIGH, "an unstretched period measured the wrong phases");
chk(n_stalls = 0, "an unstretched clock reported stalls");
chk(to_integer(min_high_seen) = T_HIGH, "min_high_seen wrong on a clean clock");
-- 3. THE test of this chapter: a stretch must not erode the HIGH phase
stretch_next_low(300);
wait_periods(1);
chk(highlog(n_log-1) = T_HIGH,
"after a 300-tick stretch the HIGH phase was wrong -- the generator counted from its own RELEASE, not from the observed rise");
chk(lowlog(n_log-1) = T_LOW + 300, "the stretched LOW phase measured the wrong value");
-- 4. the stall is reported with its duration
stretch_next_low(150);
wait_periods(1);
chk(lowlog(n_log-1) = T_LOW + 150, "low phase wrong after a 150-tick stretch");
-- 4b. the period AFTER a stretch is back to nominal: the regression for a stale stall count
wait_periods(1);
chk(lowlog(n_log-1) = T_LOW,
"the period after a stretch did not return to the nominal low phase -- a stale stall count was carried forward");
chk(highlog(n_log-1) = T_HIGH, "the period after a stretch had the wrong high phase");
-- 5. the HIGH minimum survives every stretch -- the compliance claim of the chapter
chk(to_integer(min_high_seen) = T_HIGH, "stretching eroded the high phase");
-- 6. a very long stretch (section 4.2.2 -- no limit)
stretch_next_low(4000);
wait_periods(1);
chk(lowlog(n_log-1) = T_LOW + 4000, "a 4000-tick stretch gave the wrong low phase");
chk(highlog(n_log-1) = T_HIGH, "the high phase after a 4000-tick stretch was wrong");
-- 7. back-to-back stretches: section 3.1.9's bit-level case
for k in 0 to 2 loop
stretch_next_low(80);
wait_periods(1);
chk(highlog(n_log-1) = T_HIGH, "bit-level stretching eroded the high phase");
chk(lowlog(n_log-1) = T_LOW + 80, "a bit-level stretch gave the wrong low phase");
end loop;
-- 8. min_low_seen tracks the SHORTEST low phase, which is the unstretched one. A stretch
-- only ever lengthens a low phase, which is why it cannot violate tLOW.
chk(to_integer(min_low_seen) = T_LOW,
"min_low_seen is not the unstretched low phase -- a stretch must only LENGTHEN it");
-- 9. the stall ends on the rise, not on the release
stretch_next_low(60);
chk(scl_drive_low = '0', "the generator was still driving low during a stretch");
wait_periods(1);
chk(stalled = '0', "stalled still set after the line rose");
-- 10. disabling stops the clock cleanly
enable <= '0';
tick(400);
chk(scl_drive_low = '0', "SCL left driven low after disable -- that is a stuck bus");
if errors = 0 then
report "i2c_scl_sync_generator self-check complete: the high phase is counted from the observed rise so stretching cannot erode tHIGH, a stretch only lengthens the low phase, bit-level and byte-level stretching both work with no stretch-specific logic" severity note;
else
report "FAILURES in i2c_scl_sync_generator" severity error;
end if;
done <= true;
wait;
end process;
end architecture;6a. Five Decisions Worth Defending
The HIGH phase is counted from the observed rise; the LOW phase from the generator's own intent. §2's asymmetry, implemented. The two are not symmetrical because a pull-down is authoritative and a release is not, and a generator that treats them the same is wrong in exactly one direction.
The wait state is a state, not a flag. S_WAIT_HIGH exists as a distinct state precisely so that "released but not yet high" is representable. A generator that went straight from S_LOW to S_HIGH on its own release has nowhere to be during a stretch, which is another way of describing mutation K1.
The low phase is measured as it OCCURRED, including the stretch. The measurement is taken when the line rises rather than when the generator released, so t_low_actual is what the bus really saw. That is what makes Chapter 12.3's accounting possible from this block's outputs.
The stall count is cleared on ENTRY to the wait state. This is the bug §7 records, and it was found by porting to a third language rather than by either of the first two suites.
A low line during the high phase abandons the period. §5's first point. The alternative — reporting it — manufactures a tHIGH violation out of another master's legitimate behaviour.
6b. Verified Execution
$ iverilog -g2012 -o d2 i2c_scl_sync_generator.sv i2c_scl_sync_generator_tb.sv && ./d2
PASS: the high phase is counted from the observed rise so stretching cannot erode tHIGH, a
stretch only lengthens the low phase, bit-level and byte-level stretching both work with no
stretch-specific logic
i2c_scl_sync_generator_tb.sv:199: $finish called at 74520000 (1ps)
$ iverilog -g2005 -o v2 i2c_scl_sync_generator.v i2c_scl_sync_generator_tb.v && ./v2
PASS: the high phase is counted from the observed rise so stretching cannot erode tHIGH, a
stretch only lengthens the low phase, bit-level and byte-level stretching both work with no
stretch-specific logic
i2c_scl_sync_generator_tb.v:211: $finish called at 74520000 (1ps)
$ nvc -a i2c_scl_sync_generator.vhd i2c_scl_sync_generator_tb.vhd
$ nvc -e i2c_scl_sync_generator_tb && nvc -r i2c_scl_sync_generator_tb --stop-time=2000us
** Note: 74520ns+1: i2c_scl_sync_generator self-check complete: the high phase is counted
from the observed rise so stretching cannot erode tHIGH, a stretch only lengthens the low
phase, bit-level and byte-level stretching both work with no stretch-specific logicAll three at 74520 ns.
7. What the Testbench Proves
The testbench owns the wired-AND: the observed line is the generator's release ANDed with a slave's hold. That is the only honest way to test a generator whose correctness is defined against the observed line.
| # | stimulus | what it establishes |
|---|---|---|
| 1 | reset | the minimum trackers read their maximum |
| 2 | four free-running periods | low and high are exactly T_LOW and T_HIGH; no stalls |
| 3 | a 300-tick stretch | the high phase is still exactly T_HIGH; the low phase is T_LOW + 300 |
| 4 | a 150-tick stretch | the stall is counted; the low phase grows by exactly 150 |
| 4b | the period after a stretch | back to nominal on both phases |
| 5 | every stretch so far | min_high_seen is still T_HIGH — stretching eroded nothing |
| 6 | a 4000-tick stretch | the low phase grows by 4000; the high phase is untouched |
| 7 | three consecutive stretched periods | bit-level stretching works; every high phase exact |
| 8 | min_low_seen | equals the unstretched low phase |
| 9 | during a stretch | the generator is released, and stalled clears on the rise |
| 10 | disable | SCL is released, not left low |
Test 3 is the chapter. One assertion — the high phase after a 300-tick stretch equals T_HIGH exactly — separates a compliant generator from one that collapses the phase. Every other property in the suite is satisfied by both.
Test 8 is the formal reason stretching cannot violate tLOW, and it is worth more than it looks. A stretch only ever lengthens a low phase, so it can only move the measurement in the compliant direction — tLOW has a minimum and no maximum. min_low_seen therefore stays at the unstretched value no matter how much stretching occurs, and if it ever fell below that, something other than stretching happened.
Test 4b is the regression for §8's found bug, and its design is the lesson: every other check in the suite looks at the stretched period. A generator carrying a stale stall total forward reported the stretched length on every following period, and no test noticed until one was written that looks at a clean period immediately after a stretched one.
Test 10 is two lines and guards the module's worst failure. A generator that stops while driving SCL low produces a stuck clock, and Chapter 12.4 §2 shows the specification's remedy for that is a hardware reset — not a protocol action.
8. Mutation Testing
Five defects injected into the SystemVerilog generator.
| # | injected defect | outcome |
|---|---|---|
| K1 | the HIGH phase is counted from the master's own release | killed — test 3 |
| K2 | the stall count is not cleared on entry to the wait state | killed — test 4b |
| K3 | the low phase does not include the stall | killed — test 3 |
| K4 | the minimum trackers start at zero | killed — test 1 |
| K5 | the stall count starts at 0, so every stretch is one tick short | killed — test 3 |
Five injected, five killed. Two are worth recording properly.
K1 is the defect this whole chapter is organised around. Replacing the wait-state's condition with an unconditional pass removes the wait entirely: the generator proceeds to its high phase the moment it releases, regardless of the line. The failure message is the stretched LOW phase measured 130, expected 430 — the generator reports the low phase it intended rather than the one the bus had, because it never observed the stretch at all.
K2 was found by the VHDL port, not by the SystemVerilog or Verilog suites, and that is worth being explicit about because it is a recurring argument for tri-language work.
The defect: stall_ticks was never cleared, so the previous stretch's total was still standing when the next wait state was entered — and the exit path adds it to the low phase. Every period after a stretch therefore reported the stretched length.
The SystemVerilog and Verilog suites passed. Not because they were weaker, but because every check in them looked at a stretched period, and on a stretched period the stale total happens to be overwritten by the current one before it is used. The VHDL run failed on a different check for reasons of signal-versus-variable timing, and chasing that difference is what exposed the shared defect underneath.
Porting a design to a third language is a verification activity, not a translation exercise. Three implementations of the same intent disagreeing is evidence; two agreeing is not.
The fix went into all three languages and test 4b went in alongside it, so the gap is closed by a test rather than by the accident of a simulator's scheduling.
K5 is the off-by-one, and it is the one I introduced myself. The first version started the stall count at 0, so every reported stretch was one sample period short. It was caught because the testbench asserts the measured low phase equals T_LOW plus the length actually driven — an exact value, not a range. Chapter 11.5 §7 draws the same conclusion from its mutation E2: a checker's measurements must be asserted, not only its verdicts, because a defect that shifts a number in the safe direction is invisible to a verdict.
9. Verification Connection — Properties About a Line You Do Not Control
// THE property of this chapter, and it is short because the difficulty is in knowing to write it
// at all: the high phase must be measured from the observed RISE. Everything else about a
// generator with mutation K1 looks correct, including its average frequency and its tLOW.
property p_thigh_from_observed_rise;
@(posedge clk) $rose(scl_in) |=> (scl_in throughout ##[T_HIGH-1:$] $fell(scl_in));
endproperty
assert property (p_thigh_from_observed_rise)
else $error("SCL fell within tHIGH of the observed rise -- the high phase was counted from this master's own release, not from the line");
// A release is a REQUEST. So a generator must never assume the line followed it, and the
// property that states this is an implication in the direction people forget: driving low
// implies the line IS low, but releasing implies nothing at all.
property p_pulldown_is_authoritative;
@(posedge clk) (scl_drive_low) |-> (!scl_in);
endproperty
assert property (p_pulldown_is_authoritative)
else $error("this master drove SCL low and the line was high -- contention, not a wait state");
// Note that there is deliberately NO property of the form `!scl_drive_low |-> scl_in`. That
// would assert that a release always produces a high line, which is exactly the false belief
// mutation K1 encodes. Section 2's asymmetry is a fact about which properties are writable.
// The NEGATIVE property that guards the worst failure: whatever the generator does, it must not
// park SCL low. Chapter 12.4 section 2 shows a stuck SCL has no protocol-level recovery, so this
// assertion is cheap insurance against an unrecoverable state.
property p_never_parks_low;
@(posedge clk) (!enable) |-> ##[1:MAX_PHASE] !scl_drive_low;
endproperty
assert property (p_never_parks_low)
else $error("the generator stopped while holding SCL low -- that is a stuck bus"); covergroup i2c_scl_sync_cg with function sample(int t_low, int t_high, int stall,
int low_nominal, bit prev_stretched);
// The high phase is binned around its NOMINAL value with a single-value bin on it, because
// the property under test is that it is exactly right rather than roughly right.
high_phase: coverpoint t_high {
bins collapsed = {[0:1]}; // what mutation K1 produces -- must stay EMPTY
bins short = {[2:59]};
bins exact = {60}; // T_HIGH: the only legal value from this generator
bins over = {[61:$]};
}
// Stall length, in the same order-of-magnitude bins as Chapter 12.1's, plus a zero bin --
// because an unstretched period is a case the suite must contain, not merely tolerate.
stall_len: coverpoint stall {
bins none = {0};
bins one = {1};
bins short = {[2:99]};
bins medium = {[100:9999]};
bins long = {[10000:$]};
}
// THE cross that mutation K2 required. A stretched period followed by a clean one is the only
// shape that exposes a stale stall total, and a suite whose stretches are all isolated --
// or all consecutive -- never produces it.
after_stretch: coverpoint prev_stretched { bins clean_after_stretch = {1}; }
high_x_after: cross high_phase, after_stretch;
// Consecutive stretching is section 3.1.9's bit-level case and a distinct regime: it is the
// one where the bus runs at the slave's rate rather than the master's.
consecutive: coverpoint (stall > 0 && prev_stretched) { bins bit_level_run = {1}; }
endgroup10. FPGA and ASIC Implications
The generator is four states, two counters and two minimum trackers — around 130 flops at TICK_W = 24. The synchronization costs nothing in area; it is a question of which signal the state machine looks at.
SCL becomes an input as well as an output, and that path needs a synchroniser. SCL is asynchronous to the sample clock, so the feedback edge of §2's diagram carries a two-flop synchroniser at minimum, plus the spike filter if one is fitted. That latency is inside the generator's reaction time, which matters at Fast-mode Plus: a 60 ns filter (Chapter 11.8 §4) plus a synchroniser means the generator learns the line rose roughly 80 ns late, and its high phase is 80 ns longer than intended. Longer is compliant — tHIGH has a minimum, not a maximum — but it does eat the period budget of Chapter 11.9.
A generator built this way needs no configuration bit for stretching. It tolerates stretching because it sequences on the line, and on a bus where nothing stretches it behaves identically. There is no "enable stretch support" register, and a design that has one has probably built two clock generators.
The two terminal counts of Chapter 11.2 §10 remain necessary and are not sufficient. That chapter showed a compliant clock needs separate low and high counts rather than a divided period. This chapter adds that the high count must be started by the right event. Both are required, and neither implies the other.
And a generator that can be disabled must release the line when it is. §6's test 10. A state machine reset while in S_LOW will park SCL low unless the reset path clears the drive, and that is the one failure on this bus with no protocol-level remedy.
11. Debugging — The EEPROM That Corrupted Only Its Own Writes
Pitfall — starting the high-phase count at the master's own release
// A master's SCL generator, written from Chapter 11.2's lesson and correct as far as that goes:
// two separate terminal counts, so the duty cycle is not a divided period.
//
// localparam LOW_CNT = 150; // 1.5 us at 100 MHz
// localparam HIGH_CNT = 100; // 1.0 us -> 400 kHz, both minima clear
//
// always_ff @(posedge clk) begin
// case (state)
// S_LOW: if (cnt == LOW_CNT) begin scl_drive_low <= 1'b0; cnt <= 0; state <= S_HIGH; end
// else cnt <= cnt + 1;
// S_HIGH: if (cnt == HIGH_CNT) begin scl_drive_low <= 1'b1; cnt <= 0; state <= S_LOW; end
// else cnt <= cnt + 1;
// endcase
// end
//
// SCL is open-drain, a pull-up is fitted, and the design was verified against four sensors and two
// GPIO expanders. Frequency measured 400 kHz, duty cycle 40 %, tLOW and tHIGH both comfortable.
//
// There is no S_WAIT_HIGH. The generator goes from S_LOW straight to S_HIGH on its OWN release,
// and it never reads scl_in at all -- the input is wired to the core and unused.Five of the six devices were flawless. The sixth, an EEPROM, corrupted data on WRITES only. Reads from the same part were perfect, at any address, indefinitely.
The corruption was always in the same place: the byte immediately following a page boundary. Everything before it was written correctly and everything after it was written correctly.
The obvious theory was page-boundary handling in the driver, and a great deal of time went into the address arithmetic and the page-wrap rules for that part number. The arithmetic was right.
The second theory was the part. Two more EEPROMs from different date codes behaved identically, and an EEPROM from a different manufacturer behaved identically -- which was taken as evidence that the fault was in the driver after all, since three parts cannot all be wrong.
That reasoning had it backwards, and recognising so is what broke it open: three independent parts behaving the same way is evidence they are all CORRECT and the master is not.
A scope on SCL during a write showed it. After the acknowledge of the byte before a page boundary, the EEPROM held SCL low for about 3 ms while it programmed the page -- a textbook byte-level stretch, exactly section 3.1.9's handshake. When it released, SCL rose and was pulled low again within about 40 ns. The high phase on that bit was 40 ns against a 600 ns minimum, so the EEPROM never sampled the bit the master was presenting.
The master's high counter had expired 3 ms earlier, during the stretch. It had been counting the whole time the line was held down, because it started counting when the MASTER released -- not when the LINE rose.
The generator started its high-phase count at its own release instead of at the observed rise. UM10204 section 3.1.7 says the opposite in as many words: "When all masters concerned have counted off their LOW period, the clock line is released and goes HIGH ... and all the masters start counting their HIGH periods" -- and it explains why, in the clause between: "There is then no difference between the master clocks and the state of the SCL line."
The counting may only start once intent and observation have converged, and during a stretch they have not.
Everything else about the generator was right, which is why nothing else showed it. The frequency averaged correctly. tLOW was longer than the minimum, which is always compliant. Every bit to every non-stretching device was exact. The only bit ever damaged was the first one after a stretch ended -- and the only device that stretched was the EEPROM, and only while programming, which is only during writes, which is why reads were perfect.
The failure landed precisely on the byte the stretch existed to protect.
12. Common Misconceptions
"Stretching is a protocol feature the bus implements." It is a consequence of wired-AND that the bus cannot avoid. §3.1.7 describes the entire mechanism while discussing multi-master synchronization, without mentioning stretching.
"A master needs stretch-support logic." It needs to sequence its phases on the observed line. A generator built that way tolerates stretching without knowing the concept exists, and needs no configuration bit.
"Releasing SCL makes it high." Releasing is a request; only a pull-down is a command. The line rises when every device has released, which is why !scl_drive_low |-> scl_in is not a writable property.
"Counting the low phase from my own intent is as wrong as counting the high phase that way." The low phase is fine: intent and line agree on the way down, because a pull-down is authoritative. The hazard is one-sided.
"A stretch can cause a tLOW violation." A stretch only lengthens a low phase, and tLOW has a minimum with no maximum. It moves the measurement in the compliant direction — §7's test 8 is the assertion.
"If the frequency and duty cycle measure correctly, the generator is right." Both measure correctly with mutation K1 present. The only check that catches it is the high phase on the period a stretch ended.
"A generator that stops can leave SCL wherever it was." A generator that stops while driving low produces a stuck clock, which is the one fault on this bus with no protocol-level recovery.
"Two implementations agreeing means the design is correct." §8's K2 passed in SystemVerilog and Verilog and was exposed by the VHDL port. Two agreeing implementations are one hypothesis tested twice.
13. Reason It Through
Why is a release fundamentally different from a pull-down, and what follows for a state machine?
Because open-drain gives a pull-down absolute authority — no device can override it — while a release only permits the line to rise if every device has released. So a state machine may treat its own pull-down as having taken effect immediately, and must treat its own release as a request whose outcome it has to observe. That is why the wait state exists.
A generator measures 400 kHz with a 40 % duty cycle, tLOW and tHIGH both comfortable, and corrupts one device's data. What single measurement would settle it?
The high phase on the period immediately after a stretch ends. Every aggregate measure is satisfied by a generator that counts its high phase from its own release; only that one period shows the collapse.
Why does the failure land on the byte the stretch was protecting?
Because the collapse happens on the bit whose high phase follows the stretch — and the slave stretched precisely in order to be ready for that bit. The master's high counter expired during the stretch, so the instant the slave releases the line the master pulls it low again, and the bit the slave spent milliseconds preparing for is never sampled.
Three different manufacturers' parts all fail the same way with one master. What does that imply?
That the parts are correct and the master is not. Independent implementations converging on the same behaviour is evidence that the behaviour is what the specification requires — the opposite of the usual instinct, which is that the majority cannot all be wrong about the same thing.
A defect passed in SystemVerilog and Verilog and was exposed by a VHDL port. What does that say about tri-language work?
That it is verification rather than translation. The two Verilog-family suites shared the same stimulus and the same blind spot; the third language's different scheduling produced a different failure, and chasing the difference found a defect common to all three. Two implementations agreeing is one hypothesis tested twice.
Why does a generator built on the observed line need no "enable stretching" configuration?
Because a stretching slave is indistinguishable, to that state machine, from a slow rise — both are "the line is not high yet". The generator handles both with the same wait state, so there is nothing to configure, and a design carrying such a bit has probably implemented two generators.
14. Understanding Check
15. Summary
The mechanism already existed. §3.1.7 describes clock stretching completely while talking about multi-master synchronization; §3.1.9 grants a slave permission to participate in it. There is no stretching logic to write.
Driving low is a command; releasing is a request. Every consequence in this module follows from that asymmetry, and it is why the hazard is one-sided: counting the low phase from intent is fine, counting the high phase from intent is not.
The high phase must be counted from the observed rise. A generator that counts from its own release collapses the high phase on the bit a stretch ended — the bit the slave was preparing — while its frequency, duty cycle and tLOW all still measure correctly.
The wait state is where a master is during a stretch, and a generator without one has nowhere to be.
A stretch cannot violate tLOW, because it only lengthens a low phase and tLOW has no maximum.
A generator that sequences on the line needs no stretch support and no configuration bit. A stretching slave and a slow rise are the same thing to it.
Porting to a third language is verification. The stale-stall-count bug passed two Verilog-family suites and was exposed by VHDL; two implementations agreeing is one hypothesis tested twice.
And a generator must never park SCL low, because that is the single fault on this bus with no protocol-level recovery.
16. What Comes Next
Chapter 12.3 prices what this chapter made harmless. Stretching costs nothing in correctness once the generator is right — and it costs throughput and latency in amounts worth measuring, because the two levels of Chapter 12.1 §3 cost very differently.
It also turns that chapter's Hs-mode note into a hardware check. §3.1.9's closing sentence — "In Hs-mode, this handshake feature can only be used on byte level" — is the one normative constraint in this module that depends on the speed mode, and the reason behind it is mechanical: in Hs-mode the master drives SCL with a current source and disables it only after the acknowledge, so a slave stretching anywhere else would be fighting an active driver rather than holding a released line. That is Chapter 12.1 §4's push-pull fork appearing again, inside a single speed mode.
Continue learning
Related tutorials
- Related topic
Why I²C Clock Stretching Exists
The one mechanism that lets a target push back on a clock it does not own. Covers the mismatch it solves, the specification's two stretching levels, and why 'optional' makes a non-stretching bus an electrical contract.
- Related topic
I²C SCL Synchronization — How Masters Agree on One Clock
Two sentences of the specification give a complete combining rule: the low phase is the longest and the high phase the shortest. The synchronized clock therefore belongs to neither master.
- Related topic
I²C SDA Arbitration — Wired-AND Decides Bit by Bit
Arbitration with no arbiter, no priority and no protocol exchange — resolved by one asymmetric test each master performs on itself. Settles what 'no information is lost' actually means.
- Related topic
Decomposing an I²C Master — From Requirements to Architecture
An I²C master is not one state machine, and the reason is structural rather than stylistic: the protocol imposes four independent time bases that change on four unrelated events. Derives the block structure from the normative obligations, establishes the wired-AND bus model every later chapter is written against, and shows why the framing generator cannot live inside the bit engine.
