I²C · Module 5
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.
Chapter 5.1 counted the reserved space and found exactly two edges in it. This chapter takes the falling one.
It is the single most important event on the bus. Every transfer begins with it, every device on the segment has to react to it, and it is the only thing that moves the bus from free to busy. It is also the simplest piece of hardware in this module — which makes it the right place to establish the detection pattern that Chapter 5.3 and Chapter 5.4 both build on.
1. The Definition, and What It Does Not Say
UM10204 §3.1.4 is one sentence:
A HIGH to LOW transition on the SDA line while SCL is HIGH defines a START condition.
Read what that sentence leaves out, because the omissions are the interesting part.
It says nothing about who — that comes from the next sentence in the specification, which states that START and STOP conditions are always generated by the master. It says nothing about how long the condition lasts, or how long SCL must remain high around it; those are timing parameters and they are Chapter 5.5. It says nothing about what follows, which is the address byte and is Module 6. And it says nothing about how a device is supposed to notice, which is the part this chapter builds.
What it does say is that the condition is a transition, not a level. This is worth pausing on because it determines the shape of every detector in this module. A level can be tested with a comparison; a transition cannot be tested at all without knowing what came before. The definition therefore hands the implementer a requirement for memory before any of the rest of the design exists.
Idle, then START, then the first clock pulse
10 cycles2. The Same Edge, Twice, Meaning Different Things
The reason a START is detectable at all is that ordinary data is forbidden from producing the same waveform — and it is worth seeing the two side by side, because on SDA alone they are identical.
An ordinary data bit carrying a zero also involves SDA going from high to low. The falling edge is the same edge, with the same direction and the same shape. What differs is only what SCL was doing at the time.
A data zero and a START — the identical SDA edge
10 cycles3. What Every Device Does When a START Arrives
This is the part that makes START the critical event rather than merely the first one. A START is a broadcast, and it is unconditional. Every device on the segment sees it — there is no addressing yet, because the address has not been transmitted — and every device must act on it.
Three things happen, and the third is the one that gets designed wrong.
The bus becomes busy. UM10204: the bus is considered busy after the START condition. Any other potential controller on the segment must now not attempt a transfer of its own. On a single-controller bus this is bookkeeping; on a multi-controller bus it is the first line of defence, and what happens when two controllers issue a START at nearly the same instant is Module 13.
Every target begins listening for its address. The byte after the START is an address, so every device has to start shifting in bits and comparing. Most of them will not match and will go back to ignoring the bus, and that is the expected outcome — the majority of devices on a healthy bus spend most of their time deciding a transfer is not for them. Chapter 6.5 builds the comparison.
Every target abandons whatever partial state it had. This is the one worth stating explicitly. A START is not just "a transfer begins" — it is also "any transfer that was in progress is over, however incomplete". A target that was half way through receiving a byte does not get to finish it. A target that was waiting for a second address byte does not get to wait any longer. The START resets the protocol state of every device unconditionally.
That last rule is what makes the bus recoverable. Because a START always means "start over", a controller that has lost track of what a target is doing has a way to resynchronise everyone: issue a START. Without the unconditional-reset rule, a confused bus would need an out-of-band mechanism to recover, and there is no out-of-band on two wires.
4. Why the START Comes Before the Clock
Look again at Figure 1 and note the order: SDA falls first, and only afterwards does SCL go low.
That ordering is not a stylistic choice, and it is not arbitrary. The START is defined as an SDA edge during SCL's high phase, so SCL has to still be high when SDA moves — which means the controller cannot begin its first clock pulse until the START edge has already happened. The clock waits for the framing, not the other way round.
The specification makes the dependency explicit in its timing table, where the parameter governing the interval between the START edge and the first clock pulse carries the condition note "After this period, the first clock pulse is generated." The interval has a specified minimum. Chapter 5.5 measures it and builds the sequencer that honours it; what matters here is the causal order, because a design that pulls SCL low too eagerly does not produce a slightly-marginal START — it produces no START at all, since SDA's edge then lands during a low phase and is ordinary data.
5. Deriving the Detector
Now build the hardware, and derive it rather than presenting it.
The requirement is to notice a high-to-low transition on SDA that occurs while SCL is high. Start from what a synchronous design can actually see: on each clock edge it gets one sample of each conductor. Everything else has to be reconstructed.
A transition needs two samples. "SDA went from high to low" is a statement about two consecutive observations, so the detector must store the previous one. One bit of history per conductor is the minimum, and as it turns out the minimum is also sufficient.
"While SCL is high" needs to be true across the transition, not at a point. This is the subtle part, and it is the same subtlety Chapter 4.2 worked through for its stability checker. The naive reading is to test SCL on the current sample: did SDA just fall, and is SCL high now? That condition is satisfied by a case which is not a START — an SDA edge that happened while SCL was still low, in the very same sampling interval that SCL rose. In a sampled model those two events are indistinguishable inside one interval, and the conservative choice is to require SCL high on both samples:
start_detected = scl_prev AND scl_now AND sda_prev AND NOT sda_now
^^^^^^^^ ^^^^^^^ ^^^^^^^^ ^^^^^^^^^^
SCL was SCL still SDA was SDA is now
already high after high before low -- the
high before the edge the edge falling edge
the edge
Each of the four terms rejects a different non-START case:
drop scl_prev -> an SDA fall coincident with SCL RISING is reported
drop scl_now -> an SDA fall coincident with SCL FALLING is reported
drop sda_prev -> a level is reported instead of an edge
drop sda_now -> the opposite edge (a STOP) is reported as a STARTFour terms, four distinct jobs. §8 injects a fault into each and confirms the count — and the lesson from Chapter 4.2 applies again here, because the two SCL terms are each wrong at only one boundary and a test that exercises neither cannot tell a complete guard from a partial one.
6. The Detector in Three Languages
The design is the same hardware in all three: two history flip-flops, a four-term combinational condition, and a registered single-cycle output.
module i2c_start_detector (
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_detected // single-cycle event pulse
);
// Previous observed samples. A framing edge is a RELATIONSHIP between two
// samples, so one sample of history is the minimum the detector can work
// with. Reset values are the idle bus: both lines released, therefore HIGH.
logic scl_q, sda_q;
always_ff @(posedge clk) begin
if (!rst_n) begin
scl_q <= 1'b1; sda_q <= 1'b1;
start_detected <= 1'b0;
end else begin
// START = SDA HIGH->LOW while SCL is HIGH. SCL must read HIGH on
// BOTH samples: requiring only the current sample would report a
// START for an SDA fall that happened while SCL was still low and
// merely became visible in the same interval SCL rose.
start_detected <= scl_q && scl_in && sda_q && !sda_in;
scl_q <= scl_in;
sda_q <= sda_in;
end
end
endmodule module i2c_start_detector_tb;
logic clk = 1'b0, rst_n, scl_in, sda_in;
logic start_detected;
int errors = 0;
int pulses = 0;
i2c_start_detector dut (.*);
always #5 clk = ~clk;
initial begin #20000; $display("FAIL: watchdog expired"); $finish; end
always @(posedge clk) if (rst_n && start_detected) pulses++;
task automatic expect_pulses(input int want, input string what);
if (pulses !== want) begin
$display("FAIL: %s -- expected %0d START pulse(s), saw %0d", what, want, pulses);
errors++;
end
pulses = 0;
endtask
// An ordinary data bit: SDA changes only while SCL is LOW.
task automatic legal_bit(input logic v);
scl_in = 1'b0; repeat (2) @(negedge clk);
sda_in = v; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (3) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
endtask
// START: SDA falls while SCL is HIGH, then SCL is pulled low.
task automatic do_start();
scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; repeat (3) @(negedge clk); // the START edge
scl_in = 1'b0; repeat (2) @(negedge clk);
endtask
// STOP: SDA RISES while SCL is HIGH. Must never look like a START.
task automatic do_stop();
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 (3) @(negedge clk); // the STOP edge
endtask
// SDA falls while SCL is LOW -- an ordinary data change, not a START.
task automatic sda_fall_while_low();
scl_in = 1'b0; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
endtask
// BOUNDARY: SDA falls in the SAME sample interval SCL rises. Ambiguous in a
// sampled model; this detector deliberately does not claim a START.
task automatic fall_coincident_with_scl_rise();
scl_in = 1'b0; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; scl_in = 1'b1; repeat (3) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
endtask
// BOUNDARY, MIRRORED: SDA falls in the SAME interval SCL FALLS. Also not a
// START -- the HIGH window has ended. This is the case that catches a
// detector which looked only at the PREVIOUS SCL sample.
task automatic fall_coincident_with_scl_fall();
scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; scl_in = 1'b0; repeat (3) @(negedge clk);
endtask
initial begin
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1;
repeat (2) @(negedge clk);
if (start_detected !== 1'b0) begin $display("FAIL: pulse asserted during reset"); errors++; end
rst_n = 1'b1; @(negedge clk); pulses = 0;
// 1 -- ordinary data bits must never produce a START.
legal_bit(1'b0); legal_bit(1'b1); legal_bit(1'b1); legal_bit(1'b0);
expect_pulses(0, "ordinary data bits");
// 2 -- a real START produces EXACTLY ONE pulse (an event, not a level).
do_start();
expect_pulses(1, "a single START condition");
// 3 -- STOP is the opposite edge and must not register as a START.
do_stop();
expect_pulses(0, "a STOP condition");
// 4 -- an SDA fall while SCL is LOW is ordinary data movement.
sda_fall_while_low();
expect_pulses(0, "SDA falling while SCL is low");
// 5 -- BOUNDARY: coincident with the SCL rise, deliberately not a START.
fall_coincident_with_scl_rise();
expect_pulses(0, "SDA falling as SCL rises");
// 5b -- BOUNDARY, MIRRORED: coincident with the SCL FALL, also not a START.
// Both guard terms are needed; a rise-side test alone cannot show it.
fall_coincident_with_scl_fall();
expect_pulses(0, "SDA falling as SCL falls");
// 6 -- two STARTs in a row produce two separate pulses.
do_start(); do_start();
expect_pulses(2, "two successive STARTs");
// 7 -- reset clears the detector and it works afterwards.
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
rst_n = 1'b1; @(negedge clk); pulses = 0;
do_start();
expect_pulses(1, "a START after reset");
// 8 -- RESET HISTORY: a START arriving in the very FIRST cycle after reset
// release must not be missed. This is the only test that can observe
// what the detector assumed the bus was doing before it was watching:
// reset assumes an IDLE bus (both lines HIGH), so the first sample
// already has a valid "SDA was high, SCL was high" history to compare
// against. A detector that reset its history LOW silently drops this
// START -- and a monitor enabled onto a live bus does exactly this.
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
rst_n = 1'b1; sda_in = 1'b0; repeat (3) @(negedge clk);
expect_pulses(1, "a START in the first cycle after reset release");
scl_in = 1'b0; repeat (1) @(negedge clk);
if (errors == 0) $display("PASS: START detected once per event; data, STOP and boundary cases rejected");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule module i2c_start_detector (
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_detected // single-cycle event pulse
);
// One sample of history: a framing edge is a relationship between two
// samples. Reset values are the idle bus -- both lines released, so HIGH.
reg scl_q, sda_q;
always @(posedge clk) begin
if (!rst_n) begin
scl_q <= 1'b1; sda_q <= 1'b1;
start_detected <= 1'b0;
end else begin
// START = SDA HIGH->LOW while SCL is HIGH, on BOTH samples.
start_detected <= scl_q & scl_in & sda_q & ~sda_in;
scl_q <= scl_in;
sda_q <= sda_in;
end
end
endmodule module i2c_start_detector_tb;
reg clk, rst_n, scl_in, sda_in;
wire start_detected;
integer errors;
integer pulses;
integer step;
i2c_start_detector dut (.clk(clk), .rst_n(rst_n), .scl_in(scl_in),
.sda_in(sda_in), .start_detected(start_detected));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin #20000; $display("FAIL: watchdog expired"); $finish; end
always @(posedge clk) if (rst_n && start_detected) pulses = pulses + 1;
task expect_pulses; input integer want; begin
if (pulses !== want) begin
$display("FAIL: step %0d -- expected %0d START pulse(s), saw %0d", step, want, pulses);
errors = errors + 1;
end
pulses = 0;
step = step + 1;
end endtask
task legal_bit; input v; begin
scl_in = 1'b0; repeat (2) @(negedge clk);
sda_in = v; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (3) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
end endtask
task do_start; begin
scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; repeat (3) @(negedge clk);
scl_in = 1'b0; repeat (2) @(negedge clk);
end endtask
task do_stop; 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 (3) @(negedge clk);
end endtask
task sda_fall_while_low; begin
scl_in = 1'b0; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
end endtask
task fall_coincident_with_scl_rise; begin
scl_in = 1'b0; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; scl_in = 1'b1; repeat (3) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
end endtask
// BOUNDARY, MIRRORED: SDA falls in the same interval SCL FALLS -- not a START.
task fall_coincident_with_scl_fall; begin
scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
sda_in = 1'b0; scl_in = 1'b0; repeat (3) @(negedge clk);
end endtask
initial begin
errors = 0; pulses = 0; step = 1;
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1;
repeat (2) @(negedge clk);
if (start_detected !== 1'b0) begin $display("FAIL: pulse asserted during reset"); errors = errors + 1; end
rst_n = 1'b1; @(negedge clk); pulses = 0;
legal_bit(1'b0); legal_bit(1'b1); legal_bit(1'b1); legal_bit(1'b0);
expect_pulses(0);
do_start();
expect_pulses(1);
do_stop();
expect_pulses(0);
sda_fall_while_low();
expect_pulses(0);
fall_coincident_with_scl_rise();
expect_pulses(0);
fall_coincident_with_scl_fall();
expect_pulses(0);
do_start(); do_start();
expect_pulses(2);
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
rst_n = 1'b1; @(negedge clk); pulses = 0;
do_start();
expect_pulses(1);
// RESET HISTORY: a START in the very FIRST cycle after reset release must
// not be missed -- reset assumes an IDLE bus, so the first sample already
// has a valid history. Resetting the history LOW silently drops this START.
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; repeat (2) @(negedge clk);
rst_n = 1'b1; sda_in = 1'b0; repeat (3) @(negedge clk);
expect_pulses(1);
scl_in = 1'b0; repeat (1) @(negedge clk);
if (errors == 0) $display("PASS: START detected once per event; data, STOP and boundary cases rejected");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
entity i2c_start_detector 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_detected : out std_logic -- single-cycle event pulse
);
end entity;
architecture rtl of i2c_start_detector is
-- One sample of history: a framing edge is a relationship between two
-- samples. Reset values are the idle bus -- both lines released, so HIGH.
signal scl_q : std_logic := '1';
signal sda_q : std_logic := '1';
begin
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
scl_q <= '1'; sda_q <= '1';
start_detected <= '0';
else
-- START = SDA HIGH->LOW while SCL is HIGH, on BOTH samples.
if scl_q = '1' and scl_in = '1' and sda_q = '1' and sda_in = '0' then
start_detected <= '1';
else
start_detected <= '0';
end if;
scl_q <= scl_in;
sda_q <= sda_in;
end if;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
entity i2c_start_detector_tb is
end entity;
architecture sim of i2c_start_detector_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 start_detected : std_logic;
-- Driven only by the counting process, read by the checking process, so the
-- checker snapshots it rather than clearing it (one driver per signal).
signal pulses : 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_start_detector
port map (clk => clk, rst_n => rst_n, scl_in => scl_in,
sda_in => sda_in, start_detected => start_detected);
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 20 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)
begin
if rising_edge(clk) then
if rst_n = '1' and start_detected = '1' then
pulses <= pulses + 1;
end if;
end if;
end process;
stim : process
variable errs : natural := 0;
variable base : natural := 0;
variable step : natural := 1;
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_pulses (want : in natural) is
begin
if pulses - base /= want then
report "step " & integer'image(step) & ": expected " &
integer'image(want) & " START pulse(s), saw " &
integer'image(pulses - base) severity error;
errs := errs + 1;
end if;
base := pulses;
step := step + 1;
end procedure;
procedure legal_bit (v : in std_logic) is
begin
scl_in <= '0'; waitn(2);
sda_in <= v; waitn(2);
scl_in <= '1'; waitn(3);
scl_in <= '0'; waitn(1);
end procedure;
procedure do_start is
begin
scl_in <= '1'; sda_in <= '1'; waitn(2);
sda_in <= '0'; waitn(3);
scl_in <= '0'; waitn(2);
end procedure;
procedure do_stop is
begin
scl_in <= '0'; sda_in <= '0'; waitn(2);
scl_in <= '1'; waitn(2);
sda_in <= '1'; waitn(3);
end procedure;
procedure sda_fall_while_low is
begin
scl_in <= '0'; sda_in <= '1'; waitn(2);
sda_in <= '0'; waitn(2);
scl_in <= '1'; waitn(2);
scl_in <= '0'; waitn(1);
end procedure;
-- BOUNDARY, MIRRORED: SDA falls in the same interval SCL FALLS -- not a START.
procedure fall_coincident_with_scl_fall is
begin
scl_in <= '1'; sda_in <= '1'; waitn(2);
sda_in <= '0'; scl_in <= '0'; waitn(3);
end procedure;
procedure fall_coincident_with_scl_rise is
begin
scl_in <= '0'; sda_in <= '1'; waitn(2);
sda_in <= '0'; scl_in <= '1'; waitn(3);
scl_in <= '0'; waitn(1);
end procedure;
begin
waitn(2);
if start_detected /= '0' then
report "pulse asserted during reset" severity error; errs := errs + 1; end if;
rst_n <= '1'; waitn(1); base := pulses;
legal_bit('0'); legal_bit('1'); legal_bit('1'); legal_bit('0');
expect_pulses(0);
do_start; expect_pulses(1);
do_stop; expect_pulses(0);
sda_fall_while_low; expect_pulses(0);
fall_coincident_with_scl_rise; expect_pulses(0);
fall_coincident_with_scl_fall; expect_pulses(0);
do_start; do_start; expect_pulses(2);
rst_n <= '0'; scl_in <= '1'; sda_in <= '1'; waitn(2);
rst_n <= '1'; waitn(1); base := pulses;
do_start; expect_pulses(1);
-- RESET HISTORY: a START in the very FIRST cycle after reset release must
-- not be missed -- reset assumes an IDLE bus, so the first sample already
-- has a valid history. Resetting the history LOW silently drops this START.
rst_n <= '0'; scl_in <= '1'; sda_in <= '1'; waitn(2);
rst_n <= '1'; sda_in <= '0'; waitn(3);
expect_pulses(1);
scl_in <= '0'; waitn(1);
if errs = 0 then
report "i2c_start_detector self-check complete: one pulse per START, all rejections held" severity note;
else
report "i2c_start_detector self-check FAILED" severity error;
end if;
test_done <= '1';
wait;
end process;
end architecture;6a. Cross-Language Parity
All three describe identical hardware, and the testbenches are the same eight steps in three idioms.
| SystemVerilog | Verilog-2005 | VHDL | |
|---|---|---|---|
| history registers | logic scl_q, sda_q | reg scl_q, sda_q | signal scl_q, sda_q : std_logic |
| reset history value | 1'b1 (idle bus) | 1'b1 | '1' |
| detection condition | && chain | & chain | and chain |
| output | registered logic | registered reg | registered std_logic |
| pulse counting in TB | int incremented | integer incremented | signal driven by one process, snapshotted by the checker |
| watchdog | #20000 then report and $finish | same | a watchdog process, guarded by a completion flag |
The VHDL testbench differs in structure for a language reason rather than a design one. A signal in VHDL may have one driver, so the pulse counter is driven by a dedicated counting process and the checker reads it and snapshots a baseline, instead of clearing a shared variable the way the Verilog versions do. The measurements are identical; the bookkeeping respects the language.
The watchdog needs a second thought in VHDL, and it is worth spelling out because every testbench in this module shares the idiom. In the Verilog versions a watchdog is one line — wait, print, $finish — and $finish ends the simulation, so a watchdog that has not fired by the time the checker finishes is simply never reached. VHDL has no $finish in the same sense: the checker ends with wait, which suspends that process and leaves the simulation running until the time limit. A naive VHDL watchdog therefore fires on every run, including successful ones, and reports a failure that did not happen.
The fix is a completion flag the checker raises just before it suspends, which the watchdog tests before complaining:
signal test_done : std_logic := '0'; -- raised by the checker at the end
watchdog : process
begin
wait for 20 us;
if test_done = '0' then -- only complain if nothing finished
report "watchdog expired ..." severity failure;
end if;
wait;
end process;
Verified both ways round: with correct RTL the watchdog stays silent, and with
a deliberately stalled design (the START hold never completing, so `done` never
asserts) it reports a failure at its limit instead of the run ending quietly.
A watchdog that has only ever been observed NOT firing has not been tested.That last line is the general point. A watchdog is error-handling code, and error-handling code that has never executed is unverified — which is why it is worth deliberately stalling a design once to watch the watchdog work.
Verified execution. All three testbenches were compiled and run, and all three complete at the same simulated time:
| language | simulator | result | completes at |
|---|---|---|---|
| SystemVerilog | Icarus Verilog, -g2012 | PASS | 970 ns |
| Verilog-2005 | Icarus Verilog, -g2005 | PASS | 970 ns |
| VHDL | nvc 1.23.0 | PASS | 970 ns |
7. What the Testbench Has To Cover
A detector is a classifier, so the interesting tests are the things it must refuse, not the thing it must find. Six of the eight steps are rejections.
| step | stimulus | required result | why it is in the suite |
|---|---|---|---|
| 1 | four ordinary data bits | no pulse | the common case must be silent, or the bus is unusable |
| 2 | a real START | exactly one pulse | an event, not a level |
| 3 | a STOP | no pulse | the opposite edge must not be confused with this one |
| 4 | SDA falls while SCL is low | no pulse | ordinary data movement |
| 5 | SDA falls as SCL rises | no pulse | boundary: ambiguous in a sampled model |
| 5b | SDA falls as SCL falls | no pulse | the mirror boundary — the other SCL term |
| 6 | two STARTs in a row | exactly two pulses | repeatable, not a one-shot |
| 7, 8 | reset, then a START in the first cycle after release | one pulse | what the detector assumed before it was watching |
Step 2's "exactly one" is doing real work. A detector that asserted a level for as long as the condition held would pass a test that only asked did you see a START, and would then break every consumer that counts events. Counting pulses rather than checking a flag is what makes the distinction testable.
Step 8 is the one that had to be added, and §8 explains why.
i2c_start_detector — one pulse per START
10 cycles8. Mutation Testing — and the One That Survived
Five faults were injected into the detector and the testbench run against each.
| mutation | what it breaks | result |
|---|---|---|
| drop the previous-SCL guard term | over-reports on the SCL rising edge | FAIL — caught by step 5 |
| drop the current-SCL guard term | over-reports on the SCL falling edge | FAIL — caught by step 5b |
| detect the rising edge instead | finds STOPs and misses STARTs | FAIL — step 2 saw 0 pulses |
| report a level instead of a pulse | event consumers double-count | FAIL — step 2 saw more than one |
| reset the history registers LOW | see below | initially PASSED |
All five are caught now. The fifth is the one worth the space.
The mutation that survived. Resetting scl_q and sda_q to zero instead of one left the whole suite passing. It is tempting to call that mutation behaviourally equivalent — the history registers are overwritten with real bus samples on the very next clock, so how could their initial value matter?
It matters for exactly one cycle, and that cycle is a real operating condition. Consider what the detector must do if a START arrives in the first cycle after reset is released. The golden design resets its history to the idle bus, both lines high, so on that first evaluation it already holds a valid "SDA was high, SCL was high" history and correctly reports the START. The mutant holds scl_q low, cannot satisfy the guard, and silently drops a real START.
That is not a contrived case. It is what happens whenever a monitor is enabled onto a live bus, or a controller's reset is released while another controller is mid-transaction, or a device comes out of a low-power state. A detector that misses the START misses the address that follows it, and then misinterprets the rest of the transfer — which is a far more confusing failure than not detecting anything at all.
The fix was a test, not a change to the RTL: hold reset with the bus idle, then release reset and drop SDA in the same interval. The mutant now fails it.
The general lesson. A reset value that is immediately overwritten still has observable behaviour, and the observation window is the first cycle. Asking "does this reset value matter?" is the same question as "what must this block do in the cycle it starts working?" — and for anything that watches an external bus, the answer is rarely "nothing", because the bus does not wait to be watched. A mutation that survives is usually telling you something true about your verification.
9. An Assertion for the Same Property
The detector's contract can be stated directly as a property, and it is worth comparing.
// The output must pulse exactly when the four-term condition held on the
// previous cycle -- an if-and-only-if, so it catches both a missed START and
// a spurious one. $past is what makes the two-sample history expressible.
property start_pulse_iff_framing_edge;
@(posedge clk) disable iff (!rst_n)
start_detected <==> $past(scl_in && $past(scl_in) && $past(sda_in) && !sda_in);
endproperty
// And the event-not-level property: two consecutive asserted cycles would
// mean a level, and every consumer that counts events would double-count.
property start_is_a_single_cycle_pulse;
@(posedge clk) disable iff (!rst_n)
start_detected |=> !start_detected;
endpropertyThe second property is the more valuable of the two in practice, and it illustrates something about what assertions are good at. "This output is never asserted for two cycles in a row" is a statement about every cycle of every test, forever, and no test has to remember to check it. The procedural testbench in §6 establishes the same thing by counting pulses, which works but only in the scenarios it runs.
The first property is nearly a restatement of the implementation, which is a known hazard: an assertion derived by transcribing the RTL will agree with the RTL even when both are wrong. It earns its place only because it was written from the specification sentence — an SDA high-to-low transition while SCL is high — rather than from the code.
Note also that both are clocked on the internal clock, and therefore inherit Chapter 4.2's limitation: they are assertions about samples, not about the bus. Neither can say anything about how long SCL was actually high in nanoseconds. That is Chapter 5.5's problem and it needs timestamps, not cycles.
10. Verification Connection — A Detector Is Not a Monitor
It is worth separating two components that both watch SDA and SCL, because conflating them produces a verification environment that cannot localise a failure.
A framing detector answers one question: did this specific waveform relationship occur? Its output is an event, it has no notion of a transfer, and it cannot be wrong about anything except edges. The design in §6 is one.
A protocol monitor reconstructs meaning. It consumes framing events, then the address byte, then direction, then payload, and produces a transaction object for a scoreboard. It has a state machine, it knows what phase a transfer is in, and it can be wrong in many more ways.
The reason to keep them apart is diagnostic. When a monitor reports "no transaction seen", the question is immediately whether the bus carried no START or whether the monitor mishandled one that was there — and if framing detection is buried inside the monitor's state machine, there is no way to tell from the outside. A monitor built on a separate, separately-verified detector reduces that to a single check: did the detector fire?
This is also where the reset-value lesson from §8 comes back. A monitor connected to a bus that is already active is the normal case in a real verification environment — testbenches enable components between transactions, but hardware debug does not get that courtesy. Module 21 builds the monitor properly.
11. FPGA and ASIC Implications
The detector costs almost nothing, and that is the point. Two flip-flops and a four-input gate. There is never a reason to share one, and there is never a reason to implement framing detection in software when the hardware is this small — which is exactly why UM10204 notes that detection is easy if the device has the interfacing hardware and awkward if it does not.
The inputs must be synchronised before they reach it. SDA and SCL are asynchronous to the internal clock: they are driven by other devices, filtered by board RC, and have no relationship to the sampling clock at all. Sampling an asynchronous signal directly into logic that then fans out to several places invites metastability, and the standard remedy is a synchroniser on each line before anything else uses it. The detector in §6 assumes that has already happened — its scl_in and sda_in are already-synchronised signals, not raw pads. Module 19 owns the synchroniser and the oversampling filter, and getting it wrong produces intermittent false STARTs that no amount of protocol debugging will explain.
Sampling rate determines what the detector can see, and the specification names a floor. A START whose SDA edge and subsequent SCL fall both land inside one sampling interval is invisible to a sampled detector. UM10204's note that a software implementation must sample SDA at least twice per clock period is the same constraint from the other direction. On an FPGA with a fast internal clock this is comfortable; on a slow clock or a deeply-filtered input it is a real design calculation.
On an ASIC the detector is usually always-on. A peripheral that can be woken by bus activity needs framing detection running in a domain that is not gated off, which makes those two flip-flops part of the always-on partition and their reset a power-on reset rather than a peripheral reset. That changes the answer to §8's question about reset values: the detector may come out of reset onto a bus that has been live for hours.
12. Debugging — The START That Was Never There
A controller whose first transfer after reset was always ignored
Pitfall — pulling SCL low in the same decision that issues the START
// A controller issues a START by driving both lines in what looks like the
// natural order -- get the bus to the state the byte engine expects, in as few
// cycles as possible:
//
// ST_IDLE:
// if (start_req) begin
// sda_drive_low <= 1'b1; // SDA falls -- "this is the START"
// scl_drive_low <= 1'b1; // and take the clock low, ready for bit 0
// state <= ST_BYTE;
// end
//
// Both assignments are in the same clocked block, so both take effect on the
// same edge. To the designer this reads as "assert the START and begin", and the
// simulation waveform looks plausible: SDA goes low, SCL goes low, the byte
// engine starts shifting, and the testbench's own monitor -- which was written
// to look for 'SDA low and SCL low' as the start of a transfer -- agrees.No target ever acknowledges the address. Every transfer NACKs.
The capture is the confusing part. SDA and SCL both go low at the beginning of the transfer, the address bits are clocked out correctly, the bit timing measures within spec, and the waveform looks like a textbook I2C transfer to anyone scanning it quickly. A decoder in a logic analyser may even display the address and data, because many decoders resynchronise on the first falling SCL edge rather than insisting on a framing event.
Swapping in a different target changes nothing. Slowing the clock down changes nothing. Adding pull-up strength changes nothing. Every hypothesis that treats this as an electrical or a timing problem fails, because it is neither.
There was no START condition on the bus. Not a marginal one -- none.
A START is an SDA edge that occurs WHILE SCL IS HIGH. Driving both lines low on the same clock edge means SDA's falling edge and SCL's falling edge are simultaneous, so at no point is there an interval with SCL high and SDA newly low. Every target's framing detector -- which requires SCL high across the transition, for the reasons in section 5 -- correctly sees nothing. The targets are not ignoring the START; they never received one, so they never began listening for an address, so the address bits that followed were clocked into devices that were not paying attention.
The reason this survives review is that the design looks like it asserts the START, and the reason it survives simulation is that the testbench monitor was written with the same misunderstanding as the RTL. A monitor that defines the beginning of a transfer as 'both lines low' will agree with a controller that produces 'both lines low', and the pair of them will be consistently wrong. That is the specific hazard of writing the checker from the same mental model as the design.
Note also the ordering constraint this violates: section 4 pointed out that the clock cannot begin until the framing edge has already happened, and that the specification words the requirement as 'after this period, the first clock pulse is generated'. The bug is that ordering collapsed to zero.
// Separate the two edges into two states, with the framing edge strictly first
// and a counted interval between them:
//
// ST_IDLE: if (start_req) begin
// sda_drive_low <= 1'b1; // the START edge, alone
// cnt <= T_HD_STA - 1; // SCL is still HIGH here
// state <= ST_START_HOLD;
// end
// ST_START_HOLD: if (cnt != 0) cnt <= cnt - 1;
// else begin
// scl_drive_low <= 1'b1; // NOW the first clock pulse
// state <= ST_BYTE;
// end
//
// That is exactly the first two states of the sequencer Chapter 5.5 builds, and
// the interval between them is tHD;STA.
//
// The verification fix matters more than the RTL fix, because the RTL fix is
// obvious once the bug is understood and the verification gap is what let it
// ship. Two changes:
//
// 1. Write the framing check from the SPECIFICATION SENTENCE, not from the
// design's intent: 'an SDA high-to-low transition WHILE SCL IS HIGH'. The
// detector in section 6 is that sentence, and it reports zero STARTs for
// this controller -- immediately, on the first test.
//
// 2. Assert the count, not just the occurrence. A test that checks 'a START
// was seen' can pass for the wrong reason; a test that checks 'exactly one
// START was seen before the first address bit' cannot.
//
// The bench habit that finds it in minutes: trigger the analyser on the framing
// condition itself rather than on activity. An analyser set to trigger on 'SDA
// falling while SCL is high' never triggers on this board, and that null result
// is the diagnosis.13. Common Misconceptions
"A START is SDA going low." A START is SDA going low while SCL is high. SDA goes low in the ordinary course of transmitting a zero, many times per transfer. §2 shows the identical edge in both roles, and §12 is what happens to a design that drops the qualifier.
"The controller pulls SCL low to start a transfer." It pulls SCL low after the START, to begin the first clock pulse. The framing edge comes first and the clock waits for it; reversing the order produces no START at all.
"A START tells the addressed device to get ready." There is no addressed device yet — the address has not been sent. A START is a broadcast that every device on the segment acts on, and most of them will conclude within a byte that the transfer is not theirs.
"A device that is idle can ignore the bus until it is addressed." It cannot know it is being addressed without first seeing the START and then shifting in the address byte. Framing detection is a continuous obligation.
"Testing SCL on the current sample is good enough." It is wrong at one boundary, and the mirror simplification is wrong at the other. Both SCL terms are load-bearing, and §8 injects a fault into each to prove it.
"A reset value that gets overwritten immediately cannot matter." It determines behaviour in the first cycle, and for a block watching an external bus the first cycle is a real operating condition. §8 is a worked example of that assumption being wrong.
"If the logic analyser decodes the transfer, the framing was legal." Many decoders resynchronise on clock activity rather than insisting on a framing event, so a decoder can display a plausible address and payload for a transfer that had no START at all. §12 depends on exactly this.
14. Reason It Through
A controller drives SDA low one cycle before it drives SCL low. Is that a legal START?
It produces a START edge, because there is an interval with SCL high and SDA newly low — so unlike §12's design, this one does put a framing event on the bus. Whether it is legal is a different question, and the answer is that one cycle is almost certainly too short: the interval between the START edge and the first clock pulse has a specified minimum, and one internal clock cycle of a fast FPGA is far below it. The distinction matters because the two failures look nothing alike. No START means no target ever responds; a too-short START means targets respond on some boards, at some temperatures. Chapter 5.5 quantifies it.
Why can a detector for this event be stateless while a classifier cannot?
Because the definition refers only to the two conductors. "An SDA high-to-low transition while SCL is high" is answerable from two samples of two signals and nothing else. Deciding whether that START is a first START or a repeated one requires knowing whether a transfer was already in progress, which is not visible on the conductors at that instant — so it requires state that the detector has no need for. Chapter 5.4 adds exactly that state and nothing more.
A monitor is enabled onto a bus in the middle of a transfer, and immediately reports a START. Is it broken?
Not necessarily — it depends on what the bus was doing. If a genuine START arrived in the monitor's first sampling interval, reporting it is correct, and §8 is about making sure the monitor can. If no edge occurred at all and the monitor reported one anyway, then its reset history did not match the bus it was connected to: it assumed SDA was high, the bus had SDA low, and the first real sample looked like a falling edge. That failure mode is the reason a monitor enabled onto a live bus should either be reset while the bus is idle or declare its state unknown until it has seen a STOP.
Two of the four terms in the detection condition concern SCL. Why is dropping either one a different bug?
Because each rejects a different boundary. The previous-SCL term rejects an SDA fall that coincides with SCL rising; the current-SCL term rejects one that coincides with SCL falling. A mutant missing the first over-reports at the rising boundary and is correct everywhere else; a mutant missing the second over-reports at the falling boundary and is correct everywhere else. A test suite that exercises neither boundary cannot distinguish either mutant from the golden design — which is why both boundary tests are in §7, and why Chapter 4.2 had to learn the same thing about its stability checker.
Your testbench monitor and your controller RTL were written by the same engineer from the same notes. What class of bug does that arrangement not catch?
Any bug in the shared understanding. The monitor will agree with the RTL precisely where both misread the specification, which is the situation in §12 — a controller that produced "both lines low" and a monitor that looked for "both lines low" were consistent with each other and inconsistent with the bus. The defence is to write the checker from the specification sentence rather than from the design's intent, which is a discipline rather than a tool, and to prefer checks that count events over checks that test flags.
15. Understanding Check
16. Summary
A START is a high-to-low SDA transition while SCL is high, and both halves of that sentence are load-bearing. The identical SDA edge during a low phase is an ordinary data zero.
Only the controller can generate one, and that follows from clock ownership: placing an edge inside a high phase requires deciding when the high phase is.
It is a broadcast, and it is unconditional. The bus becomes busy, every target begins listening for an address, and every target abandons whatever partial protocol state it held. That last rule is what makes the bus recoverable, and it is what makes a repeated START useful.
The framing edge precedes the first clock pulse, with a specified minimum interval between them. Collapsing that ordering does not produce a marginal START — it produces none, which is §12's failure.
Detection needs two samples and four terms. One sample of history per conductor, and a condition requiring SCL high on both samples. Each of the four terms rejects a different non-START case, and the two SCL terms are each wrong at only one boundary — so both boundaries need their own test.
A reset value that is immediately overwritten is still observable, for the length of one cycle. §8's surviving mutation is a worked example, and the case it exposed — a START in the first cycle after reset release — is ordinary rather than exotic.
A detector is not a monitor. Keeping them separate is what lets a failing environment distinguish "the bus carried no START" from "the monitor mishandled one".
17. What Comes Next
The bus can now be started, and the event that starts it can be detected and trusted. What has not been established is how a transfer ends — and it turns out that ending one is the less symmetric half of the pair, because releasing the bus is not the same as making it available.
Chapter 5.3 takes the rising edge: the STOP condition, the difference between released and free that Chapter 5.1 introduced, and a tracker that keeps the two apart — the design whose absence caused that chapter's debugging case.
Browse the full path on the I²C tutorials index. For the reserved space this edge comes from, see Bus Idle and the Framing Primitives; for the rule that makes it detectable, The Data-Valid Rule.
Continue learning
Related tutorials
- Related topic
The STOP Condition and Releasing the Bus
STOP is SDA rising while SCL is high — the mirror edge of START, with obligations that are not mirrored at all. Releasing the bus is not the same as making it available, and the interval between the two is where a whole class of intermittent failure lives.
- 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
START and STOP Generation — The Framing Sequencer
The edges the bit engine cannot produce, and why that is structural rather than stylistic. Builds the four framing sequences from their Table 10 intervals, shows why every SCL release inside them must wait for the readback or the START is not a START at all, and finds a defect that no bus-level check could have caught.
- Related topic
The Data-Valid Rule — SDA Stable While SCL Is High
One sentence governs every bit on an I²C bus, and it is derived rather than decreed: the receiver needs a settled value at the instant it looks. What falls out is that an SDA edge while SCL is HIGH cannot be data — which is why the bus reserves it for framing.
