I²C · Module 18
START and STOP Detection Inside a Slave
Detection is two AND gates. The substance is what a START resets, and the distinction that is not on the wire at all — a first START and a repeated START are the identical edge, separated only by state the target keeps itself.
Chapter 18.2 gave the target five facts: two synchronised levels and four one-cycle edge pulses. This chapter turns them into the three events that structure every transfer.
The detection is almost nothing. Two lines of RTL:
start_now = sda_fall && scl_q
stop_now = sda_rise && scl_qThe chapter is about everything around them.
1. The Rule, and Why It Needs Both Lines
So framing is an SDA edge qualified by an SCL level — which is exactly why 18.2 exports both and not just edges.
2. A Slave's Detector Is Not the Master's Framer Reversed
Chapter 17.5 generated these edges. It knew when they were coming, so it could hold SDA for tHD;STA and count tSU;STO — it chose the instants.
A target has no such knowledge. By the time it observes the edge, the edge has already happened. Its only job is to classify it and reset the right state.
3. The Distinction That Is Not on the Wire
That is why this block produces bus_active as a level, and why every later chapter is gated on it. It is also why the block reports restart_pulse separately: Chapter 18.10 needs to know which kind arrived, because a repeated START must restart the frame while leaving a register pointer alive.
This block does not decide what survives. It only reports which kind of START it was.
4. The Trap: A Repeated START Begins by Releasing SDA
Look at how a master builds one (17.5 §2): mid-transfer SDA may be low from a data zero, and a line already low cannot fall. So the sequence lifts SDA first, while SCL is still low, then releases SCL, then drops SDA.
Test T7 asserts that no STOP is reported anywhere inside a repeated START sequence.
5. What Is Deliberately Not Here
A slave that silently absorbs a STOP arriving part-way through a byte cannot be debugged from outside the chip, which is the whole argument for reporting a condition this block does nothing about.
6. Framing, and Two Non-Events
One START, then two SDA transitions that are not framing
10 cyclesIntervals 7 and 9 are the point of the figure. Both are SDA transitions; neither produces an event, because SCL is low. A detector that ignored the level would fire twice there — and the second one, being a fall, would restart the address phase in the middle of a byte.
The two figures are separate deliberately: one is about when an event is an event, the other about which event it is. A composite would obscure both.
7. The Detector, in Three Languages
// -----------------------------------------------------------------------------
// i2c_slave_framing.sv
// START, repeated START and STOP, from the slave's side of the wire.
//
// THE RULE, and it is two signals evaluated together. §3.1.1:
//
// START: "A HIGH to LOW transition on the SDA line while SCL is HIGH"
// STOP: "A LOW to HIGH transition on the SDA line while SCL is HIGH"
//
// So framing is an SDA EDGE qualified by an SCL LEVEL. Chapter 18.2 produces both, and
// this block is almost entirely those two lines:
//
// start_pulse = sda_fall && scl_q
// stop_pulse = sda_rise && scl_q
//
// WHY A SLAVE'S FRAMING DETECTOR IS NOT THE MASTER'S FRAMER TURNED ROUND. Module 17.5
// GENERATED these edges and therefore knew when they were coming; it could hold SDA for
// tHD;STA because it chose the instant. A slave has no such knowledge: the edge has
// already happened by the time it is observed, and the slave's only job is to classify
// it correctly and reset the right state.
//
// AND THE HARD PART IS WHAT IT RESETS, not what it detects. Detection is two AND gates.
// The design decision is that a START -- of either kind -- must return the slave's
// PROTOCOL state to "expecting an address byte", because §3.1.10's note 4 requires it:
//
// note 4, verbatim: "A START condition immediately followed by a STOP condition
// (void message) is an illegal format. Many devices however are designed to operate
// properly under this condition." And the operative requirement for a target is that
// a repeated START restarts the FRAME.
//
// So this block produces the one signal every later chapter is gated on -- `bus_active`
// -- and the distinction between a first START and a repeated one, which is not on the
// wire and is state:
//
// not active + START = a first START
// active + START = a repeated START
//
// Chapter 18.10 is where the consequence lives: framing state restarts, and application
// state (a register pointer) may deliberately survive. This block only reports which
// kind of START it was; it does not decide what survives.
//
// WHAT IS DELIBERATELY NOT HERE. No bit counter, no address register, no acknowledge.
// A framing detector that also counted bits would have to be reset by its own output,
// and the two jobs have different reset conditions -- which is how a slave ends up
// unable to recover from a repeated START arriving mid-byte.
// -----------------------------------------------------------------------------
module i2c_slave_framing #(
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
// From Chapter 18.2. This block reads NOTHING else: no pins, no timing.
input logic scl_q,
input logic sda_rise,
input logic sda_fall,
// The three framing events, one cycle each.
output logic start_pulse, // a START of either kind
output logic restart_pulse, // ... and it arrived while a transfer was open
output logic stop_pulse,
// The level every later chapter is gated on: between a START and a STOP.
output logic bus_active,
// Reported, not acted on: a START or STOP that arrived part-way through a byte.
// Chapter 18.11 decides the policy; this block only counts it, because a slave that
// silently absorbs mid-byte framing cannot be debugged from the outside.
input logic mid_byte, // from the bit counter: a byte is in progress
output logic framing_midbyte,
output logic [CNT_W-1:0] n_starts,
output logic [CNT_W-1:0] n_restarts,
output logic [CNT_W-1:0] n_stops
);
// The two AND gates that are the whole of §3.1.1, from the slave's side.
//
// Note what is NOT qualified: there is no check that SDA was previously high before
// a fall, because `sda_fall` already means exactly that -- Chapter 18.2 produced it
// from a level change. Re-deriving the precondition here would be a second source of
// truth about the same wire.
wire start_now = sda_fall & scl_q;
wire stop_now = sda_rise & scl_q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
start_pulse <= 1'b0;
restart_pulse <= 1'b0;
stop_pulse <= 1'b0;
// A slave out of reset must assume NO transfer is in progress. Assuming one
// was open would make it try to interpret the middle of somebody else's
// transaction as an address byte.
bus_active <= 1'b0;
framing_midbyte <= 1'b0;
n_starts <= {CNT_W{1'b0}};
n_restarts <= {CNT_W{1'b0}};
n_stops <= {CNT_W{1'b0}};
end else begin
start_pulse <= 1'b0;
restart_pulse <= 1'b0;
stop_pulse <= 1'b0;
framing_midbyte <= 1'b0;
if (start_now) begin
start_pulse <= 1'b1;
// The ONLY thing that distinguishes a repeated START from a first one is
// whether a transfer was already open. It is not on the wire.
if (bus_active) begin
restart_pulse <= 1'b1;
n_restarts <= n_restarts + 1'b1;
end else begin
n_starts <= n_starts + 1'b1;
end
bus_active <= 1'b1;
if (mid_byte) framing_midbyte <= 1'b1;
end else if (stop_now) begin
stop_pulse <= 1'b1;
n_stops <= n_stops + 1'b1;
bus_active <= 1'b0;
if (mid_byte) framing_midbyte <= 1'b1;
end
end
end
endmodule // -----------------------------------------------------------------------------
// i2c_slave_framing.v
// START, repeated START and STOP, from the slave's side of the wire.
//
// THE RULE, and it is two signals evaluated together. §3.1.1:
//
// START: "A HIGH to LOW transition on the SDA line while SCL is HIGH"
// STOP: "A LOW to HIGH transition on the SDA line while SCL is HIGH"
//
// So framing is an SDA EDGE qualified by an SCL LEVEL. Chapter 18.2 produces both, and
// this block is almost entirely those two lines:
//
// start_pulse = sda_fall && scl_q
// stop_pulse = sda_rise && scl_q
//
// WHY A SLAVE'S FRAMING DETECTOR IS NOT THE MASTER'S FRAMER TURNED ROUND. Module 17.5
// GENERATED these edges and therefore knew when they were coming; it could hold SDA for
// tHD;STA because it chose the instant. A slave has no such knowledge: the edge has
// already happened by the time it is observed, and the slave's only job is to classify
// it correctly and reset the right state.
//
// AND THE HARD PART IS WHAT IT RESETS, not what it detects. Detection is two AND gates.
// The design decision is that a START -- of either kind -- must return the slave's
// PROTOCOL state to "expecting an address byte", because §3.1.10's note 4 requires it:
//
// note 4, verbatim: "A START condition immediately followed by a STOP condition
// (void message) is an illegal format. Many devices however are designed to operate
// properly under this condition." And the operative requirement for a target is that
// a repeated START restarts the FRAME.
//
// So this block produces the one signal every later chapter is gated on -- `bus_active`
// -- and the distinction between a first START and a repeated one, which is not on the
// wire and is state:
//
// not active + START = a first START
// active + START = a repeated START
//
// Chapter 18.10 is where the consequence lives: framing state restarts, and application
// state (a register pointer) may deliberately survive. This block only reports which
// kind of START it was; it does not decide what survives.
//
// WHAT IS DELIBERATELY NOT HERE. No bit counter, no address register, no acknowledge.
// A framing detector that also counted bits would have to be reset by its own output,
// and the two jobs have different reset conditions -- which is how a slave ends up
// unable to recover from a repeated START arriving mid-byte.
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
// -----------------------------------------------------------------------------
module i2c_slave_framing #(
parameter integer CNT_W = 16
) (
input wire clk,
input wire rst_n,
// From Chapter 18.2. This block reads NOTHING else: no pins, no timing.
input wire scl_q,
input wire sda_rise,
input wire sda_fall,
// The three framing events, one cycle each.
output reg start_pulse, // a START of either kind
output reg restart_pulse, // ... and it arrived while a transfer was open
output reg stop_pulse,
// The level every later chapter is gated on: between a START and a STOP.
output reg bus_active,
// Reported, not acted on: a START or STOP that arrived part-way through a byte.
// Chapter 18.11 decides the policy; this block only counts it, because a slave that
// silently absorbs mid-byte framing cannot be debugged from the outside.
input wire mid_byte, // from the bit counter: a byte is in progress
output reg framing_midbyte,
output reg [CNT_W-1:0] n_starts,
output reg [CNT_W-1:0] n_restarts,
output reg [CNT_W-1:0] n_stops
);
// The two AND gates that are the whole of §3.1.1, from the slave's side.
//
// Note what is NOT qualified: there is no check that SDA was previously high before
// a fall, because `sda_fall` already means exactly that -- Chapter 18.2 produced it
// from a level change. Re-deriving the precondition here would be a second source of
// truth about the same wire.
wire start_now = sda_fall & scl_q;
wire stop_now = sda_rise & scl_q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
start_pulse <= 1'b0;
restart_pulse <= 1'b0;
stop_pulse <= 1'b0;
// A slave out of reset must assume NO transfer is in progress. Assuming one
// was open would make it try to interpret the middle of somebody else's
// transaction as an address byte.
bus_active <= 1'b0;
framing_midbyte <= 1'b0;
n_starts <= {CNT_W{1'b0}};
n_restarts <= {CNT_W{1'b0}};
n_stops <= {CNT_W{1'b0}};
end else begin
start_pulse <= 1'b0;
restart_pulse <= 1'b0;
stop_pulse <= 1'b0;
framing_midbyte <= 1'b0;
if (start_now) begin
start_pulse <= 1'b1;
// The ONLY thing that distinguishes a repeated START from a first one is
// whether a transfer was already open. It is not on the wire.
if (bus_active) begin
restart_pulse <= 1'b1;
n_restarts <= n_restarts + 1'b1;
end else begin
n_starts <= n_starts + 1'b1;
end
bus_active <= 1'b1;
if (mid_byte) framing_midbyte <= 1'b1;
end else if (stop_now) begin
stop_pulse <= 1'b1;
n_stops <= n_stops + 1'b1;
bus_active <= 1'b0;
if (mid_byte) framing_midbyte <= 1'b1;
end
end
end
endmodule -- -----------------------------------------------------------------------------
-- i2c_slave_framing.vhd
-- START, repeated START and STOP from the slave's side, in VHDL. Same ports, same
-- generic, same reset values and same one-cycle pulse timing as the SystemVerilog and
-- Verilog versions.
-- -----------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_slave_framing is
generic (
CNT_W : positive := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
-- From Chapter 18.2. This block reads NOTHING else: no pins, no timing.
scl_q : in std_logic;
sda_rise : in std_logic;
sda_fall : in std_logic;
-- The three framing events, one cycle each.
start_pulse : out std_logic;
restart_pulse : out std_logic;
stop_pulse : out std_logic;
-- The level every later chapter is gated on.
bus_active : out std_logic;
-- Reported, not acted on.
mid_byte : in std_logic;
framing_midbyte : out std_logic;
n_starts : out unsigned(CNT_W-1 downto 0);
n_restarts : out unsigned(CNT_W-1 downto 0);
n_stops : out unsigned(CNT_W-1 downto 0)
);
end entity i2c_slave_framing;
architecture rtl of i2c_slave_framing is
-- The two AND gates that are the whole of §3.1.1, from the slave's side.
signal start_now, stop_now : std_logic;
signal r_start, r_restart, r_stop, r_active, r_mid : std_logic := '0';
signal c_sta, c_rs, c_sto : unsigned(CNT_W-1 downto 0) := (others => '0');
begin
start_now <= sda_fall and scl_q;
stop_now <= sda_rise and scl_q;
process (clk, rst_n)
begin
if rst_n = '0' then
r_start <= '0';
r_restart <= '0';
r_stop <= '0';
-- A slave out of reset must assume NO transfer is in progress.
r_active <= '0';
r_mid <= '0';
c_sta <= (others => '0');
c_rs <= (others => '0');
c_sto <= (others => '0');
elsif rising_edge(clk) then
r_start <= '0';
r_restart <= '0';
r_stop <= '0';
r_mid <= '0';
if start_now = '1' then
r_start <= '1';
-- The ONLY thing that distinguishes a repeated START from a first one is
-- whether a transfer was already open. It is not on the wire.
if r_active = '1' then
r_restart <= '1';
c_rs <= c_rs + 1;
else
c_sta <= c_sta + 1;
end if;
r_active <= '1';
if mid_byte = '1' then r_mid <= '1'; end if;
elsif stop_now = '1' then
r_stop <= '1';
c_sto <= c_sto + 1;
r_active <= '0';
if mid_byte = '1' then r_mid <= '1'; end if;
end if;
end if;
end process;
start_pulse <= r_start;
restart_pulse <= r_restart;
stop_pulse <= r_stop;
bus_active <= r_active;
framing_midbyte <= r_mid;
n_starts <= c_sta;
n_restarts <= c_rs;
n_stops <= c_sto;
end architecture rtl;7a. The testbenches
Thirteen checks. The bench is a hand-written controller: SDA changes only while SCL is LOW for data and only while SCL is HIGH for framing. That distinction is the chapter's subject, so a bench that blurred it could not test the block.
| # | Test | Property |
|---|---|---|
| T1 | reset assumes no transfer | otherwise it reads somebody else's data as an address |
| T2 | a quiet bus produces nothing | the false-positive test |
| T3 | a START is one pulse, and opens the transfer | |
| T4 | the central negative test — SDA moving while SCL is LOW is data | |
| T5 | a STOP closes the transfer | |
| T6 | a repeated START is the same edge | only the state differs |
| T7 | and the SDA lift inside it is not a STOP | §4's trap |
| T8 | after a STOP the next START is a first START again | the classification must clear |
| T9 | mid-byte framing on a STOP is reported | not absorbed |
| T9b | mid-byte framing on a repeated START is reported too | the other half; see §8 |
| T10 | and it is not reported between bytes | the false-positive half |
| T11 | three complete frames, nothing accumulates | |
| T12 | reset mid-transfer returns to idle and does not resume |
// -----------------------------------------------------------------------------
// i2c_slave_framing_tb.sv
// Independent oracle for i2c_slave_framing, driven through the real front end.
//
// The bench is a hand-written CONTROLLER: it drives the two pins the way a master
// does, with SDA changed only while SCL is low for data and only while SCL is high for
// framing. That matters -- a bench that drove framing and data the same way could not
// distinguish a detector that checks the SCL level from one that does not.
//
// The DUT is instantiated behind i2c_slave_sync, so what is being tested is the pair as
// the slave will actually use it. Every wait is bounded.
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module i2c_slave_framing_tb;
// A deliberately slow bus relative to clk: eight cycles per half period, so the
// synchroniser's two-cycle latency is a small fraction of a phase, as on real
// hardware. Chapter 18.5 is where the ratio starts to constrain the design.
localparam integer HALF = 8;
logic clk = 1'b0, rst_n = 1'b0;
logic m_scl = 1'b1, m_sda = 1'b1; // the controller's drive intent, active HIGH
// in the bench for readability
logic mid_byte = 1'b0;
// Open-drain resolution, exactly as the bus does it: the line is low if the
// controller pulls it low. Nothing else drives here.
wire scl_pin = m_scl;
wire sda_pin = m_sda;
logic scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall;
logic start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte;
logic [15:0] n_sta, n_rs, n_sto;
integer errors = 0;
integer n, k;
// ---- observers -------------------------------------------------------------
integer n_start_obs = 0, n_restart_obs = 0, n_stop_obs = 0, n_mid_obs = 0;
integer wide = 0;
logic sp_d = 1'b0, rp_d = 1'b0, tp_d = 1'b0;
always @(posedge clk) begin
if (rst_n) begin
if (start_pulse) n_start_obs <= n_start_obs + 1;
if (restart_pulse) n_restart_obs <= n_restart_obs + 1;
if (stop_pulse) n_stop_obs <= n_stop_obs + 1;
if (framing_midbyte) n_mid_obs <= n_mid_obs + 1;
if ((start_pulse & sp_d) | (restart_pulse & rp_d) | (stop_pulse & tp_d))
wide <= wide + 1;
end
sp_d <= start_pulse; rp_d <= restart_pulse; tp_d <= stop_pulse;
end
i2c_slave_sync #(.SYNC_DEPTH(2)) u_sync (
.clk(clk), .rst_n(rst_n), .scl_pin(scl_pin), .sda_pin(sda_pin),
.scl_q(scl_q), .sda_q(sda_q),
.scl_rise(scl_rise), .scl_fall(scl_fall),
.sda_rise(sda_rise), .sda_fall(sda_fall));
i2c_slave_framing #(.CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n),
.scl_q(scl_q), .sda_rise(sda_rise), .sda_fall(sda_fall),
.start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
.bus_active(bus_active), .mid_byte(mid_byte), .framing_midbyte(framing_midbyte),
.n_starts(n_sta), .n_restarts(n_rs), .n_stops(n_sto));
always #5 clk = ~clk;
task step; begin @(posedge clk); @(negedge clk); end endtask
task phase; begin for (n = 0; n < HALF; n = n + 1) step; end endtask
task do_reset;
begin
@(negedge clk); rst_n = 1'b0; m_scl = 1'b1; m_sda = 1'b1; mid_byte = 1'b0;
n_start_obs = 0; n_restart_obs = 0; n_stop_obs = 0; n_mid_obs = 0; wide = 0;
step; step;
@(negedge clk); rst_n = 1'b1;
phase;
end
endtask
// ---- controller-side stimulus ---------------------------------------------
//
// A START: SDA falls while SCL is HIGH. Then SCL is pulled low to begin the transfer.
task gen_start;
begin
@(negedge clk); m_sda = 1'b1; m_scl = 1'b1; phase;
@(negedge clk); m_sda = 1'b0; // the START edge
phase;
@(negedge clk); m_scl = 1'b0;
phase;
end
endtask
// A repeated START: release SDA while SCL is LOW (ordinary preparation), release
// SCL, then drop SDA while SCL is HIGH. The first move must NOT be seen as framing.
task gen_restart;
begin
@(negedge clk); m_scl = 1'b0; m_sda = 1'b1; phase; // SDA up, SCL still low
@(negedge clk); m_scl = 1'b1; phase; // SCL up
@(negedge clk); m_sda = 1'b0; phase; // the Sr edge
@(negedge clk); m_scl = 1'b0; phase;
end
endtask
// A STOP: SDA low, SCL released high, then SDA released while SCL is HIGH.
task gen_stop;
begin
@(negedge clk); m_scl = 1'b0; m_sda = 1'b0; phase;
@(negedge clk); m_scl = 1'b1; phase;
@(negedge clk); m_sda = 1'b1; // the STOP edge
phase;
end
endtask
// One ordinary data bit: SDA is changed only while SCL is LOW, then clocked.
task gen_bit (input b);
begin
@(negedge clk); m_scl = 1'b0; phase;
@(negedge clk); m_sda = b; phase; // changed in the LOW phase
@(negedge clk); m_scl = 1'b1; phase;
@(negedge clk); m_scl = 1'b0; phase;
end
endtask
task ck_bit (input [200*8:1] what, input g, input e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0b expected %0b", what, g, e);
errors = errors + 1;
end
end
endtask
task ck_int (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_slave_framing: an SDA edge qualified by an SCL level ===");
// ----------------------------------------------------------------
// T1. RESET ASSUMES NO TRANSFER. A slave that came up believing one was open
// would try to read the middle of somebody else's transaction as an address.
// ----------------------------------------------------------------
do_reset;
$display("T1 a slave out of reset assumes the bus is idle");
ck_bit("T1 no transfer open", bus_active, 1'b0);
ck_int("T1 no framing seen", n_start_obs + n_stop_obs, 0);
// ----------------------------------------------------------------
// T2. A QUIET BUS PRODUCES NOTHING. The false-positive test.
// ----------------------------------------------------------------
for (k = 0; k < 6; k = k + 1) phase;
$display("T2 a quiet bus produces no framing");
ck_int("T2 still nothing", n_start_obs + n_stop_obs, 0);
ck_bit("T2 still idle", bus_active, 1'b0);
// ----------------------------------------------------------------
// T3. A START. One pulse, one cycle wide, and the bus becomes active.
// ----------------------------------------------------------------
do_reset;
gen_start;
$display("T3 a START is one pulse, and it opens the transfer");
ck_int("T3 exactly one START", n_start_obs, 1);
ck_int("T3 and it was not a repeated one", n_restart_obs, 0);
ck_bit("T3 the bus is now active", bus_active, 1'b1);
ck_int("T3 the pulse was one cycle", wide, 0);
ck_int("T3 the counter agrees", n_sta, 1);
// ----------------------------------------------------------------
// T4. THE CENTRAL NEGATIVE TEST. An SDA transition while SCL is LOW is ordinary
// data preparation and is NOT framing. A detector that ignored the SCL level
// would report a START on every data bit that happens to be a zero -- which
// is most of them -- and the slave would restart its address phase mid-byte,
// forever.
// ----------------------------------------------------------------
n_start_obs = 0; n_stop_obs = 0; n_restart_obs = 0;
gen_bit(1'b0); // SDA falls while SCL is low
gen_bit(1'b1); // and rises while SCL is low
gen_bit(1'b0);
$display("T4 SDA moving while SCL is LOW is data, not framing");
ck_int("T4 no START was reported", n_start_obs, 0);
ck_int("T4 no STOP was reported", n_stop_obs, 0);
ck_bit("T4 and the transfer is still the same one", bus_active, 1'b1);
// ----------------------------------------------------------------
// T5. A STOP closes the transfer.
// ----------------------------------------------------------------
gen_stop;
$display("T5 a STOP closes the transfer");
ck_int("T5 exactly one STOP", n_stop_obs, 1);
ck_bit("T5 the bus is idle again", bus_active, 1'b0);
ck_int("T5 the counter agrees", n_sto, 1);
// ----------------------------------------------------------------
// T6. A REPEATED START, and the distinction that is NOT on the wire. The edge is
// identical to a first START; only `bus_active` separates them.
// ----------------------------------------------------------------
do_reset;
gen_start;
gen_bit(1'b1); gen_bit(1'b0);
n_start_obs = 0; n_restart_obs = 0;
gen_restart;
$display("T6 a repeated START is the same edge; only the state differs");
ck_int("T6 a START was reported", n_start_obs, 1);
ck_int("T6 and it was flagged as a REPEATED start", n_restart_obs, 1);
ck_bit("T6 the transfer is still open", bus_active, 1'b1);
ck_int("T6 the restart counter moved", n_rs, 1);
// ----------------------------------------------------------------
// T7. AND THE SDA RELEASE THAT PRECEDES A REPEATED START IS NOT A STOP. This is
// the trap: a repeated START begins by lifting SDA, and if that lift were
// classified as framing the slave would see STOP-then-START and end the
// transaction -- losing exactly the state a repeated START exists to keep.
// ----------------------------------------------------------------
ck_int("T7 no STOP was reported during the repeated START", n_stop_obs, 0);
// ----------------------------------------------------------------
// T8. A FIRST START AFTER A STOP counts as a first START again, not a repeated
// one. The classification is stateful, so it must clear.
// ----------------------------------------------------------------
gen_stop;
n_start_obs = 0; n_restart_obs = 0;
gen_start;
$display("T8 after a STOP the next START is a first START again");
ck_int("T8 one START", n_start_obs, 1);
ck_int("T8 and NOT a repeated one", n_restart_obs, 0);
// ----------------------------------------------------------------
// T9. MID-BYTE FRAMING IS REPORTED, not absorbed. A slave that silently swallows
// a STOP arriving part-way through a byte cannot be debugged from outside,
// and Chapter 18.11 needs the report to decide a policy.
// ----------------------------------------------------------------
do_reset;
gen_start;
@(negedge clk); mid_byte = 1'b1; // the bit counter says a byte is in progress
n_mid_obs = 0;
gen_stop;
$display("T9 framing arriving mid-byte is reported rather than absorbed");
ck_int("T9 the mid-byte report fired", n_mid_obs, 1);
@(negedge clk); mid_byte = 1'b0;
// ----------------------------------------------------------------
// T9b. AND THE OTHER HALF: mid-byte framing on a repeated START. T9 tests the STOP
// path only, so the START path's report was untested -- and the two are
// separate branches. A repeated START arriving part-way through a byte is the
// more likely of the two in practice, because a master that aborts a transfer
// usually retries with a restart rather than a STOP.
// ----------------------------------------------------------------
do_reset;
gen_start;
@(negedge clk); mid_byte = 1'b1;
n_mid_obs = 0;
gen_restart;
$display("T9b a repeated START mid-byte is reported too, not only a STOP");
ck_int("T9b the mid-byte report fired on the restart", n_mid_obs, 1);
@(negedge clk); mid_byte = 1'b0;
// ----------------------------------------------------------------
// T10. AND IT IS NOT REPORTED WHEN NO BYTE IS IN PROGRESS. The false-positive
// half: an ordinary STOP between bytes is completely normal.
// ----------------------------------------------------------------
do_reset;
gen_start;
n_mid_obs = 0;
gen_stop;
$display("T10 an ordinary STOP between bytes is not a mid-byte report");
ck_int("T10 no mid-byte report", n_mid_obs, 0);
// ----------------------------------------------------------------
// T11. MANY TRANSFERS, and nothing accumulates. Three complete frames.
// ----------------------------------------------------------------
do_reset;
for (k = 0; k < 3; k = k + 1) begin
gen_start;
gen_bit(1'b1); gen_bit(1'b0); gen_bit(1'b1);
gen_stop;
end
$display("T11 three complete frames, counted exactly");
ck_int("T11 three STARTs", n_start_obs, 3);
ck_int("T11 three STOPs", n_stop_obs, 3);
ck_int("T11 no repeated STARTs", n_restart_obs, 0);
ck_bit("T11 and the bus is left idle", bus_active, 1'b0);
ck_int("T11 no pulse was ever wide", wide, 0);
// ----------------------------------------------------------------
// T12. RESET MID-TRANSFER RETURNS TO IDLE. Whatever the bus is doing, a reset
// slave has no transfer of its own -- and must not resume one it cannot
// have seen the beginning of.
// ----------------------------------------------------------------
do_reset;
gen_start;
ck_bit("T12 a transfer is open before reset", bus_active, 1'b1);
@(negedge clk); rst_n = 1'b0; step; step;
@(negedge clk); rst_n = 1'b1; phase;
$display("T12 reset mid-transfer returns to idle and does not resume");
ck_bit("T12 no transfer open after reset", bus_active, 1'b0);
ck_int("T12 counters cleared", n_sta + n_rs + n_sto, 0);
if (errors == 0) $display("=== i2c_slave_framing: ALL CHECKS PASSED ===");
else $display("=== i2c_slave_framing: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule // -----------------------------------------------------------------------------
// i2c_slave_framing_tb.v
// Independent oracle for i2c_slave_framing, driven through the real front end.
//
// The bench is a hand-written CONTROLLER: it drives the two pins the way a master
// does, with SDA changed only while SCL is low for data and only while SCL is high for
// framing. That matters -- a bench that drove framing and data the same way could not
// distinguish a detector that checks the SCL level from one that does not.
//
// The DUT is instantiated behind i2c_slave_sync, so what is being tested is the pair as
// the slave will actually use it. Every wait is bounded.
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module i2c_slave_framing_tb;
// A deliberately slow bus relative to clk: eight cycles per half period, so the
// synchroniser's two-cycle latency is a small fraction of a phase, as on real
// hardware. Chapter 18.5 is where the ratio starts to constrain the design.
localparam integer HALF = 8;
reg clk = 1'b0, rst_n = 1'b0;
reg m_scl = 1'b1, m_sda = 1'b1; // the controller's drive intent, active HIGH
// in the bench for readability
reg mid_byte = 1'b0;
// Open-drain resolution, exactly as the bus does it: the line is low if the
// controller pulls it low. Nothing else drives here.
wire scl_pin = m_scl;
wire sda_pin = m_sda;
wire scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall;
wire start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte;
wire [15:0] n_sta, n_rs, n_sto;
integer errors = 0;
integer n, k;
// ---- observers -------------------------------------------------------------
integer n_start_obs = 0, n_restart_obs = 0, n_stop_obs = 0, n_mid_obs = 0;
integer wide = 0;
reg sp_d = 1'b0, rp_d = 1'b0, tp_d = 1'b0;
always @(posedge clk) begin
if (rst_n) begin
if (start_pulse) n_start_obs <= n_start_obs + 1;
if (restart_pulse) n_restart_obs <= n_restart_obs + 1;
if (stop_pulse) n_stop_obs <= n_stop_obs + 1;
if (framing_midbyte) n_mid_obs <= n_mid_obs + 1;
if ((start_pulse & sp_d) | (restart_pulse & rp_d) | (stop_pulse & tp_d))
wide <= wide + 1;
end
sp_d <= start_pulse; rp_d <= restart_pulse; tp_d <= stop_pulse;
end
i2c_slave_sync #(.SYNC_DEPTH(2)) u_sync (
.clk(clk), .rst_n(rst_n), .scl_pin(scl_pin), .sda_pin(sda_pin),
.scl_q(scl_q), .sda_q(sda_q),
.scl_rise(scl_rise), .scl_fall(scl_fall),
.sda_rise(sda_rise), .sda_fall(sda_fall));
i2c_slave_framing #(.CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n),
.scl_q(scl_q), .sda_rise(sda_rise), .sda_fall(sda_fall),
.start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
.bus_active(bus_active), .mid_byte(mid_byte), .framing_midbyte(framing_midbyte),
.n_starts(n_sta), .n_restarts(n_rs), .n_stops(n_sto));
always #5 clk = ~clk;
task step; begin @(posedge clk); @(negedge clk); end endtask
task phase; begin for (n = 0; n < HALF; n = n + 1) step; end endtask
task do_reset;
begin
@(negedge clk); rst_n = 1'b0; m_scl = 1'b1; m_sda = 1'b1; mid_byte = 1'b0;
n_start_obs = 0; n_restart_obs = 0; n_stop_obs = 0; n_mid_obs = 0; wide = 0;
step; step;
@(negedge clk); rst_n = 1'b1;
phase;
end
endtask
// ---- controller-side stimulus ---------------------------------------------
//
// A START: SDA falls while SCL is HIGH. Then SCL is pulled low to begin the transfer.
task gen_start;
begin
@(negedge clk); m_sda = 1'b1; m_scl = 1'b1; phase;
@(negedge clk); m_sda = 1'b0; // the START edge
phase;
@(negedge clk); m_scl = 1'b0;
phase;
end
endtask
// A repeated START: release SDA while SCL is LOW (ordinary preparation), release
// SCL, then drop SDA while SCL is HIGH. The first move must NOT be seen as framing.
task gen_restart;
begin
@(negedge clk); m_scl = 1'b0; m_sda = 1'b1; phase; // SDA up, SCL still low
@(negedge clk); m_scl = 1'b1; phase; // SCL up
@(negedge clk); m_sda = 1'b0; phase; // the Sr edge
@(negedge clk); m_scl = 1'b0; phase;
end
endtask
// A STOP: SDA low, SCL released high, then SDA released while SCL is HIGH.
task gen_stop;
begin
@(negedge clk); m_scl = 1'b0; m_sda = 1'b0; phase;
@(negedge clk); m_scl = 1'b1; phase;
@(negedge clk); m_sda = 1'b1; // the STOP edge
phase;
end
endtask
// One ordinary data bit: SDA is changed only while SCL is LOW, then clocked.
task gen_bit (input b);
begin
@(negedge clk); m_scl = 1'b0; phase;
@(negedge clk); m_sda = b; phase; // changed in the LOW phase
@(negedge clk); m_scl = 1'b1; phase;
@(negedge clk); m_scl = 1'b0; phase;
end
endtask
task ck_bit (input [200*8:1] what, input g, input e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0b expected %0b", what, g, e);
errors = errors + 1;
end
end
endtask
task ck_int (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_slave_framing: an SDA edge qualified by an SCL level ===");
// ----------------------------------------------------------------
// T1. RESET ASSUMES NO TRANSFER. A slave that came up believing one was open
// would try to read the middle of somebody else's transaction as an address.
// ----------------------------------------------------------------
do_reset;
$display("T1 a slave out of reset assumes the bus is idle");
ck_bit("T1 no transfer open", bus_active, 1'b0);
ck_int("T1 no framing seen", n_start_obs + n_stop_obs, 0);
// ----------------------------------------------------------------
// T2. A QUIET BUS PRODUCES NOTHING. The false-positive test.
// ----------------------------------------------------------------
for (k = 0; k < 6; k = k + 1) phase;
$display("T2 a quiet bus produces no framing");
ck_int("T2 still nothing", n_start_obs + n_stop_obs, 0);
ck_bit("T2 still idle", bus_active, 1'b0);
// ----------------------------------------------------------------
// T3. A START. One pulse, one cycle wide, and the bus becomes active.
// ----------------------------------------------------------------
do_reset;
gen_start;
$display("T3 a START is one pulse, and it opens the transfer");
ck_int("T3 exactly one START", n_start_obs, 1);
ck_int("T3 and it was not a repeated one", n_restart_obs, 0);
ck_bit("T3 the bus is now active", bus_active, 1'b1);
ck_int("T3 the pulse was one cycle", wide, 0);
ck_int("T3 the counter agrees", n_sta, 1);
// ----------------------------------------------------------------
// T4. THE CENTRAL NEGATIVE TEST. An SDA transition while SCL is LOW is ordinary
// data preparation and is NOT framing. A detector that ignored the SCL level
// would report a START on every data bit that happens to be a zero -- which
// is most of them -- and the slave would restart its address phase mid-byte,
// forever.
// ----------------------------------------------------------------
n_start_obs = 0; n_stop_obs = 0; n_restart_obs = 0;
gen_bit(1'b0); // SDA falls while SCL is low
gen_bit(1'b1); // and rises while SCL is low
gen_bit(1'b0);
$display("T4 SDA moving while SCL is LOW is data, not framing");
ck_int("T4 no START was reported", n_start_obs, 0);
ck_int("T4 no STOP was reported", n_stop_obs, 0);
ck_bit("T4 and the transfer is still the same one", bus_active, 1'b1);
// ----------------------------------------------------------------
// T5. A STOP closes the transfer.
// ----------------------------------------------------------------
gen_stop;
$display("T5 a STOP closes the transfer");
ck_int("T5 exactly one STOP", n_stop_obs, 1);
ck_bit("T5 the bus is idle again", bus_active, 1'b0);
ck_int("T5 the counter agrees", n_sto, 1);
// ----------------------------------------------------------------
// T6. A REPEATED START, and the distinction that is NOT on the wire. The edge is
// identical to a first START; only `bus_active` separates them.
// ----------------------------------------------------------------
do_reset;
gen_start;
gen_bit(1'b1); gen_bit(1'b0);
n_start_obs = 0; n_restart_obs = 0;
gen_restart;
$display("T6 a repeated START is the same edge; only the state differs");
ck_int("T6 a START was reported", n_start_obs, 1);
ck_int("T6 and it was flagged as a REPEATED start", n_restart_obs, 1);
ck_bit("T6 the transfer is still open", bus_active, 1'b1);
ck_int("T6 the restart counter moved", n_rs, 1);
// ----------------------------------------------------------------
// T7. AND THE SDA RELEASE THAT PRECEDES A REPEATED START IS NOT A STOP. This is
// the trap: a repeated START begins by lifting SDA, and if that lift were
// classified as framing the slave would see STOP-then-START and end the
// transaction -- losing exactly the state a repeated START exists to keep.
// ----------------------------------------------------------------
ck_int("T7 no STOP was reported during the repeated START", n_stop_obs, 0);
// ----------------------------------------------------------------
// T8. A FIRST START AFTER A STOP counts as a first START again, not a repeated
// one. The classification is stateful, so it must clear.
// ----------------------------------------------------------------
gen_stop;
n_start_obs = 0; n_restart_obs = 0;
gen_start;
$display("T8 after a STOP the next START is a first START again");
ck_int("T8 one START", n_start_obs, 1);
ck_int("T8 and NOT a repeated one", n_restart_obs, 0);
// ----------------------------------------------------------------
// T9. MID-BYTE FRAMING IS REPORTED, not absorbed. A slave that silently swallows
// a STOP arriving part-way through a byte cannot be debugged from outside,
// and Chapter 18.11 needs the report to decide a policy.
// ----------------------------------------------------------------
do_reset;
gen_start;
@(negedge clk); mid_byte = 1'b1; // the bit counter says a byte is in progress
n_mid_obs = 0;
gen_stop;
$display("T9 framing arriving mid-byte is reported rather than absorbed");
ck_int("T9 the mid-byte report fired", n_mid_obs, 1);
@(negedge clk); mid_byte = 1'b0;
// ----------------------------------------------------------------
// T9b. AND THE OTHER HALF: mid-byte framing on a repeated START. T9 tests the STOP
// path only, so the START path's report was untested -- and the two are
// separate branches. A repeated START arriving part-way through a byte is the
// more likely of the two in practice, because a master that aborts a transfer
// usually retries with a restart rather than a STOP.
// ----------------------------------------------------------------
do_reset;
gen_start;
@(negedge clk); mid_byte = 1'b1;
n_mid_obs = 0;
gen_restart;
$display("T9b a repeated START mid-byte is reported too, not only a STOP");
ck_int("T9b the mid-byte report fired on the restart", n_mid_obs, 1);
@(negedge clk); mid_byte = 1'b0;
// ----------------------------------------------------------------
// T10. AND IT IS NOT REPORTED WHEN NO BYTE IS IN PROGRESS. The false-positive
// half: an ordinary STOP between bytes is completely normal.
// ----------------------------------------------------------------
do_reset;
gen_start;
n_mid_obs = 0;
gen_stop;
$display("T10 an ordinary STOP between bytes is not a mid-byte report");
ck_int("T10 no mid-byte report", n_mid_obs, 0);
// ----------------------------------------------------------------
// T11. MANY TRANSFERS, and nothing accumulates. Three complete frames.
// ----------------------------------------------------------------
do_reset;
for (k = 0; k < 3; k = k + 1) begin
gen_start;
gen_bit(1'b1); gen_bit(1'b0); gen_bit(1'b1);
gen_stop;
end
$display("T11 three complete frames, counted exactly");
ck_int("T11 three STARTs", n_start_obs, 3);
ck_int("T11 three STOPs", n_stop_obs, 3);
ck_int("T11 no repeated STARTs", n_restart_obs, 0);
ck_bit("T11 and the bus is left idle", bus_active, 1'b0);
ck_int("T11 no pulse was ever wide", wide, 0);
// ----------------------------------------------------------------
// T12. RESET MID-TRANSFER RETURNS TO IDLE. Whatever the bus is doing, a reset
// slave has no transfer of its own -- and must not resume one it cannot
// have seen the beginning of.
// ----------------------------------------------------------------
do_reset;
gen_start;
ck_bit("T12 a transfer is open before reset", bus_active, 1'b1);
@(negedge clk); rst_n = 1'b0; step; step;
@(negedge clk); rst_n = 1'b1; phase;
$display("T12 reset mid-transfer returns to idle and does not resume");
ck_bit("T12 no transfer open after reset", bus_active, 1'b0);
ck_int("T12 counters cleared", n_sta + n_rs + n_sto, 0);
if (errors == 0) $display("=== i2c_slave_framing: ALL CHECKS PASSED ===");
else $display("=== i2c_slave_framing: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- -----------------------------------------------------------------------------
-- i2c_slave_framing_tb.vhd
-- Independent oracle for i2c_slave_framing, behind the real front end. Behavioural twin
-- of the SystemVerilog and Verilog benches: twelve tests, the same observers, the same
-- finish time.
--
-- The bench is a hand-written CONTROLLER: SDA changes only while SCL is LOW for data and
-- only while SCL is HIGH for framing. A bench that drove both the same way could not
-- distinguish a detector that checks the SCL level from one that does not.
-- -----------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_slave_framing_tb is
end entity i2c_slave_framing_tb;
architecture sim of i2c_slave_framing_tb is
constant HALF : positive := 8;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal m_scl : std_logic := '1';
signal m_sda : std_logic := '1';
signal mid_byte : std_logic := '0';
signal scl_q, sda_q : std_logic;
signal scl_rise, scl_fall, sda_rise, sda_fall : std_logic;
signal start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte : std_logic;
signal n_sta, n_rs, n_sto : unsigned(15 downto 0);
signal halt : boolean := false;
-- Observers, as SIGNALS so they update on the next edge exactly as the SystemVerilog
-- registers do. Variables would advance immediately and lead the other two languages.
signal n_start_obs, n_restart_obs, n_stop_obs, n_mid_obs : integer := 0;
signal wide : integer := 0;
signal clr : boolean := false;
begin
u_sync : entity work.i2c_slave_sync
generic map (SYNC_DEPTH => 2)
port map (clk => clk, rst_n => rst_n, scl_pin => m_scl, sda_pin => m_sda,
scl_q => scl_q, sda_q => sda_q,
scl_rise => scl_rise, scl_fall => scl_fall,
sda_rise => sda_rise, sda_fall => sda_fall);
dut : entity work.i2c_slave_framing
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_q => scl_q,
sda_rise => sda_rise, sda_fall => sda_fall,
start_pulse => start_pulse, restart_pulse => restart_pulse,
stop_pulse => stop_pulse, bus_active => bus_active,
mid_byte => mid_byte, framing_midbyte => framing_midbyte,
n_starts => n_sta, n_restarts => n_rs, n_stops => n_sto);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for 5 ns;
clk <= '1'; wait for 5 ns;
end loop;
wait;
end process;
obs : process (clk, clr)
variable sp_d, rp_d, tp_d : std_logic := '0';
begin
if clr then
n_start_obs <= 0; n_restart_obs <= 0; n_stop_obs <= 0; n_mid_obs <= 0; wide <= 0;
elsif rising_edge(clk) then
if rst_n = '1' then
if start_pulse = '1' then n_start_obs <= n_start_obs + 1; end if;
if restart_pulse = '1' then n_restart_obs <= n_restart_obs + 1; end if;
if stop_pulse = '1' then n_stop_obs <= n_stop_obs + 1; end if;
if framing_midbyte = '1' then n_mid_obs <= n_mid_obs + 1; end if;
if (start_pulse = '1' and sp_d = '1') or (restart_pulse = '1' and rp_d = '1')
or (stop_pulse = '1' and tp_d = '1') then
wide <= wide + 1;
end if;
end if;
sp_d := start_pulse; rp_d := restart_pulse; tp_d := stop_pulse;
end if;
end process;
stim : process
variable err : integer := 0;
variable n, k : integer;
procedure step is
begin
wait until rising_edge(clk); wait until falling_edge(clk);
end procedure;
procedure phase is
begin
for i in 1 to HALF loop step; end loop;
end procedure;
procedure do_reset is
begin
wait until falling_edge(clk);
rst_n <= '0'; m_scl <= '1'; m_sda <= '1'; mid_byte <= '0';
clr <= true; wait for 1 ns; clr <= false;
step; step;
wait until falling_edge(clk); rst_n <= '1';
phase;
end procedure;
-- A START: SDA falls while SCL is HIGH, then SCL is pulled low.
procedure gen_start is
begin
wait until falling_edge(clk); m_sda <= '1'; m_scl <= '1'; phase;
wait until falling_edge(clk); m_sda <= '0'; phase;
wait until falling_edge(clk); m_scl <= '0'; phase;
end procedure;
-- A repeated START: lift SDA while SCL is LOW (not framing), release SCL, then
-- drop SDA while SCL is HIGH.
procedure gen_restart is
begin
wait until falling_edge(clk); m_scl <= '0'; m_sda <= '1'; phase;
wait until falling_edge(clk); m_scl <= '1'; phase;
wait until falling_edge(clk); m_sda <= '0'; phase;
wait until falling_edge(clk); m_scl <= '0'; phase;
end procedure;
-- A STOP: SDA low, SCL released, then SDA released while SCL is HIGH.
procedure gen_stop is
begin
wait until falling_edge(clk); m_scl <= '0'; m_sda <= '0'; phase;
wait until falling_edge(clk); m_scl <= '1'; phase;
wait until falling_edge(clk); m_sda <= '1'; phase;
end procedure;
-- One data bit: SDA changed only while SCL is LOW, then clocked.
procedure gen_bit (b : std_logic) is
begin
wait until falling_edge(clk); m_scl <= '0'; phase;
wait until falling_edge(clk); m_sda <= b; phase;
wait until falling_edge(clk); m_scl <= '1'; phase;
wait until falling_edge(clk); m_scl <= '0'; phase;
end procedure;
procedure ck_bit (what : string; g : std_logic; e : std_logic) is
begin
if g /= e then
report " FAIL " & what severity note;
err := err + 1;
end if;
end procedure;
procedure ck_int (what : string; g : integer; e : integer) is
begin
if g /= e then
report " FAIL " & what & ": got " & integer'image(g)
& " expected " & integer'image(e) severity note;
err := err + 1;
end if;
end procedure;
begin
report "=== i2c_slave_framing: an SDA edge qualified by an SCL level ===" severity note;
-- T1. Reset assumes no transfer.
do_reset;
report "T1 a slave out of reset assumes the bus is idle" severity note;
ck_bit("T1 no transfer open", bus_active, '0');
ck_int("T1 no framing seen", n_start_obs + n_stop_obs, 0);
-- T2. A quiet bus produces nothing.
for k in 0 to 5 loop phase; end loop;
report "T2 a quiet bus produces no framing" severity note;
ck_int("T2 still nothing", n_start_obs + n_stop_obs, 0);
ck_bit("T2 still idle", bus_active, '0');
-- T3. A START.
do_reset;
gen_start;
report "T3 a START is one pulse, and it opens the transfer" severity note;
ck_int("T3 exactly one START", n_start_obs, 1);
ck_int("T3 and it was not a repeated one", n_restart_obs, 0);
ck_bit("T3 the bus is now active", bus_active, '1');
ck_int("T3 the pulse was one cycle", wide, 0);
ck_int("T3 the counter agrees", to_integer(n_sta), 1);
-- T4. THE CENTRAL NEGATIVE TEST: SDA moving while SCL is LOW is data.
clr <= true; wait for 1 ns; clr <= false;
gen_bit('0');
gen_bit('1');
gen_bit('0');
report "T4 SDA moving while SCL is LOW is data, not framing" severity note;
ck_int("T4 no START was reported", n_start_obs, 0);
ck_int("T4 no STOP was reported", n_stop_obs, 0);
ck_bit("T4 and the transfer is still the same one", bus_active, '1');
-- T5. A STOP closes the transfer.
gen_stop;
report "T5 a STOP closes the transfer" severity note;
ck_int("T5 exactly one STOP", n_stop_obs, 1);
ck_bit("T5 the bus is idle again", bus_active, '0');
ck_int("T5 the counter agrees", to_integer(n_sto), 1);
-- T6. A repeated START: the same edge, distinguished only by state.
do_reset;
gen_start;
gen_bit('1'); gen_bit('0');
clr <= true; wait for 1 ns; clr <= false;
gen_restart;
report "T6 a repeated START is the same edge; only the state differs" severity note;
ck_int("T6 a START was reported", n_start_obs, 1);
ck_int("T6 and it was flagged as a REPEATED start", n_restart_obs, 1);
ck_bit("T6 the transfer is still open", bus_active, '1');
ck_int("T6 the restart counter moved", to_integer(n_rs), 1);
-- T7. And the SDA lift that precedes a repeated START is NOT a STOP.
ck_int("T7 no STOP was reported during the repeated START", n_stop_obs, 0);
-- T8. After a STOP the next START is a first START again.
gen_stop;
clr <= true; wait for 1 ns; clr <= false;
gen_start;
report "T8 after a STOP the next START is a first START again" severity note;
ck_int("T8 one START", n_start_obs, 1);
ck_int("T8 and NOT a repeated one", n_restart_obs, 0);
-- T9. Mid-byte framing is reported, not absorbed.
do_reset;
gen_start;
wait until falling_edge(clk); mid_byte <= '1';
clr <= true; wait for 1 ns; clr <= false;
gen_stop;
report "T9 framing arriving mid-byte is reported rather than absorbed" severity note;
ck_int("T9 the mid-byte report fired", n_mid_obs, 1);
wait until falling_edge(clk); mid_byte <= '0';
-- T9b. AND THE OTHER HALF: mid-byte framing on a repeated START. T9 tests the STOP
-- path only, so the START path's report was untested -- and they are separate
-- branches. A repeated START mid-byte is the more likely of the two, because a
-- master that aborts usually retries with a restart rather than a STOP.
do_reset;
gen_start;
wait until falling_edge(clk); mid_byte <= '1';
clr <= true; wait for 1 ns; clr <= false;
gen_restart;
report "T9b a repeated START mid-byte is reported too, not only a STOP" severity note;
ck_int("T9b the mid-byte report fired on the restart", n_mid_obs, 1);
wait until falling_edge(clk); mid_byte <= '0';
-- T10. And not reported when no byte is in progress.
do_reset;
gen_start;
clr <= true; wait for 1 ns; clr <= false;
gen_stop;
report "T10 an ordinary STOP between bytes is not a mid-byte report" severity note;
ck_int("T10 no mid-byte report", n_mid_obs, 0);
-- T11. Three complete frames, nothing accumulates.
do_reset;
for k in 0 to 2 loop
gen_start;
gen_bit('1'); gen_bit('0'); gen_bit('1');
gen_stop;
end loop;
report "T11 three complete frames, counted exactly" severity note;
ck_int("T11 three STARTs", n_start_obs, 3);
ck_int("T11 three STOPs", n_stop_obs, 3);
ck_int("T11 no repeated STARTs", n_restart_obs, 0);
ck_bit("T11 and the bus is left idle", bus_active, '0');
ck_int("T11 no pulse was ever wide", wide, 0);
-- T12. Reset mid-transfer returns to idle and does not resume.
do_reset;
gen_start;
ck_bit("T12 a transfer is open before reset", bus_active, '1');
wait until falling_edge(clk); rst_n <= '0'; step; step;
wait until falling_edge(clk); rst_n <= '1'; phase;
report "T12 reset mid-transfer returns to idle and does not resume" severity note;
ck_bit("T12 no transfer open after reset", bus_active, '0');
ck_int("T12 counters cleared", to_integer(n_sta) + to_integer(n_rs)
+ to_integer(n_sto), 0);
if err = 0 then
report "=== i2c_slave_framing: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_slave_framing: " & integer'image(err) & " CHECK(S) FAILED ==="
severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;7b. Execution
| Design | SystemVerilog | Verilog-2001 | VHDL | Finish |
|---|---|---|---|---|
i2c_slave_framing | PASS 13/13 | PASS 13/13 | PASS 13/13 | 11950 ns, all three |
8. Mutation Testing
Eleven defects, one per claim.
| # | Injected defect | Expected detection | Result |
|---|---|---|---|
| M1 | remove the SCL qualification — any SDA fall is a START | T4 | KILLED (7) |
| M2 | reverse the START edge | T3, T11 | KILLED (17) |
| M3 | swap START and STOP | T3, T5 | KILLED (15) |
| M4 | remove the SCL qualification from STOP | T4, T7 | KILLED (9) |
| M5 | a repeated START is never distinguished | T6 | KILLED (3) |
| M6 | every START is reported as repeated | T3, T8 | KILLED (6) |
| M7 | a STOP does not clear bus_active | T5, T8 | KILLED (5) |
| M8 | reset assumes a transfer is open | T1 | KILLED (8) |
| M9 | the pulses become levels, never cleared | T3, T11 | KILLED (12) |
| M10 | mid-byte framing on a START is absorbed | T9b new | KILLED (2) |
| M11 | mid-byte framing on a STOP is absorbed | T9 | KILLED (2) |
baseline: PASS (verified before injecting anything)
valid mutants: 11 killed: 11 survived: 0 equivalent: 0 invalid: 0
restored: PASSM10 survived first, and it was the same lesson as 18.2
T9 tested mid-byte framing on a STOP. The START path's report is a separate branch, and nothing exercised it — so a mutation that silenced it passed.
T9b adds the missing half, and it is arguably the more important one: a master that aborts a transfer usually retries with a repeated START rather than a STOP, so mid-byte framing in practice arrives on the branch that was untested.
9. Verification Connection — What the Monitor and the Target Must Agree On
// 1. FRAMING REQUIRES THE LEVEL. A start pulse may only be produced when SCL was
// high -- this is mutation M1 as an assertion, and it is the property every other
// block in the target depends on.
property p_start_needs_scl_high;
@(posedge clk) disable iff (!rst_n) start_pulse |-> $past(scl_q);
endproperty
a_qual: assert property (p_start_needs_scl_high);
// 2. THE TWO EVENTS ARE MUTUALLY EXCLUSIVE. One SDA edge cannot be both, because an
// edge has one direction.
property p_not_both;
@(posedge clk) disable iff (!rst_n) !(start_pulse && stop_pulse);
endproperty
a_excl: assert property (p_not_both);
// 3. A RESTART IMPLIES A START. `restart_pulse` is a qualification of `start_pulse`,
// not an independent event -- so a consumer can gate on either without needing to
// know the other's timing.
property p_restart_implies_start;
@(posedge clk) disable iff (!rst_n) restart_pulse |-> start_pulse;
endproperty
a_impl: assert property (p_restart_implies_start);
// 4. bus_active TRACKS THE FRAMING. It rises with a START and falls with a STOP, and
// nothing else moves it.
property p_active_tracks;
@(posedge clk) disable iff (!rst_n)
$changed(bus_active) |-> (start_pulse || stop_pulse);
endproperty
a_track: assert property (p_active_tracks);
// WHAT A PIN-LEVEL MONITOR SHARES WITH THIS BLOCK, and why that is a hazard.
//
// A protocol monitor classifies framing with the SAME two conditions. So if the
// monitor and the DUT are both wrong in the same way -- both forgetting the SCL
// qualifier, say -- they agree perfectly and the scoreboard is silent.
//
// Which means the monitor must NOT be written by copying the DUT's expression. The
// benches in this module avoid the trap differently: the CONTROLLER drives framing and
// data with deliberately different timing, so a DUT that conflates them disagrees with
// the stimulus rather than with a monitor that might share its mistake.
//
// COVERAGE. Three bins, and the third is the one a clean regression never reaches:
//
// cover: a first START -- every transfer
// cover: a repeated START -- combined transfers only
// cover: framing arriving MID-BYTE -- an aborted transfer only10. FPGA and ASIC Implications
On an FPGA this block is pure logic with no pin contact, so it has no I/O considerations of its own — which is the payoff of 18.2 owning the boundary. What it does inherit is the two-cycle latency: a START is detected two cycles after the edge, and nothing in the protocol requires a target to respond to one faster than that. The master's next act is to clock, which takes a whole phase.
The one implementation note worth making is about bus_active. It is the gate for every later block, so it fans out widely — and because it changes only on framing events, it is a slow signal with a lot of load. That is a placement consideration rather than a timing risk, and Module 19.6 owns the constraint side.
On an ASIC, the relevant point is that framing detection must survive the pad's input filter. A filter that suppresses Table 10's tSP spike also slightly delays both lines — and because framing depends on the relative timing of an SDA edge and an SCL level, a filter with different delays on the two lines could in principle move an edge across a level change. In practice both pads are the same cell with the same filter, so the skew is negligible; the reason to state it is that it is the one place where a per-line difference would matter, and it is worth knowing before somebody filters one line and not the other.
Reset leaves bus_active low, which is the only safe value: a target that came up believing a transfer was open would try to read the middle of somebody else's transaction as an address byte.
11. Debugging — The Target That Would Not Stay Addressed
A newly written target is addressed correctly and acknowledges its address. It then fails to receive the first data byte: the master sees the address acknowledged and every subsequent byte NACKed. A logic analyser shows a clean, conforming write transaction from the master -- correct START, correct address, correct acknowledge, correct data bytes -- and the target simply stops responding after the address.
The SCL qualifier was missing from the START condition, so every ordinary data-preparation fall was reported as a START. Section 3.1.1 defines a START as an SDA fall while SCL is HIGH precisely because an SDA fall while SCL is LOW is the normal way a zero bit is presented -- it happens on most bits of most bytes. The address phase therefore worked by luck and the first data byte containing a zero destroyed the transaction. Nothing was wrong with the receive path, the acknowledge generator or the address comparator; all three were driven by an event that had been manufactured below them.
Qualify the edge with the level: start_now = sda_fall AND scl_q, which is mutation M1 in reverse. Then note what made this survivable for so long -- the address byte happened not to contain a qualifying transition, so the block appeared to work for the one byte that mattered most. The regression that catches it is T4, and its shape is worth copying: drive ordinary data bits deliberately, with SDA changing only in the LOW phase, and assert that ZERO framing events are reported. A bench that only ever sends framing cannot fail a detector that thinks everything is framing.Three generalisations.
The defect was upstream of three correct blocks. The address block, the receive path and the acknowledge generator all behaved exactly as specified on an event that never happened. A single-entry-point observation pipeline localises the risk and globalises the blast radius.
It worked for the address byte by luck. 0xA0's only SDA fall while SCL was low fell before acquisition was armed. The first byte containing a zero in a different position broke it — so the failure looked like a data-path bug because the address path appeared fine.
The test that catches it asserts a zero. "Drive ordinary data and assert no framing" is a negative test, and negative tests are the easiest to omit from a plan that enumerates behaviours rather than non-behaviours.
12. Common Misconceptions
"A START is an SDA falling edge." It is an SDA falling edge while SCL is high. Without the qualifier it fires on most data zeros. §1.
"The SCL check is a refinement." It is the entire distinction between framing and data — the two most different things on the bus share one edge shape. §1.
"A repeated START is a different waveform." It is electrically identical to a first START. Only the target's own bus_active separates them. §3.
"So the target can ignore the difference." Then it cannot handle a combined transfer: a repeated START must restart the frame while leaving application state alone, and a first START must not. §3 and 18.10.
"A repeated START is just a START, so nothing precedes it." It begins by lifting SDA while SCL is low. If that lift were read as framing the target would see STOP-then-START and discard the state the restart exists to keep. §4.
"A framing detector should also track bit position." Then it needs resetting by its own output, and the two jobs have different reset conditions. §5.
"Mid-byte framing should be silently absorbed." A target that absorbs it cannot be debugged from outside the chip. Report it; let 18.11 decide the policy. §5.
"If the address byte decodes, framing detection works." It may have worked by luck — whether the address contained a qualifying transition before acquisition armed. §11.
"Testing framing means sending framing." A detector that thinks everything is framing passes every such test. The decisive test drives ordinary data and asserts zero events. §11.
"Writing the monitor from the DUT's expression saves effort." Then both can be wrong the same way and the scoreboard stays silent. §9.
13. Reason It Through
SDA falls while SCL is low. Why must this not be classified as a START?
Because that is how a zero bit is presented — §3.1.2 permits SDA to change only while SCL is low, so it happens on most bits of most bytes. Classifying it as framing restarts the address phase several times per byte. §1.
What distinguishes a repeated START from a first START, and where does that information live?
Only whether a transfer was already open. It lives in the target's own bus_active state, not on the wire — the two edges are electrically identical. §3.
A repeated START sequence lifts SDA before dropping it. Why is that lift not a STOP?
Because it happens while SCL is LOW, and a STOP requires SDA to rise while SCL is HIGH. The same qualifier that protects against data zeros protects against this. §4.
Why does this block report mid-byte framing without acting on it?
Because the policy is a transaction-level decision (18.11), while the observation is a framing-level fact. Reporting a condition it does nothing about is what makes the behaviour debuggable from outside the chip. §5.
A target acknowledges its address and then NACKs every data byte. The analyser shows a clean transaction. Where do you look?
At whether the framing detector is emitting spurious STARTs during the data byte — which happens if the SCL qualifier is missing, because data zeros then look like framing and the address phase re-arms mid-byte. §11.
Why did that defect survive the address byte?
Because 0xA0's only SDA fall while SCL was low occurred before acquisition was armed. The luck was in the bit pattern, not the logic. §11.
Three of this module's mutation survivors so far share a shape. What is it, and what does it imply for a test plan?
Each was the untested half of a symmetric property — one direction of independence, the pulse but not the level, the STOP branch but not the START branch. It implies enumerating branches, not behaviours. §8.
14. Understanding Check
15. Summary
Detection is two AND gates: an SDA edge qualified by the SCL level. Both lines are needed, which is why 18.2 exports levels as well as edges.
Dropping the SCL qualifier makes data zeros look like STARTs, several times per byte — and the address phase then re-arms mid-byte, forever.
A slave's detector is not the master's framer reversed. The master chose the instants; the target classifies an edge that has already happened and resets the right state.
A first START and a repeated START are the identical electrical event. Only the target's own bus_active separates them, so a target that does not track it cannot handle a combined transfer.
A repeated START begins by lifting SDA while SCL is low, and the SCL qualifier is what stops that lift being read as a STOP — the same qualifier doing a second job.
No bit counter lives here, because it would need resetting by this block's own output, and the two jobs have different reset conditions.
Mid-byte framing is reported and not acted on, because the policy is a transaction-level decision and an absorbed condition cannot be debugged from outside.
Eleven mutants, eleven killed — after M10 survived because only the STOP branch of the mid-byte report was tested.
That is the third survivor in this module with one shape: the untested half of a symmetric property. Enumerate branches, not behaviours.
And the decisive test asserts a zero. Drive ordinary data, assert no framing — because a detector that thinks everything is framing passes every positive test.
16. What Comes Next
The target now knows when a transfer begins and ends. Chapter 18.4 makes it find out whether the transfer is its.
That chapter's argument is a separation: receiving eight bits, deciding whether they match, and deciding whether to answer are three jobs that fail separately, and a design with one state called "address" cannot tell you which of them broke. It is also where the direction bit is latched — the one piece of the address byte that governs everything afterwards, long after the byte itself has left the wire.
Continue learning
Related tutorials
- 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
Sampling SDA and SCL — Edge Detection Inside a Slave
The block every other one in a target is written against: two asynchronous wires turned into five synchronous facts. Three of its decisions produce defects nothing later can recover from — what the samplers reset to, which signals the edge pulses come from, and why the synchroniser belongs here and nowhere else.
- Related topic
ACK Generation and Its Timing Window
Where most slave designs first fail. The acknowledge has three parts, the window is bounded at both ends by falling edges, and asserting early is not early — SDA falling while SCL is high is a START, so a premature acknowledge restarts the transaction instead of acknowledging a byte.
- Related topic
The Transmit Datapath — Sourcing Read Data in Time
The hardest datapath in a target, because the master decides when the next bit is wanted and the slave must have decided what it is one phase earlier. Builds the pre-fetch, shows why a transmitted one is a release, and why the ninth slot must be given back.
