I²C · Module 11
tSU;STA and tHD;STA — START and Repeated-START Margins
The first parameters measured between two different signals rather than against a clock edge — and the timing-level reason a repeated START is not simply a START in the middle of a transfer.
Every parameter so far has measured SDA against SCL: a stable interval ending at a clock edge, a hold beginning at one, a response delay bounded from one. The clock was always the reference.
The framing parameters are different. tSU;STA and tHD;STA measure the interval between an SCL edge and an SDA edge, in opposite directions:
tSU;STA— from SCL's rise to SDA's fall ·tHD;STA— from SDA's fall to SCL's fall
Neither has a clock edge at both ends. Both ends of both intervals are a specific edge on a specific line, and getting the pairing wrong produces a checker that measures something real and reports it under the wrong name.
This chapter also answers, at the timing level, the question Chapter 10.2 answered at the protocol level: why a repeated START is a genuinely different event from a first START, when the waveform is the same shape.
1. The Two Rows, and an Identity
Three observations, and the third is the one that shapes the chapter.
tHD;STA(min) equals tHIGH(min) in all three modes. 4.0, 0.6, 0.26 — the same three numbers, exactly. That is not coincidence: after SDA falls, SCL is still high, and the interval before SCL may fall is the remainder of a high phase. The specification asks for a full high phase's worth. A START's hold time is a high phase wearing a different name, which is why the table can add "after this period, the first clock pulse is generated" — the high phase that has just elapsed is the first clock pulse's high time.
tSU;STA(min) equals tLOW(min) in Standard-mode only. 4.7 matches, but Fast-mode's 0.6 does not match 1.3, and Fm+'s 0.26 does not match 0.5. So the analogy that works for the hold time does not extend to the setup time — the repeated-START setup is a shorter requirement than a low phase in the faster modes, because it does not have to contain a data transition.
And the hazard: tSU;STA and tHD;STA are EQUAL in Fast-mode and Fast-mode Plus. 0.6 and 0.6; 0.26 and 0.26. They differ only in Standard-mode, 4.7 against 4.0.
2. Why a Repeated START Is Timed Differently
Both table rows say "(repeated) START" and "repeated START condition". That wording is deliberate, and it is where the two kinds of START part company.
| a first START | a repeated START | |
|---|---|---|
| what precedes it | an idle bus | the previous transfer's last bit |
| the preceding requirement is | tBUF — bus free time | tSU;STA — setup from SCL's rise |
| who else may be starting | anyone — arbitration is possible | nobody — this master holds the bus |
| its hold requirement | tHD;STA | tHD;STA, identical |
The hold half is the same for both. Once SDA has fallen while SCL is high, the event is a START, and SCL must stay high for tHD;STA regardless of what came before.
The setup half is a different parameter for each. A first START is preceded by tBUF, the bus-free time of Chapter 11.6 — because the bus was idle and the requirement is about how long it had been idle. A repeated START is preceded by tSU;STA, because the bus was active and the requirement is about the margin after the previous bit's clock pulse.
That is the timing-level reason the two events differ. Chapter 10.2 gave the protocol reason — a repeated START keeps the bus, so no other master may interleave — and the two are the same fact seen from two levels. A repeated START is not a START that happens to occur mid-transfer; it is an event whose entry condition is a clock-referenced margin rather than an idle interval.
And the numbers reflect it. In Standard-mode tBUF(min) is 4.7 µs and tSU;STA(min) is also 4.7 µs — equal, which looks like the two cases collapsing. In Fast-mode tBUF is 1.3 µs and tSU;STA is 0.6 µs: a repeated START may be issued in under half the time a first START requires. That is the speed advantage of holding the bus, expressed as a number, and it is why Chapter 10.1 prefers a repeated START for a write-then-read even setting atomicity aside.
3. The Two Margins, Drawn
tSU;STA ends where tHD;STA begins: at SDA's falling edge
10 cyclesThree things the figure settles.
SDA's falling edge is the shared endpoint. One margin ends there and the other begins there, which means a single event closes one measurement and opens another — and a checker must do both in the same cycle. §6a is about getting that right.
SCL is high across the whole of both margins. SDA falling while SCL is high is the definition of a START (Chapter 5.2), so the figure is that definition with two intervals attached. If SCL were low, the SDA fall would be an ordinary data transition and neither margin would apply.
Together the two margins are longer than a high phase. In Fast-mode, 0.6 + 0.6 = 1.2 µs of SCL high time, against tHIGH(min) of 0.6 µs. A repeated START therefore costs a stretched high phase — SCL is held high for at least twice its normal minimum — which is the real reason a repeated START is not free even though it is cheaper than a STOP-then-START.
4. What Each Margin Protects
tSU;STA protects other devices' ability to see the START coming. The previous bit's clock pulse has just ended and SCL has risen again; every device on the bus is watching for a data bit. The setup margin is the time in which their input stages settle on the high SDA level before it falls, so that the fall is unambiguously an edge rather than part of a still-settling transition. Without it, a device whose SDA input had not yet resolved the previous bit could miss the START entirely and keep counting bits.
tHD;STA protects the START's own recognition. After SDA falls, SCL must remain high long enough for every device to register that a START occurred before the first clock pulse arrives. The table's note is explicit about the boundary — "after this period, the first clock pulse is generated" — so the hold margin is the gap between detecting a START and being clocked by it.
That is why tHD;STA equals tHIGH(min): the recognition of a START takes exactly as long as the sampling of a bit, because it is the same input stage doing the same job.
5. The Start Margin Checker in Three Languages
The design watches both lines, recognises a START by its definition, measures both margins, and reports each independently.
// START AND REPEATED-START MARGINS: tSU;STA and tHD;STA. Together they bracket the SDA
// falling edge that makes a START, and between them they are what keeps a repeated START
// distinguishable from a data bit at speed.
//
// SCL rises --[ tSU;STA ]--> SDA FALLS (the START) --[ tHD;STA ]--> SCL falls
// (first clock pulse)
//
// Table 10 names them:
// tHD;STA "hold time (repeated) START condition. After this period, the first clock
// pulse is generated." min 4.0 / 0.6 / 0.26 us
// tSU;STA "set-up time for a repeated START condition" min 4.7 / 0.6 / 0.26 us
//
// TWO THINGS ABOUT THE PAIR ARE WORTH NOTICING BEFORE THE CODE.
//
// First, tSU;STA is specified for a REPEATED start only, and that is not an oversight. A
// plain START begins from an idle bus, where SCL has been released high for at least tBUF
// and usually far longer -- so the setup is satisfied by the bus having been idle, and
// there is nothing for a parameter to constrain. A repeated START is the case where SCL has
// only just been released, and the margin has to be stated because it can be short.
//
// Second, these two are measured completely differently from tSU;DAT, and the reason is
// structural rather than a matter of taste. tSU;DAT's interval ENDS at its reference edge,
// so it cannot be timed by a counter armed at the start -- by the time the rising edge
// arrives the interval is over, which is why Chapter 11.3 needs a continuously running
// timer. Both of these intervals BEGIN at an edge, so a counter armed by that edge measures
// them directly. Whether a timing parameter can be measured with an armed counter is
// decided by which end of it the reference edge sits at.
//
// PASSIVE: observes the two wires and drives nothing.
module i2c_start_margin_checker #(
parameter int TICK_W = 16,
// Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60 ticks for both.
parameter int T_SU_STA_MIN = 60,
parameter int T_HD_STA_MIN = 60
)(
input logic clk,
input logic rst_n,
input logic sda_in,
input logic scl_in,
// ---- framing detected here, so the block is self-contained on a capture ----
output logic start_det, // pulse: SDA fell while SCL was high
output logic restart_det, // pulse: ... and a transfer was already open
output logic stop_det, // pulse: SDA rose while SCL was high
output logic in_transfer,
// ---- measured, reported at the events that close each interval ----
output logic su_valid, // pulse: a tSU;STA has been measured
output logic [TICK_W-1:0] t_su_sta,
output logic hd_valid, // pulse: a tHD;STA has been measured
output logic [TICK_W-1:0] t_hd_sta,
// ---- verdicts ----
output logic viol_su_sta,
output logic viol_hd_sta,
// ---- totals and worst cases ----
output logic [TICK_W-1:0] n_su_sta,
output logic [TICK_W-1:0] n_hd_sta,
output logic [TICK_W-1:0] n_viol,
output logic [TICK_W-1:0] min_su_sta_seen,
output logic [TICK_W-1:0] min_hd_sta_seen
);
logic sda_q, scl_q;
logic scl_rise, scl_fall, sda_fall, sda_rise, scl_stable_high;
assign scl_rise = !scl_q && scl_in;
assign scl_fall = scl_q && !scl_in;
assign sda_fall = sda_q && !sda_in;
assign sda_rise = !sda_q && sda_in;
// SCL high ACROSS the SDA edge, not merely after it -- the detector discipline
// Module 4's mutation suite established and Chapter 10.2 reuses.
assign scl_stable_high = scl_q && scl_in;
// tSU;STA: armed by SCL rising, stopped by the SDA fall that makes the START.
logic su_arm;
logic [TICK_W-1:0] su_ticks;
logic [TICK_W-1:0] su_now;
assign su_now = su_ticks + 1'b1;
// tHD;STA: armed by that same SDA fall, stopped by the next SCL fall.
logic hd_arm;
logic [TICK_W-1:0] hd_ticks;
logic [TICK_W-1:0] hd_now;
assign hd_now = hd_ticks + 1'b1;
always_ff @(posedge clk) begin
if (!rst_n) begin
sda_q <= 1'b1;
scl_q <= 1'b1;
su_arm <= 1'b0;
su_ticks <= '0;
hd_arm <= 1'b0;
hd_ticks <= '0;
start_det <= 1'b0;
restart_det <= 1'b0;
stop_det <= 1'b0;
in_transfer <= 1'b0;
su_valid <= 1'b0;
t_su_sta <= '0;
hd_valid <= 1'b0;
t_hd_sta <= '0;
viol_su_sta <= 1'b0;
viol_hd_sta <= 1'b0;
n_su_sta <= '0;
n_hd_sta <= '0;
n_viol <= '0;
min_su_sta_seen <= {TICK_W{1'b1}};
min_hd_sta_seen <= {TICK_W{1'b1}};
end else begin
sda_q <= sda_in;
scl_q <= scl_in;
start_det <= 1'b0;
restart_det <= 1'b0;
stop_det <= 1'b0;
su_valid <= 1'b0;
hd_valid <= 1'b0;
// ---- arm the setup timer whenever SCL is released ----
if (scl_rise) begin
su_arm <= 1'b1;
su_ticks <= '0;
end else if (su_arm && !(sda_fall && scl_stable_high)) begin
su_ticks <= su_now;
end
// ---- framing, and the measurements the framing events close ----
if (sda_fall && scl_stable_high) begin
start_det <= 1'b1;
if (in_transfer) begin
restart_det <= 1'b1;
// tSU;STA is a REPEATED-start parameter. Measuring it on a plain START
// would report the idle time since the bus was last released, which is
// a true number and not this parameter -- and would make every first
// transfer look like it had an enormous margin, hiding the fact that
// the block never checked the case the spec cares about.
if (su_arm) begin
su_valid <= 1'b1;
t_su_sta <= su_now;
viol_su_sta <= (su_now < T_SU_STA_MIN[TICK_W-1:0]);
n_su_sta <= n_su_sta + 1'b1;
if (su_now < T_SU_STA_MIN[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
if (su_now < min_su_sta_seen) min_su_sta_seen <= su_now;
end
end
in_transfer <= 1'b1;
su_arm <= 1'b0;
// The same edge arms the hold timer. tHD;STA runs from the START to the
// first clock pulse, so its two ends are this edge and the next SCL fall.
hd_arm <= 1'b1;
hd_ticks <= '0;
end else if (sda_rise && scl_stable_high) begin
stop_det <= 1'b1;
in_transfer <= 1'b0;
// A STOP cancels any hold measurement in flight: there will be no first
// clock pulse, so the interval has no end.
hd_arm <= 1'b0;
end else if (hd_arm && scl_fall) begin
// The first clock pulse after the START. This closes tHD;STA.
hd_arm <= 1'b0;
hd_valid <= 1'b1;
t_hd_sta <= hd_now;
viol_hd_sta <= (hd_now < T_HD_STA_MIN[TICK_W-1:0]);
n_hd_sta <= n_hd_sta + 1'b1;
if (hd_now < T_HD_STA_MIN[TICK_W-1:0]) n_viol <= n_viol + 1'b1;
if (hd_now < min_hd_sta_seen) min_hd_sta_seen <= hd_now;
end else if (hd_arm) begin
hd_ticks <= hd_now;
end
end
end
endmodule `timescale 1ns/1ps
// 100 MHz sample clock. Fast-mode tSU;STA = tHD;STA = 0.6 us = 60 ticks.
module i2c_start_margin_checker_tb;
localparam int TICK_W = 16;
localparam int T_SU_STA_MIN = 60;
localparam int T_HD_STA_MIN = 60;
logic clk = 1'b0;
always #5 clk = ~clk;
logic rst_n = 1'b0;
logic sda_in = 1'b1, scl_in = 1'b1;
logic start_det, restart_det, stop_det, in_transfer;
logic su_valid, hd_valid, viol_su_sta, viol_hd_sta;
logic [TICK_W-1:0] t_su_sta, t_hd_sta, n_su_sta, n_hd_sta, n_viol;
logic [TICK_W-1:0] min_su_sta_seen, min_hd_sta_seen;
int errors = 0;
int base_su, base_hd;
logic [TICK_W-1:0] nsu_before, viol_before;
i2c_start_margin_checker #(.TICK_W(TICK_W), .T_SU_STA_MIN(T_SU_STA_MIN),
.T_HD_STA_MIN(T_HD_STA_MIN)) dut (.*);
initial begin #2000000; $display("FAIL: watchdog expired"); $finish; end
logic [TICK_W-1:0] su_log [0:31];
logic vsu_log [0:31];
int n_sulog;
always @(posedge clk) if (rst_n && su_valid && n_sulog < 32) begin
su_log[n_sulog] = t_su_sta; vsu_log[n_sulog] = viol_su_sta; n_sulog++;
end
logic [TICK_W-1:0] hd_log [0:31];
logic vhd_log [0:31];
int n_hdlog;
always @(posedge clk) if (rst_n && hd_valid && n_hdlog < 32) begin
hd_log[n_hdlog] = t_hd_sta; vhd_log[n_hdlog] = viol_hd_sta; n_hdlog++;
end
task automatic tick(input int n);
begin repeat (n) @(negedge clk); end
endtask
// A plain START from an idle bus: SDA falls while SCL is high, then SCL falls after
// `hd` ticks. tSU;STA does not apply here -- see the design header.
task automatic plain_start(input int hd);
begin
sda_in = 1'b1; scl_in = 1'b1; tick(40);
sda_in = 1'b0; tick(hd);
scl_in = 1'b0; tick(20);
end
endtask
// A REPEATED START, with both margins settable. SCL is released, `su` ticks pass, SDA
// falls, `hd` ticks pass, SCL falls to give the first clock pulse.
task automatic repeated_start(input int su, input int hd);
begin
sda_in = 1'b1; scl_in = 1'b0; tick(20); // release SDA while SCL is low
scl_in = 1'b1; tick(su); // release SCL: tSU;STA starts here
sda_in = 1'b0; tick(hd); // the Sr: tHD;STA starts here
scl_in = 1'b0; tick(20); // the first clock pulse
end
endtask
// One ordinary data bit, so the bus does something between framing events.
task automatic data_bit(input logic v);
begin
scl_in = 1'b0; sda_in = v; tick(30);
scl_in = 1'b1; tick(80);
scl_in = 1'b0; tick(30);
end
endtask
task automatic bus_stop();
begin
sda_in = 1'b0; scl_in = 1'b0; tick(20);
scl_in = 1'b1; tick(60);
sda_in = 1'b1; tick(40);
end
endtask
initial begin
tick(3);
if (min_su_sta_seen !== {TICK_W{1'b1}} || min_hd_sta_seen !== {TICK_W{1'b1}}) begin
$display("FAIL: worst-case trackers did not start at their maximum"); errors++; end
rst_n = 1'b1; tick(2);
// ---- 1: a PLAIN START with a generous hold. tHD;STA is measured and passes;
// tSU;STA must NOT be measured at all, because it is a repeated-start
// parameter and a plain START begins from an idle bus.
plain_start(100);
data_bit(1'b1);
if (n_hdlog !== 1) begin
$display("FAIL: %0d tHD;STA measurements from one plain START, expected 1",
n_hdlog); errors++; end
if (n_su_sta !== '0) begin
$display("FAIL: a plain START produced %0d tSU;STA measurements -- it is a repeated-start parameter",
n_su_sta); errors++; end
if (vhd_log[0] !== 1'b0) begin
$display("FAIL: a 100-tick tHD;STA was flagged (min %0d)", T_HD_STA_MIN);
errors++; end
if (hd_log[0] < 16'd95 || hd_log[0] > 16'd105) begin
$display("FAIL: a 100-tick tHD;STA measured %0d", hd_log[0]); errors++; end
// ---- 2: a REPEATED START with both margins generous. Now BOTH are measured.
begin
base_su = n_sulog; base_hd = n_hdlog;
repeated_start(120, 120);
data_bit(1'b1);
if (n_sulog !== base_su + 1) begin
$display("FAIL: a repeated START produced no tSU;STA measurement"); errors++; end
if (vsu_log[base_su] !== 1'b0 || vhd_log[base_hd] !== 1'b0) begin
$display("FAIL: generous margins were flagged -- su=%b hd=%b",
vsu_log[base_su], vhd_log[base_hd]); errors++; end
if (su_log[base_su] < 16'd115 || su_log[base_su] > 16'd125) begin
$display("FAIL: a 120-tick tSU;STA measured %0d", su_log[base_su]); errors++; end
end
bus_stop();
// ---- 3: tSU;STA too SHORT, hold fine. Only the setup verdict may fire. This is
// the case that matters at speed: SCL is released and SDA pulled low almost
// immediately, so a receiver cannot tell the START from a data bit.
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(15, 120);
data_bit(1'b1);
if (vsu_log[base_su] !== 1'b1) begin
$display("FAIL: a 15-tick tSU;STA was not flagged (min %0d)", T_SU_STA_MIN);
errors++; end
if (vhd_log[base_hd + 1] !== 1'b0) begin
$display("FAIL: a short tSU;STA also flagged tHD;STA"); errors++; end
end
bus_stop();
// ---- 4: tHD;STA too SHORT, setup fine. The mirror, and the one that proves the
// two intervals are measured against different pairs of edges.
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(120, 20);
data_bit(1'b1);
if (vhd_log[base_hd + 1] !== 1'b1) begin
$display("FAIL: a 20-tick tHD;STA was not flagged"); errors++; end
if (vsu_log[base_su] !== 1'b0) begin
$display("FAIL: a short tHD;STA also flagged tSU;STA"); errors++; end
end
bus_stop();
// ---- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(T_SU_STA_MIN, T_HD_STA_MIN);
data_bit(1'b1);
if (vsu_log[base_su] !== 1'b0) begin
$display("FAIL: tSU;STA exactly at the minimum was rejected"); errors++; end
if (vhd_log[base_hd + 1] !== 1'b0) begin
$display("FAIL: tHD;STA exactly at the minimum was rejected"); errors++; end
end
bus_stop();
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(T_SU_STA_MIN - 1, T_HD_STA_MIN - 1);
data_bit(1'b1);
if (vsu_log[base_su] !== 1'b1) begin
$display("FAIL: tSU;STA one tick below the minimum was accepted"); errors++; end
if (vhd_log[base_hd + 1] !== 1'b1) begin
$display("FAIL: tHD;STA one tick below the minimum was accepted"); errors++; end
end
bus_stop();
// ---- 6: a STOP cancels a hold measurement in flight. After a START with no clock
// pulse and then a STOP, tHD;STA has no end -- the first clock pulse never
// came -- so no measurement may be reported. A block that reported one would
// be timing an interval that did not happen.
begin
base_hd = n_hdlog;
sda_in = 1'b1; scl_in = 1'b1; tick(40);
sda_in = 1'b0; tick(40); // a START ...
sda_in = 1'b1; tick(40); // ... then straight to a STOP
if (n_hdlog !== base_hd) begin
$display("FAIL: a START followed by a STOP produced a tHD;STA measurement");
errors++; end
// And it must STAY cancelled. A clock pulse after the STOP must not close the
// abandoned interval -- if the arm survived the STOP, this falling edge would
// report a tHD;STA spanning the STOP itself, which is not an interval at all.
scl_in = 1'b1; tick(30);
scl_in = 1'b0; tick(30);
if (n_hdlog !== base_hd) begin
$display("FAIL: a clock pulse after the STOP closed the abandoned tHD;STA interval");
errors++; end
end
// ---- 7: the worst cases are TRACKED. A clean run after the violations of tests
// 3 to 5 must not erase them.
begin
repeat (3) begin
plain_start(300);
data_bit(1'b1);
repeated_start(300, 300);
data_bit(1'b1);
bus_stop();
end
if (min_su_sta_seen > 16'd59) begin
$display("FAIL: min_su_sta_seen = %0d after short margins were seen",
min_su_sta_seen); errors++; end
if (min_hd_sta_seen > 16'd59) begin
$display("FAIL: min_hd_sta_seen = %0d after short margins were seen",
min_hd_sta_seen); errors++; end
end
// ---- 8: an SDA edge while SCL is LOW is DATA, not framing, so it must produce
// neither a framing event nor a margin measurement. This is the check that
// stops the block from inventing STARTs inside a byte.
begin
nsu_before = n_su_sta; viol_before = n_viol;
plain_start(200);
data_bit(1'b0); data_bit(1'b1); data_bit(1'b0); data_bit(1'b1);
bus_stop();
if (n_su_sta !== nsu_before) begin
$display("FAIL: data bits produced %0d tSU;STA measurements",
n_su_sta - nsu_before); errors++; end
if (n_viol !== viol_before) begin
$display("FAIL: data bits produced %0d violations", n_viol - viol_before);
errors++; end
end
// ---- 9: an IDLE bus measures nothing at all.
begin
nsu_before = n_su_sta;
sda_in = 1'b1; scl_in = 1'b1; tick(600);
if (n_su_sta !== nsu_before) begin
$display("FAIL: an idle bus produced measurements"); errors++; end
end
if (errors == 0)
$display("PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge independently, both boundaries exact, a cancelled interval is not measured");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule // START AND REPEATED-START MARGINS: tSU;STA and tHD;STA. Together they bracket the SDA
// falling edge that makes a START, and between them they are what keeps a repeated START
// distinguishable from a data bit at speed.
//
// SCL rises --[ tSU;STA ]--> SDA FALLS (the START) --[ tHD;STA ]--> SCL falls
// (first clock pulse)
//
// Table 10 names them:
// tHD;STA "hold time (repeated) START condition. After this period, the first clock
// pulse is generated." min 4.0 / 0.6 / 0.26 us
// tSU;STA "set-up time for a repeated START condition" min 4.7 / 0.6 / 0.26 us
//
// TWO THINGS ABOUT THE PAIR ARE WORTH NOTICING BEFORE THE CODE.
//
// First, tSU;STA is specified for a REPEATED start only, and that is not an oversight. A
// plain START begins from an idle bus, where SCL has been released high for at least tBUF
// and usually far longer -- so the setup is satisfied by the bus having been idle, and
// there is nothing for a parameter to constrain. A repeated START is the case where SCL has
// only just been released, and the margin has to be stated because it can be short.
//
// Second, these two are measured completely differently from tSU;DAT, and the reason is
// structural rather than a matter of taste. tSU;DAT's interval ENDS at its reference edge,
// so it cannot be timed by a counter armed at the start -- by the time the rising edge
// arrives the interval is over, which is why Chapter 11.3 needs a continuously running
// timer. Both of these intervals BEGIN at an edge, so a counter armed by that edge measures
// them directly. Whether a timing parameter can be measured with an armed counter is
// decided by which end of it the reference edge sits at.
//
// PASSIVE: observes the two wires and drives nothing.
// (Verilog-2001)
module i2c_start_margin_checker #(
parameter TICK_W = 16,
// Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60 ticks for both.
parameter T_SU_STA_MIN = 60,
parameter T_HD_STA_MIN = 60
)(
input wire clk,
input wire rst_n,
input wire sda_in,
input wire scl_in,
// ---- framing detected here, so the block is self-contained on a capture ----
output reg start_det, // pulse: SDA fell while SCL was high
output reg restart_det, // pulse: ... and a transfer was already open
output reg stop_det, // pulse: SDA rose while SCL was high
output reg in_transfer,
// ---- measured, reported at the events that close each interval ----
output reg su_valid, // pulse: a tSU;STA has been measured
output reg [TICK_W-1:0] t_su_sta,
output reg hd_valid, // pulse: a tHD;STA has been measured
output reg [TICK_W-1:0] t_hd_sta,
// ---- verdicts ----
output reg viol_su_sta,
output reg viol_hd_sta,
// ---- totals and worst cases ----
output reg [TICK_W-1:0] n_su_sta,
output reg [TICK_W-1:0] n_hd_sta,
output reg [TICK_W-1:0] n_viol,
output reg [TICK_W-1:0] min_su_sta_seen,
output reg [TICK_W-1:0] min_hd_sta_seen
);
reg sda_q, scl_q;
wire scl_rise, scl_fall, sda_fall, sda_rise, scl_stable_high;
assign scl_rise = !scl_q && scl_in;
assign scl_fall = scl_q && !scl_in;
assign sda_fall = sda_q && !sda_in;
assign sda_rise = !sda_q && sda_in;
// SCL high ACROSS the SDA edge, not merely after it -- the detector discipline
// Module 4's mutation suite established and Chapter 10.2 reuses.
assign scl_stable_high = scl_q && scl_in;
// tSU;STA: armed by SCL rising, stopped by the SDA fall that makes the START.
reg su_arm;
reg [TICK_W-1:0] su_ticks;
wire [TICK_W-1:0] su_now;
assign su_now = su_ticks + 1'b1;
// tHD;STA: armed by that same SDA fall, stopped by the next SCL fall.
reg hd_arm;
reg [TICK_W-1:0] hd_ticks;
wire [TICK_W-1:0] hd_now;
assign hd_now = hd_ticks + 1'b1;
always @(posedge clk) begin
if (!rst_n) begin
sda_q <= 1'b1;
scl_q <= 1'b1;
su_arm <= 1'b0;
su_ticks <= {TICK_W{1'b0}};
hd_arm <= 1'b0;
hd_ticks <= {TICK_W{1'b0}};
start_det <= 1'b0;
restart_det <= 1'b0;
stop_det <= 1'b0;
in_transfer <= 1'b0;
su_valid <= 1'b0;
t_su_sta <= {TICK_W{1'b0}};
hd_valid <= 1'b0;
t_hd_sta <= {TICK_W{1'b0}};
viol_su_sta <= 1'b0;
viol_hd_sta <= 1'b0;
n_su_sta <= {TICK_W{1'b0}};
n_hd_sta <= {TICK_W{1'b0}};
n_viol <= {TICK_W{1'b0}};
min_su_sta_seen <= {TICK_W{1'b1}};
min_hd_sta_seen <= {TICK_W{1'b1}};
end else begin
sda_q <= sda_in;
scl_q <= scl_in;
start_det <= 1'b0;
restart_det <= 1'b0;
stop_det <= 1'b0;
su_valid <= 1'b0;
hd_valid <= 1'b0;
// ---- arm the setup timer whenever SCL is released ----
if (scl_rise) begin
su_arm <= 1'b1;
su_ticks <= {TICK_W{1'b0}};
end else if (su_arm && !(sda_fall && scl_stable_high)) begin
su_ticks <= su_now;
end
// ---- framing, and the measurements the framing events close ----
if (sda_fall && scl_stable_high) begin
start_det <= 1'b1;
if (in_transfer) begin
restart_det <= 1'b1;
// tSU;STA is a REPEATED-start parameter. Measuring it on a plain START
// would report the idle time since the bus was last released, which is
// a true number and not this parameter -- and would make every first
// transfer look like it had an enormous margin, hiding the fact that
// the block never checked the case the spec cares about.
if (su_arm) begin
su_valid <= 1'b1;
t_su_sta <= su_now;
viol_su_sta <= (su_now < T_SU_STA_MIN);
n_su_sta <= n_su_sta + 1'b1;
if (su_now < T_SU_STA_MIN) n_viol <= n_viol + 1'b1;
if (su_now < min_su_sta_seen) min_su_sta_seen <= su_now;
end
end
in_transfer <= 1'b1;
su_arm <= 1'b0;
// The same edge arms the hold timer. tHD;STA runs from the START to the
// first clock pulse, so its two ends are this edge and the next SCL fall.
hd_arm <= 1'b1;
hd_ticks <= {TICK_W{1'b0}};
end else if (sda_rise && scl_stable_high) begin
stop_det <= 1'b1;
in_transfer <= 1'b0;
// A STOP cancels any hold measurement in flight: there will be no first
// clock pulse, so the interval has no end.
hd_arm <= 1'b0;
end else if (hd_arm && scl_fall) begin
// The first clock pulse after the START. This closes tHD;STA.
hd_arm <= 1'b0;
hd_valid <= 1'b1;
t_hd_sta <= hd_now;
viol_hd_sta <= (hd_now < T_HD_STA_MIN);
n_hd_sta <= n_hd_sta + 1'b1;
if (hd_now < T_HD_STA_MIN) n_viol <= n_viol + 1'b1;
if (hd_now < min_hd_sta_seen) min_hd_sta_seen <= hd_now;
end else if (hd_arm) begin
hd_ticks <= hd_now;
end
end
end
endmodule `timescale 1ns/1ps
// 100 MHz sample clock. Fast-mode tSU;STA = tHD;STA = 0.6 us = 60 ticks.
module i2c_start_margin_checker_tb; // Verilog-2001
localparam TICK_W = 16;
localparam T_SU_STA_MIN = 60;
localparam T_HD_STA_MIN = 60;
reg clk = 1'b0;
always #5 clk = ~clk;
reg rst_n = 1'b0;
reg sda_in = 1'b1, scl_in = 1'b1;
wire start_det, restart_det, stop_det, in_transfer;
wire su_valid, hd_valid, viol_su_sta, viol_hd_sta;
wire [TICK_W-1:0] t_su_sta, t_hd_sta, n_su_sta, n_hd_sta, n_viol;
wire [TICK_W-1:0] min_su_sta_seen, min_hd_sta_seen;
integer errors = 0;
integer base_su = 0, base_hd = 0;
reg [TICK_W-1:0] nsu_before, viol_before;
i2c_start_margin_checker #(.TICK_W(TICK_W), .T_SU_STA_MIN(T_SU_STA_MIN),
.T_HD_STA_MIN(T_HD_STA_MIN)) dut (
.clk(clk), .rst_n(rst_n), .sda_in(sda_in), .scl_in(scl_in), .start_det(start_det),
.restart_det(restart_det), .stop_det(stop_det), .in_transfer(in_transfer),
.su_valid(su_valid), .t_su_sta(t_su_sta), .hd_valid(hd_valid), .t_hd_sta(t_hd_sta),
.viol_su_sta(viol_su_sta), .viol_hd_sta(viol_hd_sta), .n_su_sta(n_su_sta),
.n_hd_sta(n_hd_sta), .n_viol(n_viol), .min_su_sta_seen(min_su_sta_seen),
.min_hd_sta_seen(min_hd_sta_seen));
initial begin #2000000; $display("FAIL: watchdog expired"); $finish; end
reg [TICK_W-1:0] su_log [0:31];
reg vsu_log [0:31];
integer n_sulog = 0;
always @(posedge clk) if (rst_n && su_valid && n_sulog < 32) begin
su_log[n_sulog] = t_su_sta; vsu_log[n_sulog] = viol_su_sta; n_sulog = n_sulog + 1;
end
reg [TICK_W-1:0] hd_log [0:31];
reg vhd_log [0:31];
integer n_hdlog = 0;
always @(posedge clk) if (rst_n && hd_valid && n_hdlog < 32) begin
hd_log[n_hdlog] = t_hd_sta; vhd_log[n_hdlog] = viol_hd_sta; n_hdlog = n_hdlog + 1;
end
task tick;
input integer n;
begin repeat (n) @(negedge clk); end
endtask
// A plain START from an idle bus: SDA falls while SCL is high, then SCL falls after
// `hd` ticks. tSU;STA does not apply here -- see the design header.
task plain_start;
input integer hd;
begin
sda_in = 1'b1; scl_in = 1'b1; tick(40);
sda_in = 1'b0; tick(hd);
scl_in = 1'b0; tick(20);
end
endtask
// A REPEATED START, with both margins settable. SCL is released, `su` ticks pass, SDA
// falls, `hd` ticks pass, SCL falls to give the first clock pulse.
task repeated_start;
input integer su;
input integer hd;
begin
sda_in = 1'b1; scl_in = 1'b0; tick(20); // release SDA while SCL is low
scl_in = 1'b1; tick(su); // release SCL: tSU;STA starts here
sda_in = 1'b0; tick(hd); // the Sr: tHD;STA starts here
scl_in = 1'b0; tick(20); // the first clock pulse
end
endtask
// One ordinary data bit, so the bus does something between framing events.
task data_bit;
input v;
begin
scl_in = 1'b0; sda_in = v; tick(30);
scl_in = 1'b1; tick(80);
scl_in = 1'b0; tick(30);
end
endtask
task bus_stop;
begin
sda_in = 1'b0; scl_in = 1'b0; tick(20);
scl_in = 1'b1; tick(60);
sda_in = 1'b1; tick(40);
end
endtask
initial begin
tick(3);
if (min_su_sta_seen !== {TICK_W{1'b1}} || min_hd_sta_seen !== {TICK_W{1'b1}}) begin
$display("FAIL: worst-case trackers did not start at their maximum"); errors = errors + 1; end
rst_n = 1'b1; tick(2);
// ---- 1: a PLAIN START with a generous hold. tHD;STA is measured and passes;
// tSU;STA must NOT be measured at all, because it is a repeated-start
// parameter and a plain START begins from an idle bus.
plain_start(100);
data_bit(1'b1);
if (n_hdlog !== 1) begin
$display("FAIL: %0d tHD;STA measurements from one plain START, expected 1",
n_hdlog); errors = errors + 1; end
if (n_su_sta !== {TICK_W{1'b0}}) begin
$display("FAIL: a plain START produced %0d tSU;STA measurements -- it is a repeated-start parameter",
n_su_sta); errors = errors + 1; end
if (vhd_log[0] !== 1'b0) begin
$display("FAIL: a 100-tick tHD;STA was flagged (min %0d)", T_HD_STA_MIN);
errors = errors + 1; end
if (hd_log[0] < 16'd95 || hd_log[0] > 16'd105) begin
$display("FAIL: a 100-tick tHD;STA measured %0d", hd_log[0]); errors = errors + 1; end
// ---- 2: a REPEATED START with both margins generous. Now BOTH are measured.
begin
base_su = n_sulog; base_hd = n_hdlog;
repeated_start(120, 120);
data_bit(1'b1);
if (n_sulog !== base_su + 1) begin
$display("FAIL: a repeated START produced no tSU;STA measurement"); errors = errors + 1; end
if (vsu_log[base_su] !== 1'b0 || vhd_log[base_hd] !== 1'b0) begin
$display("FAIL: generous margins were flagged -- su=%b hd=%b",
vsu_log[base_su], vhd_log[base_hd]); errors = errors + 1; end
if (su_log[base_su] < 16'd115 || su_log[base_su] > 16'd125) begin
$display("FAIL: a 120-tick tSU;STA measured %0d", su_log[base_su]); errors = errors + 1; end
end
bus_stop;
// ---- 3: tSU;STA too SHORT, hold fine. Only the setup verdict may fire. This is
// the case that matters at speed: SCL is released and SDA pulled low almost
// immediately, so a receiver cannot tell the START from a data bit.
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(15, 120);
data_bit(1'b1);
if (vsu_log[base_su] !== 1'b1) begin
$display("FAIL: a 15-tick tSU;STA was not flagged (min %0d)", T_SU_STA_MIN);
errors = errors + 1; end
if (vhd_log[base_hd + 1] !== 1'b0) begin
$display("FAIL: a short tSU;STA also flagged tHD;STA"); errors = errors + 1; end
end
bus_stop;
// ---- 4: tHD;STA too SHORT, setup fine. The mirror, and the one that proves the
// two intervals are measured against different pairs of edges.
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(120, 20);
data_bit(1'b1);
if (vhd_log[base_hd + 1] !== 1'b1) begin
$display("FAIL: a 20-tick tHD;STA was not flagged"); errors = errors + 1; end
if (vsu_log[base_su] !== 1'b0) begin
$display("FAIL: a short tHD;STA also flagged tSU;STA"); errors = errors + 1; end
end
bus_stop;
// ---- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(T_SU_STA_MIN, T_HD_STA_MIN);
data_bit(1'b1);
if (vsu_log[base_su] !== 1'b0) begin
$display("FAIL: tSU;STA exactly at the minimum was rejected"); errors = errors + 1; end
if (vhd_log[base_hd + 1] !== 1'b0) begin
$display("FAIL: tHD;STA exactly at the minimum was rejected"); errors = errors + 1; end
end
bus_stop;
begin
base_su = n_sulog; base_hd = n_hdlog;
plain_start(100);
data_bit(1'b1);
repeated_start(T_SU_STA_MIN - 1, T_HD_STA_MIN - 1);
data_bit(1'b1);
if (vsu_log[base_su] !== 1'b1) begin
$display("FAIL: tSU;STA one tick below the minimum was accepted"); errors = errors + 1; end
if (vhd_log[base_hd + 1] !== 1'b1) begin
$display("FAIL: tHD;STA one tick below the minimum was accepted"); errors = errors + 1; end
end
bus_stop;
// ---- 6: a STOP cancels a hold measurement in flight. After a START with no clock
// pulse and then a STOP, tHD;STA has no end -- the first clock pulse never
// came -- so no measurement may be reported. A block that reported one would
// be timing an interval that did not happen.
begin
base_hd = n_hdlog;
sda_in = 1'b1; scl_in = 1'b1; tick(40);
sda_in = 1'b0; tick(40); // a START ...
sda_in = 1'b1; tick(40); // ... then straight to a STOP
if (n_hdlog !== base_hd) begin
$display("FAIL: a START followed by a STOP produced a tHD;STA measurement");
errors = errors + 1; end
// And it must STAY cancelled. A clock pulse after the STOP must not close the
// abandoned interval -- if the arm survived the STOP, this falling edge would
// report a tHD;STA spanning the STOP itself, which is not an interval at all.
scl_in = 1'b1; tick(30);
scl_in = 1'b0; tick(30);
if (n_hdlog !== base_hd) begin
$display("FAIL: a clock pulse after the STOP closed the abandoned tHD;STA interval");
errors = errors + 1; end
end
// ---- 7: the worst cases are TRACKED. A clean run after the violations of tests
// 3 to 5 must not erase them.
begin
repeat (3) begin
plain_start(300);
data_bit(1'b1);
repeated_start(300, 300);
data_bit(1'b1);
bus_stop;
end
if (min_su_sta_seen > 16'd59) begin
$display("FAIL: min_su_sta_seen = %0d after short margins were seen",
min_su_sta_seen); errors = errors + 1; end
if (min_hd_sta_seen > 16'd59) begin
$display("FAIL: min_hd_sta_seen = %0d after short margins were seen",
min_hd_sta_seen); errors = errors + 1; end
end
// ---- 8: an SDA edge while SCL is LOW is DATA, not framing, so it must produce
// neither a framing event nor a margin measurement. This is the check that
// stops the block from inventing STARTs inside a byte.
begin
nsu_before = n_su_sta; viol_before = n_viol;
plain_start(200);
data_bit(1'b0); data_bit(1'b1); data_bit(1'b0); data_bit(1'b1);
bus_stop;
if (n_su_sta !== nsu_before) begin
$display("FAIL: data bits produced %0d tSU;STA measurements",
n_su_sta - nsu_before); errors = errors + 1; end
if (n_viol !== viol_before) begin
$display("FAIL: data bits produced %0d violations", n_viol - viol_before);
errors = errors + 1; end
end
// ---- 9: an IDLE bus measures nothing at all.
begin
nsu_before = n_su_sta;
sda_in = 1'b1; scl_in = 1'b1; tick(600);
if (n_su_sta !== nsu_before) begin
$display("FAIL: an idle bus produced measurements"); errors = errors + 1; end
end
if (errors == 0)
$display("PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge independently, both boundaries exact, a cancelled interval is not measured");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- START AND REPEATED-START MARGINS: tSU;STA and tHD;STA. Together they bracket the SDA falling
-- edge that makes a START, and between them they keep a repeated START distinguishable from a
-- data bit at speed.
--
-- SCL rises --[ tSU;STA ]--> SDA FALLS (the START) --[ tHD;STA ]--> SCL falls
-- (first clock pulse)
--
-- Table 10 names them:
-- tHD;STA "hold time (repeated) START condition. After this period, the first clock pulse
-- is generated." min 4.0 / 0.6 / 0.26 us
-- tSU;STA "set-up time for a repeated START condition" min 4.7 / 0.6 / 0.26 us
--
-- TWO THINGS ABOUT THE PAIR ARE WORTH NOTICING.
--
-- First, tSU;STA is specified for a REPEATED start only, and that is not an oversight. A plain
-- START begins from an idle bus, where SCL has been released high for at least tBUF and usually
-- far longer -- so the setup is satisfied by the bus having been idle, and there is nothing for
-- a parameter to constrain. A repeated START is the case where SCL has only just been released.
--
-- Second, these two are measured completely differently from tSU;DAT, for a structural reason.
-- tSU;DAT's interval ENDS at its reference edge, so it cannot be timed by a counter armed at
-- the start -- by the time the rising edge arrives the interval is over, which is why Chapter
-- 11.3 needs a continuously running timer. Both of these intervals BEGIN at an edge, so a
-- counter armed by that edge measures them directly. Whether a timing parameter can be
-- measured with an armed counter is decided by which end of it the reference edge sits at.
entity i2c_start_margin_checker is
generic (
TICK_W : positive := 16;
-- Fast-mode, in ticks of a 100 MHz sample clock: 0.6 us = 60 ticks for both.
T_SU_STA_MIN : natural := 60;
T_HD_STA_MIN : natural := 60
);
port (
clk : in std_logic;
rst_n : in std_logic;
sda_in : in std_logic;
scl_in : in std_logic;
start_det : out std_logic;
restart_det : out std_logic;
stop_det : out std_logic;
in_transfer : out std_logic;
su_valid : out std_logic;
t_su_sta : out unsigned(TICK_W - 1 downto 0);
hd_valid : out std_logic;
t_hd_sta : out unsigned(TICK_W - 1 downto 0);
viol_su_sta : out std_logic;
viol_hd_sta : out std_logic;
n_su_sta : out unsigned(TICK_W - 1 downto 0);
n_hd_sta : out unsigned(TICK_W - 1 downto 0);
n_viol : out unsigned(TICK_W - 1 downto 0);
min_su_sta_seen : out unsigned(TICK_W - 1 downto 0);
min_hd_sta_seen : out unsigned(TICK_W - 1 downto 0)
);
end entity;
architecture rtl of i2c_start_margin_checker is
signal sda_q, scl_q : std_logic := '1';
signal scl_rise, scl_fall, sda_fall, sda_rise, scl_stable_high : std_logic;
-- tSU;STA: armed by SCL rising, stopped by the SDA fall that makes the START.
signal su_arm : std_logic := '0';
signal su_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
signal su_now : unsigned(TICK_W - 1 downto 0);
-- tHD;STA: armed by that same SDA fall, stopped by the next SCL fall.
signal hd_arm : std_logic := '0';
signal hd_ticks : unsigned(TICK_W - 1 downto 0) := (others => '0');
signal hd_now : unsigned(TICK_W - 1 downto 0);
signal xfer : std_logic := '0';
signal is_start_edge : std_logic;
begin
scl_rise <= (not scl_q) and scl_in;
scl_fall <= scl_q and (not scl_in);
sda_fall <= sda_q and (not sda_in);
sda_rise <= (not sda_q) and sda_in;
-- SCL high ACROSS the SDA edge, not merely after it -- the detector discipline Module 4's
-- mutation suite established and Chapter 10.2 reuses.
scl_stable_high <= scl_q and scl_in;
is_start_edge <= sda_fall and scl_stable_high;
su_now <= su_ticks + 1;
hd_now <= hd_ticks + 1;
in_transfer <= xfer;
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
sda_q <= '1';
scl_q <= '1';
su_arm <= '0';
su_ticks <= (others => '0');
hd_arm <= '0';
hd_ticks <= (others => '0');
start_det <= '0';
restart_det <= '0';
stop_det <= '0';
xfer <= '0';
su_valid <= '0';
t_su_sta <= (others => '0');
hd_valid <= '0';
t_hd_sta <= (others => '0');
viol_su_sta <= '0';
viol_hd_sta <= '0';
n_su_sta <= (others => '0');
n_hd_sta <= (others => '0');
n_viol <= (others => '0');
min_su_sta_seen <= (others => '1');
min_hd_sta_seen <= (others => '1');
else
sda_q <= sda_in;
scl_q <= scl_in;
start_det <= '0';
restart_det <= '0';
stop_det <= '0';
su_valid <= '0';
hd_valid <= '0';
-- arm the setup timer whenever SCL is released
if scl_rise = '1' then
su_arm <= '1';
su_ticks <= (others => '0');
elsif su_arm = '1' and is_start_edge = '0' then
su_ticks <= su_now;
end if;
if is_start_edge = '1' then
start_det <= '1';
if xfer = '1' then
restart_det <= '1';
-- tSU;STA is a REPEATED-start parameter. Measuring it on a plain START
-- would report the idle time since the bus was last released, which is
-- a true number and not this parameter -- and would make every first
-- transfer look like it had an enormous margin, hiding the fact that
-- the block never checked the case the spec cares about.
if su_arm = '1' then
su_valid <= '1';
t_su_sta <= su_now;
if su_now < to_unsigned(T_SU_STA_MIN, TICK_W) then
viol_su_sta <= '1';
n_viol <= n_viol + 1;
else
viol_su_sta <= '0';
end if;
n_su_sta <= n_su_sta + 1;
if su_now < min_su_sta_seen then min_su_sta_seen <= su_now; end if;
end if;
end if;
xfer <= '1';
su_arm <= '0';
-- The same edge arms the hold timer: tHD;STA runs from the START to the
-- first clock pulse, so its two ends are this edge and the next SCL fall.
hd_arm <= '1';
hd_ticks <= (others => '0');
elsif sda_rise = '1' and scl_stable_high = '1' then
stop_det <= '1';
xfer <= '0';
-- A STOP cancels any hold measurement in flight: there will be no first
-- clock pulse, so the interval has no end.
hd_arm <= '0';
elsif hd_arm = '1' and scl_fall = '1' then
-- The first clock pulse after the START. This closes tHD;STA.
hd_arm <= '0';
hd_valid <= '1';
t_hd_sta <= hd_now;
if hd_now < to_unsigned(T_HD_STA_MIN, TICK_W) then
viol_hd_sta <= '1';
n_viol <= n_viol + 1;
else
viol_hd_sta <= '0';
end if;
n_hd_sta <= n_hd_sta + 1;
if hd_now < min_hd_sta_seen then min_hd_sta_seen <= hd_now; end if;
elsif hd_arm = '1' then
hd_ticks <= hd_now;
end if;
end if;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- 100 MHz sample clock. Fast-mode tSU;STA = tHD;STA = 0.6 us = 60 ticks.
entity i2c_start_margin_checker_tb is
end entity;
architecture sim of i2c_start_margin_checker_tb is
constant TICK_W : positive := 16;
constant T_SU_STA_MIN : natural := 60;
constant T_HD_STA_MIN : natural := 60;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal sda_in, scl_in : std_logic := '1';
signal start_det, restart_det, stop_det, in_transfer : std_logic;
signal su_valid, hd_valid, viol_su_sta, viol_hd_sta : std_logic;
signal t_su_sta, t_hd_sta : unsigned(TICK_W - 1 downto 0);
signal n_su_sta, n_hd_sta, n_viol : unsigned(TICK_W - 1 downto 0);
signal min_su_sta_seen, min_hd_sta_seen : unsigned(TICK_W - 1 downto 0);
type tick_arr is array (0 to 31) of unsigned(TICK_W - 1 downto 0);
type bit_arr is array (0 to 31) of std_logic;
signal su_log, hd_log : tick_arr := (others => (others => '0'));
signal vsu_log, vhd_log : bit_arr := (others => '0');
signal n_sulog, n_hdlog : natural := 0;
signal test_done : std_logic := '0';
begin
dut : entity work.i2c_start_margin_checker
generic map (TICK_W => TICK_W, T_SU_STA_MIN => T_SU_STA_MIN,
T_HD_STA_MIN => T_HD_STA_MIN)
port map (clk => clk, rst_n => rst_n, sda_in => sda_in, scl_in => scl_in,
start_det => start_det, restart_det => restart_det, stop_det => stop_det,
in_transfer => in_transfer, su_valid => su_valid, t_su_sta => t_su_sta,
hd_valid => hd_valid, t_hd_sta => t_hd_sta, viol_su_sta => viol_su_sta,
viol_hd_sta => viol_hd_sta, n_su_sta => n_su_sta, n_hd_sta => n_hd_sta,
n_viol => n_viol, min_su_sta_seen => min_su_sta_seen,
min_hd_sta_seen => min_hd_sta_seen);
clk <= not clk after 5 ns;
watchdog : process
begin
wait for 2 ms;
if test_done = '0' then report "watchdog expired" severity failure; end if;
wait;
end process;
obs_su : process (clk)
begin
if rising_edge(clk) and rst_n = '1' and su_valid = '1' and n_sulog < 32 then
su_log(n_sulog) <= t_su_sta; vsu_log(n_sulog) <= viol_su_sta;
n_sulog <= n_sulog + 1;
end if;
end process;
obs_hd : process (clk)
begin
if rising_edge(clk) and rst_n = '1' and hd_valid = '1' and n_hdlog < 32 then
hd_log(n_hdlog) <= t_hd_sta; vhd_log(n_hdlog) <= viol_hd_sta;
n_hdlog <= n_hdlog + 1;
end if;
end process;
stim : process
variable errs : natural := 0;
variable base_su, base_hd : natural;
variable nsu_before, viol_before : unsigned(TICK_W - 1 downto 0);
procedure tick (n : in positive) is
begin
for i in 1 to n loop wait until falling_edge(clk); end loop;
end procedure;
-- A plain START from an idle bus: SDA falls while SCL is high, then SCL falls after
-- `hd` ticks. tSU;STA does not apply here -- see the design header.
procedure plain_start (hd : in positive) is
begin
sda_in <= '1'; scl_in <= '1'; tick(40);
sda_in <= '0'; tick(hd);
scl_in <= '0'; tick(20);
end procedure;
-- A REPEATED START, with both margins settable.
procedure repeated_start (su, hd : in positive) is
begin
sda_in <= '1'; scl_in <= '0'; tick(20); -- release SDA while SCL is low
scl_in <= '1'; tick(su); -- release SCL: tSU;STA starts here
sda_in <= '0'; tick(hd); -- the Sr: tHD;STA starts here
scl_in <= '0'; tick(20); -- the first clock pulse
end procedure;
procedure data_bit (v : in std_logic) is
begin
scl_in <= '0'; sda_in <= v; tick(30);
scl_in <= '1'; tick(80);
scl_in <= '0'; tick(30);
end procedure;
procedure bus_stop is
begin
sda_in <= '0'; scl_in <= '0'; tick(20);
scl_in <= '1'; tick(60);
sda_in <= '1'; tick(40);
end procedure;
begin
tick(3);
if min_su_sta_seen /= (min_su_sta_seen'range => '1')
or min_hd_sta_seen /= (min_hd_sta_seen'range => '1') then
report "worst-case trackers did not start at their maximum" severity error;
errs := errs + 1; end if;
rst_n <= '1'; tick(2);
-- 1: a PLAIN START with a generous hold. tHD;STA is measured and passes; tSU;STA must
-- NOT be measured at all, because it is a repeated-start parameter.
plain_start(100);
data_bit('1');
if n_hdlog /= 1 then
report "wrong number of tHD;STA measurements from one plain START" severity error;
errs := errs + 1; end if;
if n_su_sta /= to_unsigned(0, TICK_W) then
report "a plain START produced tSU;STA measurements -- it is a repeated-start parameter"
severity error; errs := errs + 1; end if;
if vhd_log(0) /= '0' then
report "a 100-tick tHD;STA was flagged" severity error; errs := errs + 1; end if;
if hd_log(0) < to_unsigned(95, TICK_W) or hd_log(0) > to_unsigned(105, TICK_W) then
report "a 100-tick tHD;STA measured out of range" severity error;
errs := errs + 1; end if;
-- 2: a REPEATED START with both margins generous. Now BOTH are measured.
base_su := n_sulog; base_hd := n_hdlog;
repeated_start(120, 120);
data_bit('1');
if n_sulog /= base_su + 1 then
report "a repeated START produced no tSU;STA measurement" severity error;
errs := errs + 1; end if;
if vsu_log(base_su) /= '0' or vhd_log(base_hd) /= '0' then
report "generous margins were flagged" severity error; errs := errs + 1; end if;
if su_log(base_su) < to_unsigned(115, TICK_W)
or su_log(base_su) > to_unsigned(125, TICK_W) then
report "a 120-tick tSU;STA measured out of range" severity error;
errs := errs + 1; end if;
bus_stop;
-- 3: tSU;STA too SHORT, hold fine. Only the setup verdict may fire. This is the case
-- that matters at speed: SCL released and SDA pulled low almost immediately, so a
-- receiver cannot tell the START from a data bit.
base_su := n_sulog; base_hd := n_hdlog;
plain_start(100);
data_bit('1');
repeated_start(15, 120);
data_bit('1');
if vsu_log(base_su) /= '1' then
report "a 15-tick tSU;STA was not flagged" severity error; errs := errs + 1; end if;
if vhd_log(base_hd + 1) /= '0' then
report "a short tSU;STA also flagged tHD;STA" severity error; errs := errs + 1; end if;
bus_stop;
-- 4: tHD;STA too SHORT, setup fine. The mirror, and the one that proves the two
-- intervals are measured against different pairs of edges.
base_su := n_sulog; base_hd := n_hdlog;
plain_start(100);
data_bit('1');
repeated_start(120, 20);
data_bit('1');
if vhd_log(base_hd + 1) /= '1' then
report "a 20-tick tHD;STA was not flagged" severity error; errs := errs + 1; end if;
if vsu_log(base_su) /= '0' then
report "a short tHD;STA also flagged tSU;STA" severity error; errs := errs + 1; end if;
bus_stop;
-- 5: BOUNDARIES, both parameters. Exactly at the minimum is legal.
base_su := n_sulog; base_hd := n_hdlog;
plain_start(100);
data_bit('1');
repeated_start(T_SU_STA_MIN, T_HD_STA_MIN);
data_bit('1');
if vsu_log(base_su) /= '0' then
report "tSU;STA exactly at the minimum was rejected" severity error;
errs := errs + 1; end if;
if vhd_log(base_hd + 1) /= '0' then
report "tHD;STA exactly at the minimum was rejected" severity error;
errs := errs + 1; end if;
bus_stop;
base_su := n_sulog; base_hd := n_hdlog;
plain_start(100);
data_bit('1');
repeated_start(T_SU_STA_MIN - 1, T_HD_STA_MIN - 1);
data_bit('1');
if vsu_log(base_su) /= '1' then
report "tSU;STA one tick below the minimum was accepted" severity error;
errs := errs + 1; end if;
if vhd_log(base_hd + 1) /= '1' then
report "tHD;STA one tick below the minimum was accepted" severity error;
errs := errs + 1; end if;
bus_stop;
-- 6: a STOP cancels a hold measurement in flight. After a START with no clock pulse and
-- then a STOP, tHD;STA has no end -- the first clock pulse never came -- so no
-- measurement may be reported. A block reporting one would be timing an interval that
-- did not happen.
base_hd := n_hdlog;
sda_in <= '1'; scl_in <= '1'; tick(40);
sda_in <= '0'; tick(40); -- a START ...
sda_in <= '1'; tick(40); -- ... then straight to a STOP
if n_hdlog /= base_hd then
report "a START followed by a STOP produced a tHD;STA measurement" severity error;
errs := errs + 1; end if;
-- And it must STAY cancelled. A clock pulse after the STOP must not close the abandoned
-- interval -- if the arm survived the STOP, this falling edge would report a tHD;STA
-- spanning the STOP itself, which is not an interval at all.
scl_in <= '1'; tick(30);
scl_in <= '0'; tick(30);
if n_hdlog /= base_hd then
report "a clock pulse after the STOP closed the abandoned tHD;STA interval"
severity error; errs := errs + 1; end if;
-- 7: the worst cases are TRACKED. A clean run must not erase the earlier violations.
for i in 1 to 3 loop
plain_start(300);
data_bit('1');
repeated_start(300, 300);
data_bit('1');
bus_stop;
end loop;
if min_su_sta_seen > to_unsigned(59, TICK_W) then
report "the tSU;STA worst case was erased" severity error; errs := errs + 1; end if;
if min_hd_sta_seen > to_unsigned(59, TICK_W) then
report "the tHD;STA worst case was erased" severity error; errs := errs + 1; end if;
-- 8: an SDA edge while SCL is LOW is DATA, not framing, so it must produce neither a
-- framing event nor a margin measurement. This stops the block inventing STARTs inside
-- a byte.
nsu_before := n_su_sta; viol_before := n_viol;
plain_start(200);
data_bit('0'); data_bit('1'); data_bit('0'); data_bit('1');
bus_stop;
if n_su_sta /= nsu_before then
report "data bits produced tSU;STA measurements" severity error;
errs := errs + 1; end if;
if n_viol /= viol_before then
report "data bits produced violations" severity error; errs := errs + 1; end if;
-- 9: an IDLE bus measures nothing at all.
nsu_before := n_su_sta;
sda_in <= '1'; scl_in <= '1'; tick(600);
if n_su_sta /= nsu_before then
report "an idle bus produced measurements" severity error; errs := errs + 1; end if;
if errs = 0 then
report "i2c_start_margin_checker self-check complete: tSU;STA is a repeated-start "
& "parameter only, the two margins bracket the SDA edge independently, both "
& "boundaries exact, a cancelled interval is not measured" severity note;
else
report "i2c_start_margin_checker self-check FAILED" severity error;
end if;
test_done <= '1';
wait;
end process;
end architecture;5a. Five Decisions Worth Defending
The setup timer is free-running from SCL's rise; the hold timer is armed by SDA's fall. The reference-edge rule of Chapter 11.3 §2, applied to a pair whose reference edges are on different signals. The setup interval ends at SDA's fall, so it cannot be timed by a counter armed there; the hold interval begins at that same fall, so it can.
One event closes one measurement and opens the other. SDA's fall is the shared endpoint of §3, so the cycle it occurs must both sample the setup counter and arm the hold counter. Doing them in separate cycles loses a tick from one and adds one to the other — invisible except at the boundary, which is why §6's tests 8 and 9 assert the exact minimum and one tick below it.
A START is recognised by its definition, not assumed. SDA must fall while SCL is high. An SDA fall during a low phase is a data bit and must produce no margin measurement at all — §6's test 12, and mutation E4 removes the SCL-high qualification.
tSU;STA is measured only for a REPEATED START. Table 10's row says "repeated START condition" and the design honours it: start_det, restart_det and in_transfer are separate outputs, and a plain START produces a hold measurement with no setup measurement. §2 is the reasoning — a first START's entry requirement is tBUF — and mutation E1 is the version that measures both.
The hold measurement is closed by SCL's fall, and it is cancelled if SDA rises first. An SDA rise while SCL is still high, after a START, is a STOP (Chapter 5.3) — a legal event meaning the transfer was abandoned before its first clock pulse, so no tHD;STA exists to report. The cancellation must also survive what comes next: a clock pulse arriving later must not close the interval retrospectively. §6's test 10 asserts both halves, and §7's E3 is why the second half is needed.
5b. Verified Execution
$ iverilog -g2012 -o d5 i2c_start_margin_checker.sv i2c_start_margin_checker_tb.sv && ./d5
PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge
independently, both boundaries exact, a cancelled interval is not measured
i2c_start_margin_checker_tb.sv:247: $finish called at 96780000 (1ps)
$ iverilog -g2005 -o v5 i2c_start_margin_checker.v i2c_start_margin_checker_tb.v && ./v5
PASS: tSU;STA is a repeated-start parameter only, the two margins bracket the SDA edge
independently, both boundaries exact, a cancelled interval is not measured
i2c_start_margin_checker_tb.v:258: $finish called at 96780000 (1ps)
$ nvc -a i2c_start_margin_checker.vhd i2c_start_margin_checker_tb.vhd
$ nvc -e i2c_start_margin_checker_tb && nvc -r i2c_start_margin_checker_tb --stop-time=2000us
** Note: 96780ns+0: i2c_start_margin_checker self-check complete: tSU;STA is a
repeated-start parameter only, the two margins bracket the SDA edge independently, both
boundaries exact, a cancelled interval is not measuredAll three at 96780 ns.
6. What the Testbench Proves
The block is configured with Fast-mode's values, where the two minima are equal: T_SU_STA_MIN = T_HD_STA_MIN = 60.
| # | stimulus | what it establishes |
|---|---|---|
| 1 | reset | both worst-case trackers read their maximum |
| 2 | a plain START | produces one tHD;STA and zero tSU;STA — §2's asymmetry |
| 3 | a 100-tick hold on that plain START | not flagged, and measured as 100 |
| 4 | a repeated START | does produce a tSU;STA measurement |
| 5 | generous margins on a repeated START | neither verdict fires; the setup measures 120 |
| 6 | a 15-tick setup | violates tSU;STA, and does not flag the hold |
| 7 | a 20-tick hold | violates tHD;STA, and does not flag the setup |
| 8 | each margin exactly at the minimum | accepted |
| 9 | each margin one tick below | rejected — both boundaries pinned from both sides |
| 10 | a START followed by a STOP | produces no tHD;STA, and a later clock pulse does not close the abandoned interval |
| 11 | a clean START after short margins | worst cases not erased |
| 12 | ordinary data bits | produce no tSU;STA measurements and no violations |
| 13 | an idle bus | produces no measurements |
Test 2 and test 4 are the pair that carries §2, and they are the most important tests here. Table 10 calls tSU;STA the set-up time for a repeated START condition, and the design honours that literally: a plain START — one that opens a transfer on a free bus — produces a hold measurement and no setup measurement at all, because the interval before it is tBUF, not tSU;STA. A checker that measured a setup margin for a plain START would be reporting how long the bus happened to be idle, dressed up as a compliance number.
Test 10 is the abandoned START, and it needs two assertions rather than one. An SDA rise while SCL is still high, after a START, is a STOP — the transfer was given up before its first clock pulse, so tHD;STA's closing edge never arrives and there is nothing to report. The second assertion is the one that matters: a clock pulse arriving later must not close the interval retrospectively. Mutation E3 is exactly that, and the first assertion alone does not catch it.
Test 12 is the definition check. Every data bit on the bus involves SDA moving while SCL is low, so a detector missing the SCL-high qualification would announce framing constantly. It is cheap to test and it is the failure that makes a checker useless rather than merely wrong.
7. Mutation Testing
Five defects injected into the SystemVerilog checker.
| # | injected defect | outcome |
|---|---|---|
| E1 | tSU;STA is measured on a plain START too, reporting idle time | killed — test 2 |
| E2 | tHD;STA is closed by the next SCL rise rather than the fall | killed — test 3 |
| E3 | a STOP does not cancel a hold measurement in flight | killed — test 10 |
| E4 | framing is detected without requiring SCL high across the edge | killed — test 4 |
| E5 | the setup timer is not armed by SCL being released | killed — test 4 |
Five injected, five killed. Three notes.
E1 is the mutation this chapter's §2 exists for. Measuring tSU;STA on a plain START yields a number — the time since SCL last rose on an idle bus — and that number is usually large, so it passes the minimum and looks like a clean result. Nothing about it is wrong except that the parameter does not apply. It is caught only by a test that asserts a plain START produces zero setup measurements, which is a test you write only if you have read the word "repeated" in the table row.
E2 is the reference-edge mutation, and it inflates rather than deflates. Closing the hold interval at the next rising edge instead of the falling edge measures the hold plus the whole rest of the high phase, so a 100-tick hold reports 150. That direction matters: the mutant never produces a false violation, only false passes, so a suite checking violations alone would miss it entirely. What kills it is test 3 asserting the measured value, not the verdict.
That generalises past this chapter. A checker's measurements must be asserted, not only its verdicts — a mutation that shifts a measurement in the safe direction is invisible to any test that reads only the pass/fail bit.
E3 was a real gap, and it took the second assertion in test 10. The original test checked that an abandoned START produced no hold measurement, and the mutant passed it: at the moment of the STOP, nothing is reported either way. The defect shows up only when a clock pulse arrives afterwards and closes the interval that should have been cancelled. This is the same deferred-effect shape as Chapter 11.3 §8's C2 — when a mutation removes a cancellation, the test has to run past it to the event that would wrongly consume the stale state.
8. Verification Connection — Properties Between Two Signals
// A START is a definition before it is a timing question, so the property begins with the
// definition. Every framing property in this module has this shape: recognise the event by
// its transition-while-level signature, then constrain the intervals around it.
wire start_event = $fell(sda) && scl;
// SETUP: from SCL's rise to SDA's fall. The interval ENDS at the SDA edge, so -- exactly as
// in Chapter 11.3 -- it needs a free-running timer sampled there, not a timer armed there.
int since_scl_rise;
always_ff @(posedge clk)
since_scl_rise <= $rose(scl) ? 0 : since_scl_rise + 1;
property p_su_sta;
@(posedge clk) start_event |-> (since_scl_rise >= T_SU_STA_MIN);
endproperty
assert property (p_su_sta)
else $error("tSU;STA violated: %0d ticks since SCL rose, minimum %0d",
since_scl_rise, T_SU_STA_MIN);
// HOLD: from SDA's fall to SCL's fall. The interval BEGINS at the SDA edge, so a bounded
// window works -- and the `or $rose(sda)` disjunct is section 5a's abandoned-START case,
// which is legal and must not fail the property.
property p_hd_sta;
@(posedge clk) start_event |-> (scl [*T_HD_STA_MIN]) or ($rose(sda) throughout scl [*1:$]);
endproperty
assert property (p_hd_sta)
else $error("tHD;STA violated: SCL fell within %0d ticks of the START", T_HD_STA_MIN);
// The NEGATIVE property, and for this chapter it is the important one: an SDA fall during a
// LOW phase is a data bit, and reporting a START for it would flag every zero on the bus.
// Section 6's test 12 is the simulation form of this; mutation E4 is what it catches.
property p_data_fall_is_not_a_start;
@(posedge clk) ($fell(sda) && !scl) |-> (!viol_su_sta && !viol_hd_sta);
endproperty
assert property (p_data_fall_is_not_a_start)
else $error("an SDA fall during a LOW phase was treated as a START"); covergroup i2c_start_cg with function sample(int su, int hd, int su_min, int hd_min,
int mode, bit repeated);
// Margins relative to their own minima, so the bins are reusable across speed grades --
// the same reasoning as Chapter 11.1 section 9.
su_margin: coverpoint (su - su_min) {
bins violation = {[$:-1]}; bins exact = {0};
bins tight = {[1:10]}; bins ample = {[11:$]};
}
hd_margin: coverpoint (hd - hd_min) {
bins violation = {[$:-1]}; bins exact = {0};
bins tight = {[1:10]}; bins ample = {[11:$]};
}
// THE coverpoint for this chapter. tSU;STA and tHD;STA are equal in Fast and Fast-mode
// Plus and differ only in Standard-mode, so a suite that never ran Standard-mode cannot
// have distinguished them -- and this cross is what makes that gap visible in a report
// instead of invisible in a pass.
speed: coverpoint mode {
bins standard = {0}; // the ONLY mode where the two minima differ
bins fast = {1};
bins fm_plus = {2};
}
su_x_speed: cross su_margin, speed;
hd_x_speed: cross hd_margin, speed;
// First versus repeated START, because their PRECEDING requirement is a different
// parameter -- tBUF against tSU;STA, section 2 -- even though the hold is identical.
kind: coverpoint repeated { bins first = {0}; bins repeated_start = {1}; }
su_x_kind: cross su_margin, kind;
// And the two events that must produce NO measurement, each reached by its own path.
data_phase_fall: coverpoint (su == 0 && hd == 0);
abandoned: coverpoint (su > 0 && hd == 0);
endgroup9. FPGA and ASIC Implications
Two counters and a small amount of edge-detection logic — around 60 flops at TICK_W = 16. The only subtlety is that the shared endpoint of §3 makes one cycle do two things, which is a fan-out concern rather than a timing one.
On the generating side, a repeated START costs SCL high time. §3's arithmetic: tSU;STA + tHD;STA is 1.2 µs in Fast-mode against a 0.6 µs minimum high phase, so the high phase containing a repeated START is at least twice the normal minimum. A master's state machine therefore cannot treat a repeated START as "a START issued during the transfer" — it has to lengthen the high phase, which means the SCL generator needs a mode where its high-phase terminal count is overridden.
That is a real and commonly missed requirement. A master built with the two terminal counts of Chapter 11.2 §10 still needs a third path for this case, and a master that issues a repeated START inside a normal high phase violates one margin or the other depending on where in the phase it drops SDA.
A repeated START is still much cheaper than a STOP-then-START. tSU;STA at 0.6 µs against tBUF at 1.3 µs in Fast-mode, and the STOP itself costs tSU;STO. §2's table is the comparison; Chapter 10.1 §5 is the protocol consequence.
Sizing note: both parameters are minima with no maxima, so the counters need to tolerate an arbitrarily long interval without wrapping. A master that stretches the high phase around a repeated START — which §9's first point says it must — produces intervals far longer than a data bit's, and a counter sized for a data bit will wrap and report a small number.
10. Debugging — The Repeated START That Worked at 400 kHz
Pitfall — a parameter pair that is only distinguishable in the slowest speed mode
// A master's framing timer, used for both START margins. One constant, because at the
// design's target speed the two parameters have the same value:
//
// localparam T_STA = 60; // 0.6 us at 100 MHz -- Fast-mode
// // tSU;STA(min) = 0.6 us and tHD;STA(min) = 0.6 us
//
// // repeated START: hold SDA high T_STA after SCL rises, drop it, hold SCL high T_STA
// REP_START_SETUP: if (tick == T_STA) begin sda_drive_low <= 1'b1; state <= REP_START_HOLD; end
// REP_START_HOLD: if (tick == T_STA) begin scl_drive_low <= 1'b1; state <= BIT0; end
//
// At Fast-mode this is correct. Both margins are 0.6 us and both are met exactly.
//
// The design was verified at 400 kHz, and at 1 MHz where the two are also equal (0.26 us).
// Standard-mode was regarded as the easy case and regressed only for functional correctness,
// not for timing -- "if it works at 400 kHz it works at 100".Two years later the part was designed into a system with a long backplane that required Standard-mode. Writes worked. Write-then-read sequences using a repeated START failed on about one device in three -- and on those devices, they failed every time, which is a much stranger pattern than intermittent failure.
The first reading of the capture was that the slave ignored the repeated START: it continued to treat the following byte as write data. That pointed squarely at the slaves, and two different vendors' parts were swapped out with no change. A third vendor's part worked perfectly, which was taken as evidence that the first two were non-compliant.
The tell was that the failing slaves were the OLDER parts, and older parts tend to be more conservative about framing recognition, not less. That inverted the suspicion.
Measuring the master's repeated START in Standard-mode gave 4.0 us of setup and 4.0 us of hold. The hold is correct -- tHD;STA(min) is 4.0 us. The setup is 0.7 us SHORT, because tSU;STA(min) in Standard-mode is 4.7 us, not 4.0.
One constant had been used for two parameters whose values are equal in Fast-mode and Fast-mode Plus and DIFFERENT in Standard-mode. Every test the design had ever passed was run in a mode where the two cannot be told apart.
tSU;STA(min) and tHD;STA(min) are 0.6 us and 0.6 us in Fast-mode, 0.26 and 0.26 in Fast-mode Plus, and 4.7 and 4.0 in Standard-mode. The design used one constant for both, which is correct in two of the three modes and wrong in the third.
The verification gap is the deeper cause. Standard-mode was treated as the easy case on the grounds that everything is slower, and for most parameters that reasoning holds. For a parameter PAIR it inverts: the slowest mode was the only one in which the two parameters were separable, so it was the only mode whose test could have found this.
The three-vendor pattern was a red herring in the usual way -- the slaves that worked had more tolerant framing detectors, which made a compliant master look like the wrong hypothesis.
11. Common Misconceptions
"A repeated START is just a START issued during a transfer." Its setup requirement is a different parameter — tSU;STA rather than tBUF — because the bus was active rather than idle. §2 is the comparison, and in Fast-mode it is 0.6 µs against 1.3 µs.
"tSU;STA and tHD;STA are effectively one parameter." They are equal in Fast-mode and Fast-mode Plus and differ in Standard-mode, 4.7 against 4.0. §10 is two years of that assumption.
"Standard-mode is the easy case to verify." For a parameter pair it is the only mode in which these two are separable, which makes it the mode that must be run — and §6's callout is this chapter's own suite admitting it does not.
"Slower-mode timings can be derived by scaling." Standard-mode's ratio to Fast-mode is 7.83 for tSU;STA and 6.67 for tHD;STA. Use a per-mode table.
"Any SDA fall is a START." Only a fall while SCL is high. A fall during a low phase is a data bit, and a checker missing that qualification reports a START for every zero on the bus.
"A repeated START is free because it avoids the bus-free time." It is cheaper, not free: the two margins together exceed a minimum high phase, so the high phase containing it is at least twice the normal minimum. A master needs a way to lengthen that phase.
"tHD;STA is unrelated to the clock's parameters." It equals tHIGH(min) in all three modes, because recognising a START uses the same input stage for the same length of time as sampling a bit.
12. Reason It Through
Why does tHD;STA(min) equal tHIGH(min) in all three speed modes?
Because after SDA falls, SCL is still high, and the interval before SCL may fall is the remainder of a high phase. Recognising a START takes the same input stage the same time as sampling a data bit, so the specification asks for a full high phase's worth — which is also why the table can say the first clock pulse follows this period.
A design uses one constant for both START margins and passes every Fast-mode and Fm+ test. What has it proved?
Nothing about the pair. The two minima are equal in both those modes, so no stimulus in either can distinguish a correct implementation from a merged one. It has proved the margins are long enough; it has not proved they are the right two margins.
Why is an SDA rise while SCL is high, immediately after a START, legal and yet productive of no tHD;STA measurement?
Because it is a STOP — the transfer was abandoned before its first clock pulse. tHD;STA's interval ends at SCL's fall, which never comes, and the table's boundary ("after this period, the first clock pulse is generated") never arrives. There is nothing to measure, and reporting a violation would flag a legal abort.
A repeated START in Fast-mode needs 0.6 µs of setup and 0.6 µs of hold, both with SCL high. What does that imply for the SCL generator?
That the high phase containing a repeated START must be at least 1.2 µs — twice tHIGH(min). So a master cannot issue a repeated START inside a normal high phase; its SCL generator needs a path that overrides the high-phase terminal count, in addition to the two counts a compliant clock already requires.
Several vendors' slaves fail with one master and one vendor's works. What should you check before concluding the failing parts are non-compliant?
Whether the working part is the more tolerant one. A framing detector with looser margins accepts a marginally non-compliant master, so the part that works may be the lenient one rather than the correct one — and the majority failing is weak evidence against the majority.
13. Understanding Check
14. Summary
These are the first parameters measured between two different signals. tSU;STA runs from SCL's rise to SDA's fall; tHD;STA from SDA's fall to SCL's fall. Neither has a clock edge at both ends, and SDA's fall is the shared endpoint that closes one and opens the other.
A framing event is recognised by a transition, a data bit by a level. That is why the framing margins bracket an edge while the data parameters bracket a level with a gap between them — and why the SCL-high qualification is part of the definition rather than a detail.
tHD;STA(min) equals tHIGH(min) in all three modes, because recognising a START occupies the same input stage for the same time as sampling a bit. The table's note makes the boundary explicit: the first clock pulse follows.
A repeated START's setup requirement is a different parameter from a first START's. tSU;STA against tBUF — 0.6 µs against 1.3 µs in Fast-mode — which is the timing-level form of Chapter 10.2's protocol argument for keeping the bus.
The two margins are equal in Fast-mode and Fast-mode Plus and differ only in Standard-mode. So the slowest grade is the only one in which they are separable, and it is therefore the one that must be regressed — the opposite of the usual instinct. Never use one constant for both, and never scale slower-mode timings from a faster base.
A repeated START costs SCL high time. The two margins exceed twice the minimum high phase, so a master's clock generator needs a path to lengthen it — a requirement beyond the two terminal counts a compliant clock already needs.
A measurement must be asserted, not only a verdict. Mutation E2 shifts the hold measurement in the safe direction — it inflates it — so it never produces a false violation and no verdict-only test can see it.
15. What Comes Next
Chapter 11.6 completes the framing with tSU;STO and tBUF — the STOP margin and the bus-free time that §2 identified as a first START's entry requirement.
tBUF is unlike everything so far in one respect: it is the only parameter in Table 10 measured between two transactions rather than inside one. That makes it the only parameter a single transfer cannot violate, and it creates a measurement problem the design phase had to solve explicitly — the interval is reported by the START that closes it, so the value arrives indexed against the wrong event.
It is also the parameter a busy multi-master bus is most likely to violate, for a reason that has nothing to do with any single master's design.
Continue learning
Related tutorials
- Related topic
Repeated START — Holding the Bus Between Phases
A repeated START is not a new waveform. It is the START edge again, and what makes it a different event is that the bus was already busy. That single fact is why a classifier needs state and why a monitor that joins late cannot classify what it sees.
- Related topic
Repeated START in Practice — Why Not STOP Then START
Two sequences that look nearly identical in a driver's source are completely different on the wire. This chapter measures the difference, builds the passive monitor that tells them apart from two wires alone, and names exactly what a STOP costs on a shared bus.
- Related topic
10-Bit Addressing
A two-byte addressing mode built entirely out of reserved space, coexisting with seven-bit devices on the same wires. Its first byte is deliberately not unique, and a read has to re-address with only one byte — which is why a 10-bit slave needs memory a 7-bit slave does not.
- Related topic
f(SCL), tLOW and tHIGH — The I²C Clock Envelope
A legal I²C clock is three constraints, not one frequency — and a perfectly compliant 400 kHz clock with a symmetric duty cycle is illegal in Fast-mode. Derives the envelope and an exact identity hidden in Table 10.
