I²C · Module 5
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.
Chapter 5.3 established what ending a transfer costs. The STOP edge is cheap; what follows it is not, because the bus becomes released rather than free and an interval has to elapse before anyone may transmit again.
That cost raises an obvious question. A controller that has just written a register pointer to a sensor and now wants to read that register has finished one logical operation and needs to begin another — but it has no wish to hand the bus back, because handing it back means paying the interval and, on a multi-controller segment, risking that somebody else claims it first. Is there a way to begin a new addressed transfer without ever letting the bus go free?
There is, and the striking thing is that it required inventing nothing at all.
1. The Third Event That Is Not a Third Waveform
UM10204 §3.1.4, the sentences after the START and STOP definitions:
The bus stays busy if a repeated START (Sr) is generated instead of a STOP condition. In this respect, the START (S) and repeated START (Sr) conditions are functionally identical. For the remainder of this document, therefore, the S symbol is used as a generic term to represent both the START and repeated START conditions, unless Sr is particularly relevant.
Three claims, and each one matters to a different part of this chapter.
"The bus stays busy." This is the purpose. A repeated START is issued instead of a STOP, so the transition to released never happens, the bus-free interval never begins, and no other controller ever sees an opportunity. Chapter 5.3's interval is not shortened or optimised — it is simply never entered.
"Functionally identical." This is true in a precise and limited sense, and §3 unpacks it, because read carelessly it is the source of the most common misconception about this event.
"The S symbol is used as a generic term." This is a reading hazard, and §7 is where it becomes a design hazard.
The essential structural fact, stated plainly: a repeated START is the START edge. Same conductor, same direction, same shape, same driver. Put a repeated START and a first START side by side on an oscilloscope with the surrounding context cropped away and there is nothing to distinguish them, because there is nothing to distinguish. What separates them is not in the waveform at all — it is whether a transfer was already in progress when the edge arrived.
2. What It Costs to Let Go, and What Sr Preserves
Put the two sequences next to each other, because the comparison is the reason the event exists.
STOP, then a new START — the bus goes free in between
10 cyclesA repeated START — the bus never goes free
10 cyclesWhat Sr preserves is continuous ownership. Three consequences follow, and they are worth separating because only the first is about speed.
No other controller can interpose. On a segment with one controller this is invisible. On a segment with two, it is the difference between a two-phase operation that completes and one that another controller can split down the middle. What that guarantee does and does not amount to — and where a combined transaction can still be interrupted — is Chapter 10.3, which owns atomicity properly. This chapter establishes only the mechanism.
No bus-free interval is paid. On a Standard-mode bus the interval is of the same order as a bit period, so on a two-phase operation it is a real fraction of the total time — and it is pure overhead, since no information moves during it.
Every target still resets its protocol state. This is the part that makes the whole thing work, and it comes straight from Chapter 5.2 §3: a START unconditionally resets every device's protocol state. A repeated START does too. So the controller gets the clean slate it needs to send a fresh address, without the clean slate costing it the bus.
That last point is what "functionally identical" is really pointing at, and it is why the event needed no new mechanism. The behaviour a controller wants when changing phase — everyone forgets what they were doing, everyone listens for a new address — is already what a START does. The only thing that had to change was the bus not becoming free, and that happens by simply not issuing the STOP.
3. "Functionally Identical" — True, and a Trap
The specification's phrase is accurate and it is routinely over-read. Be precise about the scope.
| property | S | Sr | identical? |
|---|---|---|---|
| the waveform on SDA and SCL | fall while SCL high | fall while SCL high | yes |
| who may generate it | controller only | controller only | yes |
| effect on every target's protocol state | full reset | full reset | yes |
| what follows it | an address byte | an address byte | yes |
| bus state before it | free | busy | no |
| bus state after it | busy | busy | yes |
| a bus-free interval precedes it | yes | no | no |
| which timing parameter governs the interval before it | bus-free time | a setup parameter | no |
Everything about how devices react is identical. Everything about the bus state around it is not. So "functionally identical" means identical in function — in what the event does to the participants — and says nothing about the conditions under which it is legal to emit one, which differ completely.
This distinction has a direct consequence for Chapter 5.5. A first START out of an idle bus is gated by the bus-free interval, because the bus has to have been quiet. A repeated START is gated by a setup requirement measured from SCL's rising edge to the SDA fall, because the bus was never quiet and there is nothing to wait for — there is only the need for the framing edge to be unambiguous. Two different parameters, for two different situations, on the same waveform. UM10204's timing table names the second one for a repeated START specifically, and reading that row as though it governed every START is a mistake this module's next chapter has to head off.
4. The Sequence Sr Requires
Figure 2 shows a step that Figure 1 does not need, and it is easy to miss: SDA has to be brought high first, and it has to happen while SCL is low.
The reason is mechanical. A repeated START is an SDA falling edge, so SDA must be high before it can fall. But at the end of a transfer phase SDA is typically low — the last data bit was a zero, or a target was holding it low to acknowledge. So the controller has to release SDA before it can produce the edge, and it may not do that while SCL is high, because an SDA rise while SCL is high is a STOP. Releasing SDA at the wrong moment does not merely fail to produce a repeated START — it produces the exact event the controller was trying to avoid.
So the sequence is forced, and every step is load-bearing:
state what the controller does why it must be here
----- ------------------------ -------------------
1. SCL low release SDA (SDA goes high) SDA must be high to fall
later; doing this while
SCL is HIGH would be a STOP
2. SCL low ensure SDA has settled high the next step samples it
3. release SCL (SCL goes high) the framing edge needs a
HIGH phase to occur in
4. wait the setup interval this is the tSU;STA margin
of Chapter 5.5
5. drive SDA low <-- THIS EDGE IS THE Sr SDA falls while SCL is high
6. wait the hold interval tHD;STA, same as for a
first START
7. drive SCL low the first clock pulse of
the new phase
Steps 1 and 5 are BOTH SDA transitions, in opposite directions, and only one
of them is framing. The difference is step 3: what SCL was doing. Move step 1
after step 3 and the controller emits a STOP -- ending the transfer it was
trying to continue, and then emitting a START into a bus-free interval it
never waited out.Chapter 5.5 builds the sequencer that implements exactly this, with the two intervals programmable. What matters here is that a repeated START is a sequence on the generating side even though it is a single edge on the observing side — an asymmetry that catches designers who reason about framing only from captures.
5. Why Classifying Requires State
Now the detection side, and the point this whole module has been building towards.
Chapter 5.2 built a detector with no state beyond two history bits. It could answer "did a START edge occur" completely, because the definition of the edge refers only to the two conductors. Ask it to distinguish S from Sr and it cannot — not because it is too simple, but because the information is not on the conductors.
Question 1: "did an SDA fall occur while SCL was high?"
|
depends only on: scl[n-1], scl[n], sda[n-1], sda[n]
|
-> answerable from two samples. NO state required.
This is Chapter 5.2's detector, and it is complete.
Question 2: "was that fall an S or an Sr?"
|
depends on: everything above, PLUS
"was a transfer already in progress?"
|
-> that last term is a FUNCTION OF HISTORY, not of the
current samples. It is set by an earlier framing event
and cleared by a later one.
-> requires ONE BIT of state. And one bit is enough.
The minimum state for classification is therefore a single flag:
set by any framing fall (S or Sr -- both make the bus busy)
clear by a framing rise (P -- the bus is released)
Read BEFORE it is updated, that flag is exactly the discriminator:
flag == 0 at the edge -> S
flag == 1 at the edge -> SrOne bit. That is the entire difference between a detector and a classifier, and it is worth noticing that the bit in question is not new either — it is bus_busy, which Chapter 5.3's tracker already maintains for a completely different purpose. The classifier is an assembly of parts this module has already built, which is why it is the right place for the module's architectural payoff.
6. The Classifier in Three Languages
One edge detector producing both framing directions, one state bit, and three mutually exclusive single-cycle outputs.
module i2c_framing_classifier (
input logic clk,
input logic rst_n,
input logic scl_in, // observed bus level, not drive intent
input logic sda_in, // observed bus level, not drive intent
output logic start_pulse, // S -- first START of a transfer
output logic repeated_start_pulse, // Sr -- START while the bus was busy
output logic stop_pulse, // P
output logic bus_busy
);
logic scl_q, sda_q;
logic saw_fall, saw_rise;
// The two framing edges. Both require SCL HIGH on BOTH samples, so an SDA
// change coincident with an SCL edge is not claimed as framing.
always_comb begin
saw_fall = scl_q && scl_in && sda_q && !sda_in;
saw_rise = scl_q && scl_in && !sda_q && sda_in;
end
always_ff @(posedge clk) begin
if (!rst_n) begin
scl_q <= 1'b1; sda_q <= 1'b1;
start_pulse <= 1'b0;
repeated_start_pulse <= 1'b0;
stop_pulse <= 1'b0;
bus_busy <= 1'b0;
end else begin
// Default every event output low: these are pulses, not levels.
start_pulse <= 1'b0;
repeated_start_pulse <= 1'b0;
stop_pulse <= 1'b0;
// S and Sr are the SAME EDGE. Nothing in the waveform distinguishes
// them -- only whether a transfer was already in progress does. That
// is why a classifier needs state and an edge detector does not.
if (saw_fall) begin
if (bus_busy) repeated_start_pulse <= 1'b1;
else start_pulse <= 1'b1;
bus_busy <= 1'b1;
end else if (saw_rise) begin
// UM10204 defines STOP by the edge itself, so the edge is
// classified as STOP whatever the tracked state was.
stop_pulse <= 1'b1;
bus_busy <= 1'b0;
end
scl_q <= scl_in;
sda_q <= sda_in;
end
end
endmodule module i2c_framing_classifier_tb;
logic clk = 1'b0, rst_n, scl_in, sda_in;
logic s_p, sr_p, p_p, busy;
int errors = 0;
int n_s = 0, n_sr = 0, n_p = 0;
i2c_framing_classifier dut (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.start_pulse(s_p), .repeated_start_pulse(sr_p), .stop_pulse(p_p), .bus_busy(busy));
always #5 clk = ~clk;
initial begin #40000; $display("FAIL: watchdog expired"); $finish; end
always @(posedge clk) if (rst_n) begin
if (s_p) n_s++;
if (sr_p) n_sr++;
if (p_p) n_p++;
// The three classifications are mutually exclusive by construction; an
// edge that produced two of them would be a classifier that cannot
// decide, which is worse than one that is wrong.
if ((s_p + sr_p + p_p) > 1) begin
$display("FAIL: %0t -- more than one framing class asserted at once", $time);
errors++;
end
end
task automatic expect_counts(input int es, input int esr, input int ep, input int step);
if (n_s !== es || n_sr !== esr || n_p !== ep) begin
$display("FAIL: step %0d -- S/Sr/P = %0d/%0d/%0d, expected %0d/%0d/%0d",
step, n_s, n_sr, n_p, es, esr, ep);
errors++;
end
endtask
// SDA falls while SCL is HIGH. From idle this is S; while busy it is Sr.
task automatic sda_fall_while_high();
sda_in = 1'b1; scl_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (2) @(negedge clk);
endtask
// Bring SDA back HIGH during the LOW phase, so the next rise-while-high is
// available. This is ordinary data movement and must classify as nothing.
task automatic release_sda_during_low();
scl_in = 1'b0; repeat (1) @(negedge clk);
sda_in = 1'b1; repeat (2) @(negedge clk);
endtask
task automatic sda_rise_while_high();
scl_in = 1'b0; sda_in = 1'b0; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b1; repeat (2) @(negedge clk);
endtask
task automatic do_bit(input logic v);
scl_in = 1'b0; repeat (1) @(negedge clk);
sda_in = v; repeat (1) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
endtask
// BOUNDARY: SDA moves in the same interval SCL rises -- not framing.
task automatic coincident_fall();
scl_in = 1'b0; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
endtask
initial begin
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1;
repeat (2) @(negedge clk);
if (busy !== 1'b0) begin $display("FAIL: bus_busy set after reset"); errors++; end
rst_n = 1'b1; @(negedge clk);
// 1 -- ordinary data must classify as nothing at all.
do_bit(1'b0); do_bit(1'b1);
expect_counts(0, 0, 0, 1);
// 2 -- a fall while high from an IDLE bus is S, not Sr.
sda_fall_while_high();
expect_counts(1, 0, 0, 2);
if (busy !== 1'b1) begin $display("FAIL: S did not set bus_busy"); errors++; end
// 3 -- data, then the SAME EDGE again while busy, is Sr, not S.
do_bit(1'b1);
release_sda_during_low();
sda_fall_while_high();
expect_counts(1, 1, 0, 3);
if (busy !== 1'b1) begin $display("FAIL: Sr cleared bus_busy"); errors++; end
// 4 -- a second Sr also classifies as Sr: repeatable, not a one-shot.
do_bit(1'b0);
release_sda_during_low();
sda_fall_while_high();
expect_counts(1, 2, 0, 4);
// 5 -- a rise while high is P, and releases the bus.
sda_rise_while_high();
expect_counts(1, 2, 1, 5);
if (busy !== 1'b0) begin $display("FAIL: P did not clear bus_busy"); errors++; end
// 6 -- THE KEY REGRESSION: after a P, the next fall must be S again.
// A classifier whose busy flag is sticky reports Sr here forever.
sda_fall_while_high();
expect_counts(2, 2, 1, 6);
// 7 -- boundary case stays unclassified.
coincident_fall();
expect_counts(2, 2, 1, 7);
if (errors == 0)
$display("PASS: S=%0d Sr=%0d P=%0d -- same edge classified by bus state, exclusively",
n_s, n_sr, n_p);
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule module i2c_framing_classifier (
input wire clk,
input wire rst_n,
input wire scl_in, // observed bus level, not drive intent
input wire sda_in, // observed bus level, not drive intent
output reg start_pulse, // S -- first START of a transfer
output reg repeated_start_pulse, // Sr -- START while the bus was busy
output reg stop_pulse, // P
output reg bus_busy
);
reg scl_q, sda_q;
wire saw_fall, saw_rise;
// Both framing edges require SCL HIGH on BOTH samples.
assign saw_fall = scl_q & scl_in & sda_q & ~sda_in;
assign saw_rise = scl_q & scl_in & ~sda_q & sda_in;
always @(posedge clk) begin
if (!rst_n) begin
scl_q <= 1'b1; sda_q <= 1'b1;
start_pulse <= 1'b0;
repeated_start_pulse <= 1'b0;
stop_pulse <= 1'b0;
bus_busy <= 1'b0;
end else begin
// Pulses, not levels: default low every cycle.
start_pulse <= 1'b0;
repeated_start_pulse <= 1'b0;
stop_pulse <= 1'b0;
// S and Sr are the SAME EDGE; only the tracked state separates them.
if (saw_fall) begin
if (bus_busy) repeated_start_pulse <= 1'b1;
else start_pulse <= 1'b1;
bus_busy <= 1'b1;
end else if (saw_rise) begin
stop_pulse <= 1'b1;
bus_busy <= 1'b0;
end
scl_q <= scl_in;
sda_q <= sda_in;
end
end
endmodule module i2c_framing_classifier_tb;
reg clk, rst_n, scl_in, sda_in;
wire s_p, sr_p, p_p, busy;
integer errors, n_s, n_sr, n_p;
i2c_framing_classifier dut (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.start_pulse(s_p), .repeated_start_pulse(sr_p), .stop_pulse(p_p), .bus_busy(busy));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin #40000; $display("FAIL: watchdog expired"); $finish; end
always @(posedge clk) if (rst_n) begin
if (s_p) n_s = n_s + 1;
if (sr_p) n_sr = n_sr + 1;
if (p_p) n_p = n_p + 1;
// Mutually exclusive by construction.
if ((s_p + sr_p + p_p) > 1) begin
$display("FAIL: %0t -- more than one framing class asserted at once", $time);
errors = errors + 1;
end
end
task expect_counts;
input integer es; input integer esr; input integer ep; input integer step;
begin
if (n_s !== es || n_sr !== esr || n_p !== ep) begin
$display("FAIL: step %0d -- S/Sr/P = %0d/%0d/%0d, expected %0d/%0d/%0d",
step, n_s, n_sr, n_p, es, esr, ep);
errors = errors + 1;
end
end endtask
task sda_fall_while_high; begin
sda_in = 1'b1; scl_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (2) @(negedge clk);
end endtask
task release_sda_during_low; begin
scl_in = 1'b0; repeat (1) @(negedge clk);
sda_in = 1'b1; repeat (2) @(negedge clk);
end endtask
task sda_rise_while_high; begin
scl_in = 1'b0; sda_in = 1'b0; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b1; repeat (2) @(negedge clk);
end endtask
task do_bit; input v; begin
scl_in = 1'b0; repeat (1) @(negedge clk);
sda_in = v; repeat (1) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
end endtask
task coincident_fall; begin
scl_in = 1'b0; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
end endtask
initial begin
errors = 0; n_s = 0; n_sr = 0; n_p = 0;
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1;
repeat (2) @(negedge clk);
if (busy !== 1'b0) begin $display("FAIL: bus_busy set after reset"); errors = errors + 1; end
rst_n = 1'b1; @(negedge clk);
do_bit(1'b0); do_bit(1'b1);
expect_counts(0, 0, 0, 1);
sda_fall_while_high();
expect_counts(1, 0, 0, 2);
if (busy !== 1'b1) begin $display("FAIL: S did not set bus_busy"); errors = errors + 1; end
do_bit(1'b1);
release_sda_during_low();
sda_fall_while_high();
expect_counts(1, 1, 0, 3);
if (busy !== 1'b1) begin $display("FAIL: Sr cleared bus_busy"); errors = errors + 1; end
do_bit(1'b0);
release_sda_during_low();
sda_fall_while_high();
expect_counts(1, 2, 0, 4);
sda_rise_while_high();
expect_counts(1, 2, 1, 5);
if (busy !== 1'b0) begin $display("FAIL: P did not clear bus_busy"); errors = errors + 1; end
// Sticky-busy regression: after P the next fall must be S again.
sda_fall_while_high();
expect_counts(2, 2, 1, 6);
coincident_fall();
expect_counts(2, 2, 1, 7);
if (errors == 0)
$display("PASS: S=%0d Sr=%0d P=%0d -- same edge classified by bus state, exclusively",
n_s, n_sr, n_p);
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
entity i2c_framing_classifier is
port (
clk : in std_logic;
rst_n : in std_logic;
scl_in : in std_logic; -- observed bus level, not drive intent
sda_in : in std_logic; -- observed bus level, not drive intent
start_pulse : out std_logic; -- S -- first START of a transfer
repeated_start_pulse : out std_logic; -- Sr -- START while the bus was busy
stop_pulse : out std_logic; -- P
bus_busy : out std_logic
);
end entity;
architecture rtl of i2c_framing_classifier is
signal scl_q : std_logic := '1';
signal sda_q : std_logic := '1';
signal busy : std_logic := '0';
signal saw_fall, saw_rise : std_logic;
begin
-- Both framing edges require SCL HIGH on BOTH samples.
saw_fall <= scl_q and scl_in and sda_q and (not sda_in);
saw_rise <= scl_q and scl_in and (not sda_q) and sda_in;
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
scl_q <= '1'; sda_q <= '1';
start_pulse <= '0';
repeated_start_pulse <= '0';
stop_pulse <= '0';
busy <= '0';
else
-- Pulses, not levels: default low every cycle.
start_pulse <= '0';
repeated_start_pulse <= '0';
stop_pulse <= '0';
-- S and Sr are the SAME EDGE; only the tracked state separates them.
if saw_fall = '1' then
if busy = '1' then
repeated_start_pulse <= '1';
else
start_pulse <= '1';
end if;
busy <= '1';
elsif saw_rise = '1' then
stop_pulse <= '1';
busy <= '0';
end if;
scl_q <= scl_in;
sda_q <= sda_in;
end if;
end if;
end process;
bus_busy <= busy;
end architecture; library ieee;
use ieee.std_logic_1164.all;
entity i2c_framing_classifier_tb is
end entity;
architecture sim of i2c_framing_classifier_tb is
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal scl_in : std_logic := '1';
signal sda_in : std_logic := '1';
signal s_p, sr_p, p_p, busy : std_logic;
-- Driven by the counting process, read by the checker.
signal n_s, n_sr, n_p : natural := 0;
signal excl_err : natural := 0;
-- Set by the checker just before it suspends, so the watchdog can tell a
-- finished run from a stalled one.
signal test_done : std_logic := '0';
begin
dut : entity work.i2c_framing_classifier
port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
start_pulse => s_p, repeated_start_pulse => sr_p,
stop_pulse => p_p, bus_busy => busy);
clk <= not clk after 5 ns;
-- Watchdog. A broken design must produce a REPORTED FAILURE, not a silent
-- stop: without this, a `wait until` against a stalled DUT simply runs to the
-- simulator's time limit and prints nothing that identifies the problem.
watchdog : process
begin
wait for 40 us;
if test_done = '0' then
report "watchdog expired -- the design never reached the expected state"
severity failure;
end if;
wait;
end process;
count : process (clk)
variable hits : natural;
begin
if rising_edge(clk) then
if rst_n = '1' then
hits := 0;
if s_p = '1' then n_s <= n_s + 1; hits := hits + 1; end if;
if sr_p = '1' then n_sr <= n_sr + 1; hits := hits + 1; end if;
if p_p = '1' then n_p <= n_p + 1; hits := hits + 1; end if;
-- The three classifications are mutually exclusive by construction.
if hits > 1 then excl_err <= excl_err + 1; end if;
end if;
end if;
end process;
stim : process
variable errs : natural := 0;
procedure waitn (n : in positive) is
begin
for i in 1 to n loop wait until falling_edge(clk); end loop;
end procedure;
procedure expect_counts (es, esr, ep, step : in natural) is
begin
if n_s /= es or n_sr /= esr or n_p /= ep then
report "step " & integer'image(step) & ": S/Sr/P = " &
integer'image(n_s) & "/" & integer'image(n_sr) & "/" & integer'image(n_p) &
", expected " & integer'image(es) & "/" & integer'image(esr) &
"/" & integer'image(ep) severity error;
errs := errs + 1;
end if;
end procedure;
procedure sda_fall_while_high is
begin
sda_in <= '1'; scl_in <= '1'; waitn(2);
sda_in <= '0'; waitn(2);
scl_in <= '0'; waitn(2);
end procedure;
procedure release_sda_during_low is
begin
scl_in <= '0'; waitn(1);
sda_in <= '1'; waitn(2);
end procedure;
procedure sda_rise_while_high is
begin
scl_in <= '0'; sda_in <= '0'; waitn(2);
scl_in <= '1'; waitn(2);
sda_in <= '1'; waitn(2);
end procedure;
procedure do_bit (v : in std_logic) is
begin
scl_in <= '0'; waitn(1);
sda_in <= v; waitn(1);
scl_in <= '1'; waitn(2);
scl_in <= '0'; waitn(1);
end procedure;
procedure coincident_fall is
begin
scl_in <= '0'; sda_in <= '1'; waitn(2);
sda_in <= '0'; scl_in <= '1'; waitn(2);
scl_in <= '0'; waitn(1);
end procedure;
begin
waitn(2);
if busy /= '0' then
report "bus_busy set after reset" severity error; errs := errs + 1; end if;
rst_n <= '1'; waitn(1);
do_bit('0'); do_bit('1');
expect_counts(0, 0, 0, 1);
sda_fall_while_high;
expect_counts(1, 0, 0, 2);
if busy /= '1' then report "S did not set bus_busy" severity error; errs := errs + 1; end if;
do_bit('1');
release_sda_during_low;
sda_fall_while_high;
expect_counts(1, 1, 0, 3);
if busy /= '1' then report "Sr cleared bus_busy" severity error; errs := errs + 1; end if;
do_bit('0');
release_sda_during_low;
sda_fall_while_high;
expect_counts(1, 2, 0, 4);
sda_rise_while_high;
expect_counts(1, 2, 1, 5);
if busy /= '0' then report "P did not clear bus_busy" severity error; errs := errs + 1; end if;
-- Sticky-busy regression: after P the next fall must be S again.
sda_fall_while_high;
expect_counts(2, 2, 1, 6);
coincident_fall;
expect_counts(2, 2, 1, 7);
if excl_err /= 0 then
report "more than one framing class asserted simultaneously" severity error;
errs := errs + 1;
end if;
if errs = 0 then
report "i2c_framing_classifier self-check complete: S=" & integer'image(n_s) &
" Sr=" & integer'image(n_sr) & " P=" & integer'image(n_p) &
" -- same edge classified by bus state, exclusively" severity note;
else
report "i2c_framing_classifier self-check FAILED" severity error;
end if;
test_done <= '1';
wait;
end process;
end architecture;6a. Two Design Decisions Worth Defending
The three outputs are mutually exclusive by construction, and the testbench checks it anyway. An edge is a fall or a rise and cannot be both, so the if/else if structure makes simultaneous assertion structurally impossible. The testbench still asserts that no two are ever high together, on every cycle of every test. That is not redundancy — it is the check that survives a future refactor. A classifier that could assert two classes at once is worse than one that picks the wrong class, because a consumer has no defined behaviour for "this edge was both a START and a STOP", and the failure would appear downstream in whatever component is least equipped to explain it.
stop_pulse is emitted on any qualified rise, regardless of bus_busy. This is a deliberate choice and the alternative is defensible, so here is the reasoning. UM10204 defines a STOP by the edge itself — "a LOW to HIGH transition on the SDA line while SCL is HIGH defines a STOP condition" — with no clause about prior state. A classifier's job is to report what the bus did, and a qualified rise is a STOP by definition. Whether a STOP arriving with no preceding START is legal is a different question, and it is a malformed-framing case that Chapter 5.5 catalogues. Keeping the classifier definitional and the legality judgement separate means the two can be verified separately, and it means the classifier does not silently swallow evidence of a malformed bus.
The asymmetry with the fall case is real and is not an inconsistency: the fall must consult bus_busy, because the specification gives the two falls different names based on exactly that. The rise has only one name.
6b. Cross-Language Parity
| SystemVerilog | Verilog-2005 | VHDL | |
|---|---|---|---|
| state bit | logic bus_busy (output reg) | reg bus_busy (output reg) | internal signal busy, driven to the port |
| edge terms | always_comb | continuous assign | concurrent signal assignment |
| default-low pulses | assigned low each cycle, overridden | same | same |
| class counting in TB | int incremented | integer incremented | counting process drives signals, checker reads them |
| exclusivity check | sum of the three > 1 | sum of the three > 1 | hits variable per cycle, error counter signal |
The VHDL classifier routes its state through an internal busy signal rather than reading the output port directly, because the design reads that bit in the same process that assigns it. The Verilog versions can read an output reg freely; VHDL's stricter port-mode rules make the internal signal the idiomatic form. The hardware is identical — one flip-flop, read and written in the same clocked process.
Verified execution. All three complete at the same simulated time with identical classifications:
| language | simulator | result | classifications | completes at |
|---|---|---|---|---|
| SystemVerilog | Icarus Verilog, -g2012 | PASS | S=2, Sr=2, P=1 | 640 ns |
| Verilog-2005 | Icarus Verilog, -g2005 | PASS | S=2, Sr=2, P=1 | 640 ns |
| VHDL | nvc 1.23.0 | PASS | S=2, Sr=2, P=1 | 640 ns |
i2c_framing_classifier — one edge, two classes
10 cycles7. The Regression That Matters Most
The testbench runs seven steps. Six of them are ordinary. One is the reason the suite exists.
| step | stimulus | required classification |
|---|---|---|
| 1 | ordinary data bits | nothing at all |
| 2 | SDA falls while high, bus free | S — exactly one |
| 3 | data, SDA released during low, SDA falls while high | Sr — not another S |
| 4 | the same again | a second Sr — repeatable |
| 5 | SDA rises while high | P, and bus_busy clears |
| 6 | SDA falls while high, after the P | S again |
| 7 | SDA falls coincident with SCL rising | nothing — boundary |
Step 6 is the one. After a STOP, the next falling edge must classify as a first START, because the bus went free and a new transfer is beginning. A classifier whose state bit is set correctly but never cleared passes steps 1 through 5 perfectly — it reports the first S, then Sr, then Sr, then P — and from that point on reports every subsequent START as a repeated START, forever. The bus appears to work. Every transfer after the first is misclassified.
That failure mode is nastier than it sounds because of where the consequence lands. A protocol monitor consuming these events builds transactions from them; if every S after the first arrives labelled Sr, the monitor concludes that all subsequent transfers are continuations of one enormous transaction that never ends. The scoreboard sees one transfer where there were two hundred. The reported error will be about transaction boundaries or about a queue that never drains, and nothing in that error points at a state bit that failed to clear.
Step 6 costs two lines and is the difference between catching that at block level and chasing it at system level.
8. Mutation Testing
Five faults injected into the verified RTL, testbench run against each.
| mutation | what it breaks | result |
|---|---|---|
ignore bus_busy — always classify as S | Sr is never reported | FAIL — step 3 saw S=2, Sr=0 |
ignore bus_busy — always classify as Sr | the first START is misreported | FAIL — step 2 saw S=0, Sr=1 |
STOP does not clear bus_busy | the step-6 failure above | FAIL — P did not clear bus_busy |
a framing fall does not set bus_busy | Sr never reported; bus never busy | FAIL — S did not set bus_busy |
| swap the two framing directions | every class is the wrong one | FAIL — step 2 saw P=1 |
All five caught, and the useful observation is about the first two rather than the third.
Mutations 1 and 2 are the same structural fault — a classifier that has stopped consulting its state — and they fail on different steps. Always-S fails first at step 3, the first Sr. Always-Sr fails at step 2, the very first START. A suite that contained only one of those two steps would catch one mutant and miss the other, and the missed one would be a design that works correctly on every bus that never uses a repeated START at all — which is a great many buses, right up until someone adds a sensor that requires a write-then-read.
That is the argument for testing both directions of a binary decision rather than assuming symmetry. The two mutants are mirror images; the tests that catch them are not interchangeable.
9. The Monitor That Joined Late
There is a limitation here that is structural rather than a defect, and being explicit about it is the difference between a monitor that reports honestly and one that lies confidently.
The classifier's discriminator is a state bit built by observing framing events. A classifier that was not watching when a transfer began has no valid state. Enable it in the middle of an active transfer and its bus_busy reads zero — not because the bus is free, but because it has not seen the START that made the bus busy. The next repeated START it observes will be classified as a first START.
Nothing in the design can detect this. The bit is not wrong in any way the block can observe; it is simply uninitialised with respect to a bus that was already running.
Three honest responses, in increasing order of sophistication:
Reset while the bus is idle. If the classifier's reset is released when the bus is genuinely free, its state is correct by construction. This is the right answer for a controller's own classifier, whose reset is under the same designer's control.
Declare the state unknown until a STOP is seen. A STOP is the resynchronisation point: whatever was happening before it, after it the bus is released and the classifier's zero is correct. A monitor can report edges without classifying them until it has observed one STOP, and classify confidently from then on. This costs one extra state bit and is the right answer for a passive monitor.
Report the edge and the uncertainty separately. The most useful output for a debug monitor is "a framing fall occurred, and I do not yet know whether it was S or Sr", because that is true and actionable, whereas a confident wrong classification sends the investigation in the wrong direction.
This is the same class of problem as Chapter 5.2's surviving mutation, and it has the same shape: state that is correct by construction in the normal case can be silently wrong in the case where observation begins mid-stream. A bus does not wait to be watched.
10. Verification Connection — Covering the Sequence Space
A single S, Sr or P is not interesting. What is interesting is which sequences of them the verification has exercised, and this is where coverage stops being vanity and starts being a question worth answering.
The framing events form a small, enumerable transition space, and a controller's legality depends on position in it:
| from | to | legal? | what it means |
|---|---|---|---|
| free | S | yes | a transfer begins |
| busy | Sr | yes | a phase change without releasing |
| busy | P | yes | a transfer ends |
| free | Sr | n/a | not a distinct case — from free, that edge is an S |
| free | P | no | a STOP with no transfer to stop — malformed |
| busy | S | n/a | not a distinct case — from busy, that edge is an Sr |
The space is small enough to cover exhaustively, which makes "did we cover it" a fair question rather than a box-ticking exercise.
// Counting how many STARTs a test produced answers nothing useful. What
// matters is which TRANSITIONS were exercised, because the classifier's whole
// job is to be correct about position in this sequence.
covergroup framing_sequence_cg @(posedge clk);
option.per_instance = 1;
// The classification actually produced this cycle.
cp_class : coverpoint framing_class iff (framing_event) {
bins s = { CLASS_S };
bins sr = { CLASS_SR };
bins p = { CLASS_P };
}
// The transitions -- this is the part that finds real holes. A suite that
// never produced sr => sr has not tested a multi-phase combined transfer;
// one that never produced p => s has not tested the step-6 regression.
cp_seq : coverpoint framing_class iff (framing_event) {
bins s_then_sr = ( CLASS_S => CLASS_SR );
bins sr_then_sr = ( CLASS_SR => CLASS_SR );
bins sr_then_p = ( CLASS_SR => CLASS_P );
bins s_then_p = ( CLASS_S => CLASS_P );
bins p_then_s = ( CLASS_P => CLASS_S ); // <- section 7, step 6
}
endgroupTwo things about that coverage model are deliberate.
The transition bins are the point; the event bins are bookkeeping. Knowing a test produced forty STARTs tells you nothing. Knowing it never produced p => s tells you the sticky-bus_busy bug in §8 could not have been caught, and knowing it never produced sr => sr tells you no multi-phase transfer was exercised. Those are actionable holes.
A bin is missing on purpose. There is no bin for a STOP arriving with the bus free, because that is malformed framing rather than a legal transition, and mixing legality violations into a legal-transition coverage model makes an unreachable bin look like a coverage hole. Error cases need their own model, and they are Chapter 5.5's subject.
The separation matters more generally: legal-behaviour coverage and error-injection coverage answer different questions and should not share a covergroup. A group containing both can never reach 100% in a clean run, which trains everybody to ignore its report.
11. FPGA and ASIC Implications
The classifier is one flip-flop more expensive than the detector, and that is the whole cost. There is no reason to omit classification from a controller or a monitor on resource grounds. The reason designs omit it is that they did not realise S and Sr were the same edge and assumed the distinction came for free from context.
The state bit belongs in the same reset domain as the framing detector. Splitting them — detector in an always-on domain, state bit in a peripheral-reset domain — produces exactly §9's failure on every peripheral reset, with the additional property that the bus does not need to be active for it to bite. A peripheral reset that clears bus_busy while a transfer is in progress makes the next repeated START classify as a first START.
A controller that supports repeated START needs the sequencing states, not just the classifier. §4's seven steps are a state machine on the generating side, and the two SDA transitions in it move in opposite directions with different legality conditions. A controller that can only emit S and P is meaningfully simpler, and a controller that emits Sr by reordering its existing S sequence will emit a STOP instead — which is Chapter 5.5's malformed-framing catalogue.
On an ASIC the classification is usually visible to software, and the register semantics matter. A status bit that says "repeated START detected" is an event, not a level, and needs the write-one-to-clear treatment Chapter 3.4 established — because a driver that reads a level cannot tell one repeated START from three.
12. Debugging — The Transfer That Never Ended
A monitor that reported one transaction for an entire test run
Pitfall — a bus_busy flag that is set correctly and cleared on the wrong event
// A classifier is written from the specification, correctly, with one omission.
// The engineer reads that S and Sr differ by whether the bus was busy, and
// implements exactly that:
//
// if (saw_fall) begin
// if (bus_busy) repeated_start_pulse <= 1'b1;
// else start_pulse <= 1'b1;
// bus_busy <= 1'b1; // a framing fall claims the bus
// end else if (saw_rise) begin
// stop_pulse <= 1'b1; // ... and the bus is released
// end // <-- bus_busy never cleared
//
// The omission is invisible in review because the code reads as a complete
// if/else over the two framing directions, and the STOP branch does report the
// STOP. Nothing is missing SYNTACTICALLY. What is missing is the state update
// that makes 'was the bus busy' mean anything on the next edge.
//
// It passes a first test easily. A test that does START, address, data, STOP
// exercises the set path, the S classification and the P report, and every one
// of them is right.Block-level tests pass. The classifier reports S, Sr and P, and a directed test for each one is green.
At system level the scoreboard reports a single transaction for the entire regression, with a payload thousands of bytes long, and then fails on a queue depth limit. The error is raised by the scoreboard, thousands of cycles after the first transfer, and names a queue -- so the investigation starts in the scoreboard, then moves to the transaction builder, then to the sequencer. All of them are correct.
Captures look perfect. Every framing waveform on the bus is legal and correctly shaped, because THE BUS IS FINE -- the DUT is generating flawless framing. The fault is entirely in the observer, which is the hardest place to look when the error message is about transaction content.
The giveaway, once someone thinks to check it, is that the monitor reports exactly one S for the whole run and everything afterwards as Sr.
bus_busy was set by a framing fall and never cleared by a framing rise.
After the very first STOP, the flag stayed high. Every subsequent START therefore arrived with bus_busy == 1 and was classified as a repeated START -- correctly, according to the flag, which was lying. Section 5's derivation is explicit that the discriminator must be SET by a framing fall AND CLEARED by a framing rise; implementing only half of that produces a flag that is monotonic instead of a flag that tracks bus state.
The reason this reaches system level rather than dying at block level is the structure of the block-level suite. A suite built from directed tests -- one test for S, one for Sr, one for P -- resets the DUT between tests, which clears the flag and hides the bug. The bug requires two transfers IN SEQUENCE, in one test, with a STOP between them. That sequence is the p => s transition in section 10's coverage model, and a directed-test suite organised one-classification-per-test never produces it.
The reason the symptom is so far from the cause is that misclassifying S as Sr is not an error to any consumer. It is a valid classification. The transaction builder does exactly the right thing with it -- treats the transfer as a continuation -- and keeps doing so, accumulating one unbounded transaction, until something unrelated runs out of room.
// The RTL fix is one line, in the branch that already existed:
//
// end else if (saw_rise) begin
// stop_pulse <= 1'b1;
// bus_busy <= 1'b0; // <-- the bus is RELEASED
// end
//
// The verification fix is the one worth keeping, and there are two parts.
//
// 1. A SEQUENCE test, not another directed test. Section 7's step 6 is the
// whole fix: after the P, drive another framing fall and require that it
// classifies as S. Two lines, at block level, and it fails immediately
// against this RTL. Section 8 confirms it -- injecting this exact fault into
// the corrected design produces 'FAIL: P did not clear bus_busy' and nothing
// else does.
//
// 2. Coverage on TRANSITIONS rather than events. The hole here is precisely
// the p => s bin of section 10's covergroup. A directed suite reports 100%
// on the event coverpoint -- it produced S, Sr and P -- while never
// producing the transition that matters. Event coverage said the suite was
// complete; transition coverage says it never tested a second transfer.
//
// The generalisable habit: for any flag that encodes 'am I in the middle of
// something', write the SET and the CLEAR in the same review pass and name the
// event for each. A flag with a set condition and no clear condition is a latch,
// and a latch that was meant to be a state bit fails only on the SECOND
// iteration -- which is exactly the iteration a directed test does not run.
//
// And a debugging habit for the symptom shape: when a scoreboard error names a
// resource limit rather than a mismatch, suspect that something upstream is
// failing to terminate rather than producing wrong values. 'One transaction,
// unbounded payload' is a boundary bug, not a data bug, and boundaries come from
// framing.13. Common Misconceptions
"A repeated START is a different waveform from a START." It is the same edge — same conductor, same direction, same shape. Only the prior bus state differs, and that is not visible at the instant of the edge.
"'Functionally identical' means they are interchangeable." They are identical in what they do to devices and completely different in the conditions under which emitting one is legal. §3 tabulates the split; a first START is gated by the bus-free interval and a repeated START by a setup margin.
"A repeated START is an optimisation." Speed is the least important of its three effects. The one that matters on a shared segment is that the bus never becomes free, so no other controller is ever offered it.
"To issue a repeated START, re-run the START sequence." The START sequence begins from a released bus. From a busy one, SDA is typically low and must be released during SCL's low phase first — and releasing it while SCL is high emits a STOP, which is the opposite of the intent. §4's ordering is forced.
"A classifier can work out S versus Sr from the waveform." It cannot. The discriminator is a bit of state set by an earlier event, and a classifier that was not watching when the transfer began has no valid value for it.
"If the classifier reports S, Sr and P correctly in directed tests, it works." Directed tests reset between cases and therefore never exercise the transitions. §12's bug passes every directed test and fails on the second transfer of any sequence.
"A misclassification will show up as an error near the misclassification." Misclassifying S as Sr is a valid classification that a consumer handles correctly, so the consequence accumulates silently and surfaces as an unrelated resource limit thousands of cycles later.
14. Reason It Through
A controller wants to write a register pointer and then read that register. Why is a repeated START preferable to a STOP followed by a START, and what exactly would a STOP risk?
A STOP would make the bus free, which costs the bus-free interval and — more importantly — offers the bus to any other controller on the segment. Another controller could claim it and address the same target, which for a device whose read depends on a register pointer set by the previous phase means the second phase may read from a pointer somebody else changed. A repeated START never releases the bus, so the pointer set in phase one is still the pointer in phase two. The precise extent of that guarantee is Chapter 10.3's subject.
Why can a repeated START not simply reuse the controller's START sequence?
Because the START sequence assumes a released bus, where SDA is already high and available to fall. At the end of a transfer phase SDA is typically low, so it has to be released first — and it must be released while SCL is low, because an SDA rise while SCL is high is a STOP. A controller that reuses the START sequence from a busy bus emits a STOP where it intended an Sr, ending the transfer it was trying to continue.
A classifier's bus_busy is set by a framing fall and cleared by a framing rise. Which of those two omissions is worse?
Omitting the clear is worse, and by a wide margin. Omitting the set means bus_busy is always zero, so every repeated START is reported as a first START — the bug is uniform, immediate, and shows up in the first test that uses a repeated START at all. Omitting the clear means the flag latches high after the first transfer, so the first transfer is classified perfectly and every subsequent one is wrong. A bug that gets the first iteration right and all later ones wrong survives directed testing and surfaces far from its cause, which is §12.
A passive monitor is enabled mid-transfer and immediately observes a repeated START. What does it report, and what should it report?
It reports a first START, because its bus_busy is zero — it never saw the edge that set it. What it should report is that a framing fall occurred whose classification is unknown, because that is the truth. The clean recovery is to treat the next STOP as a resynchronisation point and classify confidently from then on, since after a STOP the bus is genuinely released and a zero flag is correct.
Your block-level suite reports 100% coverage on a coverpoint over the three framing classes, and the design has §12's bug. Explain how both are true.
Because the coverpoint measures which classes were produced and the bug is about the order they are produced in. A directed suite with one test per class produces all three classes and reaches 100%, while resetting between tests so that no test ever contains a STOP followed by a START. The missing coverage is a transition — p => s — and a coverage model built on events rather than transitions is structurally unable to report it as a hole. This is the difference between coverage that answers "did we exercise the interesting region" and coverage that counts.
15. Understanding Check
16. Summary
A repeated START is the START edge, reused. Nothing in the waveform distinguishes it from a first START; only whether a transfer was already in progress does.
Its purpose is continuous ownership. Because no STOP is issued, the bus never becomes free — so the bus-free interval is not paid and no other controller is offered the bus. Speed is the least important of those effects.
"Functionally identical" scopes to effect, not to legality. Devices react identically to S and Sr; the conditions under which each may be emitted are entirely different, and they are governed by different timing parameters.
Emitting one is a forced sequence, not a single action. SDA must be released during SCL's low phase, then SCL released, then SDA driven low. Reordering the first step produces a STOP — the opposite of the intent.
Classification needs exactly one bit of state, set by any framing fall and cleared by a framing rise. That bit is bus_busy, which Chapter 5.3's tracker already maintained, so the classifier is an assembly of parts rather than a new mechanism.
Setting the bit and clearing it are separately necessary, and forgetting the clear is the worse bug — it gets the first transfer right and every later one wrong, which is the failure pattern directed tests cannot see.
A classifier that joined late has no valid state, and no self-check can detect it. The recoveries are to reset on an idle bus, to resynchronise on a STOP, or to report the uncertainty.
Cover transitions, not events. The sequence space is small enough to cover exhaustively, and it is the transitions — p => s above all — that distinguish a suite which tested two transfers from one which tested one transfer three times.
17. What Comes Next
All three framing events are now defined, detectable and distinguishable, and the hardware to do it exists in three languages. Every chapter so far has been careful to say that the intervals around these edges are specified minimums and to defer the numbers.
Chapter 5.5 stops deferring. It takes the three framing margins — the hold after a START, the setup before a repeated START, the setup before a STOP — establishes where each is measured from and to, quotes the verified values, builds the sequencer that generates all three with programmable counts and refuses an illegal configuration, and then catalogues the malformed framing those margins exist to prevent.
Browse the full path on the I²C tutorials index. For the edge this event reuses, see The START Condition; for the interval it avoids paying, The STOP Condition and Releasing the Bus.
Continue learning
Related tutorials
- 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
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
The START Condition
START is SDA falling while SCL is high, it is generated only by the controller, and it makes the bus busy. Derive what every device must do in response, then build a detector in three languages and find out why its two guard terms and its reset value are all load-bearing.
- Related topic
START/STOP Timing and Malformed Framing
Three framing margins, each with two anchor events, all of them minimums: the hold after a START, the setup before a repeated START, and the setup before a STOP. Build a sequencer that generates all three and refuses an illegal configuration, then catalogue the malformed framing the margins exist to prevent.
