I²C · Module 17
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.
Chapter 17.1 §3 proved that one block in this master cannot be merged into any other. This is that block.
Both lines now have controllers: 17.3 gives SCL legal phases and announces the two datapath instants, and 17.4 gives SDA one inversion and explicit ownership. Neither of them can produce a START.
1. Why This Cannot Be a Mode of the Bit Engine
Those are contradictory, deliberately. Framing is recognisable precisely because it is the one thing a data bit may never do.
So: the bit engine's whole job is to guarantee SDA is stable while SCL is high. Framing requires the opposite. The two cannot be the same block — not for readability, but because they have opposite postconditions on the same wire in the same phase.
2. The Four Sequences, and the Parameter Each Is Built From
| Sequence | What happens | Table 10 parameter |
|---|---|---|
| START from idle | SDA falls while SCL high, then hold before SCL falls | tHD;STA |
| repeated START | SDA released, SCL released, wait, SDA falls, then hold | tSU;STA then tHD;STA |
| STOP | SDA low, SCL released, wait, SDA rises | tSU;STO |
| bus free | after a STOP, before the next START may be issued | tBUF |
Two details in that table do real work.
A repeated START must lift SDA first. Mid-transfer SDA may be sitting low — it was a data zero. A line that is already low cannot produce a HIGH-to-LOW transition, so the sequence has to release SDA before anything else. A framer that skips this emits no edge at all and the bus sees nothing.
tBUF is the one interval that constrains a master while it is doing nothing. It is not a property of a transfer; it is a property of the gap between two of them, and it is the only Table 10 parameter that applies to an idle master.
3. Every SCL Release Waits for the Readback
This is the same rule Chapter 17.3 established for the clock generator, and it applies with more force here.
A repeated START releases SCL and must not begin measuring tSU;STA until the line is actually high — because a target may be stretching, and tSU;STA is a setup time against the SDA falling edge. If SCL never rose, the interval is meaningless.
4. The Sequences, on the Wire
START then STOP — the establishing state, the edge, and the interval that follows
10 cyclesRead the two intervals as the same shape pointing opposite ways. tHD;STA runs forward from an edge the framer produced to an edge it must not produce too early. tSU;STO runs forward to an edge, from a condition the framer must first establish. One is a hold, the other a setup, and confusing them puts the counter on the wrong side of the edge.
Repeated START — lift SDA, release SCL, wait for it, then fall
10 cyclesNote intervals 2 and 3: scl_drive_low is 0 and scl_bus is still 0. That is the entire content of Chapter 17.1 §4's intent-versus-observation distinction, and it is the reason stretch_wait exists as an output rather than as an internal state.
5. The Framer, in Three Languages
// -----------------------------------------------------------------------------
// i2c_framer.sv
// START, repeated START and STOP generation: the edges the bit engine cannot produce.
//
// WHY THIS IS A SEPARATE BLOCK, and not a mode of the bit engine. UM10204 §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"
//
// and §3.1.2, the data rule: "The data on the SDA line must be stable during the HIGH
// period of the clock. ... The HIGH or LOW state of the data line can only change when
// the clock signal on the SCL line is LOW."
//
// Those are contradictory, deliberately. Framing is recognisable precisely BECAUSE it
// is the one thing a data bit may never do. The bit engine's whole job is to guarantee
// SDA is stable while SCL is high; framing requires the opposite. So the two cannot be
// the same block, and the separation is structural rather than stylistic.
//
// THE FOUR SEQUENCES, and the Table 10 parameter each one is built from:
//
// START from idle SDA falls while SCL high, then tHD;STA before SCL falls
// repeated START SDA released, SCL released, tSU;STA, then SDA falls, tHD;STA
// STOP SDA low, SCL released, tSU;STO, then SDA rises
// bus free tBUF after a STOP before the next START may be issued
//
// EVERY SCL RELEASE IN THOSE SEQUENCES WAITS FOR THE READBACK. A repeated START
// releases SCL and must not measure tSU;STA until the line is actually high, because a
// target may be stretching -- and tSU;STA is a setup time against the SDA falling edge,
// which is meaningless if SCL has not risen. A framer that counted from its own release
// would emit a START while SCL was still low, which is not a START at all: it is an
// ordinary data-bit change, and every device on the bus would read it as one.
// -----------------------------------------------------------------------------
module i2c_framer #(
parameter int N_HD_STA = 5, // tHD;STA, system-clock cycles
parameter int N_SU_STA = 6, // tSU;STA
parameter int N_SU_STO = 5, // tSU;STO
parameter int N_BUF = 6, // tBUF, bus free between STOP and START
parameter int N_SU_DAT = 3, // tSU;DAT, used before releasing SCL
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
// Commands. One-cycle pulses; only one may be asserted at a time.
input logic do_start, // START from an idle bus
input logic do_restart, // repeated START from mid-transfer
input logic do_stop,
// The bus, read back.
input logic scl_in,
input logic sda_in,
// THE HANDOVER. Between its own sequences the framer holds SCL low whenever a
// transfer is open, because somebody has to: a released SCL with a transfer in
// progress is a clock pulse nobody meant to send. `scl_yield` is the controller
// saying "the SCL generator has it now".
//
// The handover has to OVERLAP. If the framer released SCL in one cycle and the
// generator began driving it in the next, the line would rise for a cycle in
// between -- a spurious clock pulse that every device on the bus would count as a
// bit. So `scl_yield` and the generator's `enable` are asserted in the SAME cycle,
// and the two blocks then drive SCL low simultaneously for that cycle.
input logic scl_yield,
// What the framer wants. These go to the SDA owner arbiter (framer has priority)
// and to the SCL merge in Chapter 17.12.
output logic sda_req, // framer wants SDA
output logic sda_bit, // the bit it wants to transmit
output logic scl_drive_low, // framer's SCL contribution
output logic busy,
output logic done, // one cycle when a sequence completes
output logic bus_free, // tBUF has elapsed since the last STOP
output logic started, // a START has been issued and no STOP since
output logic stretch_wait, // waiting for SCL to actually rise
output logic [CNT_W-1:0] starts,
output logic [CNT_W-1:0] restarts,
output logic [CNT_W-1:0] stops,
output logic [3:0] state
);
localparam [3:0] S_IDLE = 4'd0, // nothing in progress
// START from idle: SDA falls while SCL is high, then hold.
S_STA_FALL = 4'd1, // pull SDA low -- THIS is the START edge
S_STA_HOLD = 4'd2, // tHD;STA, then pull SCL low
S_STA_SCL = 4'd3,
// repeated START: release SDA, release SCL, wait, then fall.
S_RS_SDA = 4'd4, // release SDA while SCL is low
S_RS_SETUP = 4'd5, // tSU;DAT before releasing SCL
S_RS_SCLHI = 4'd6, // release SCL and WAIT for the readback
S_RS_SU = 4'd7, // tSU;STA with both lines high
S_RS_FALL = 4'd8, // pull SDA low -- the repeated START edge
S_RS_HOLD = 4'd9, // tHD;STA, then pull SCL low
// STOP: SDA low, release SCL, wait, then release SDA.
S_STO_SDA = 4'd10, // ensure SDA is low while SCL is low
S_STO_SCLHI= 4'd11, // release SCL and WAIT for the readback
S_STO_SU = 4'd12, // tSU;STO
S_STO_RISE = 4'd13, // release SDA -- THIS is the STOP edge
S_BUF = 4'd14; // tBUF
logic [CNT_W-1:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
cnt <= {CNT_W{1'b0}};
sda_req <= 1'b0;
sda_bit <= 1'b1;
scl_drive_low <= 1'b0;
busy <= 1'b0;
done <= 1'b0;
bus_free <= 1'b1; // a reset master must assume nothing is in flight
started <= 1'b0;
stretch_wait <= 1'b0;
starts <= {CNT_W{1'b0}};
restarts <= {CNT_W{1'b0}};
stops <= {CNT_W{1'b0}};
end else begin
done <= 1'b0;
stretch_wait <= 1'b0;
case (state)
S_IDLE: begin
busy <= 1'b0;
// Hold SCL low while a transfer is open, unless yielding to the clock
// generator. With no transfer open, release it.
scl_drive_low <= started && !scl_yield;
if (do_start && bus_free) begin
// Take SDA and pull it low while SCL is high. That transition IS the
// START condition; nothing else has to happen for the bus to have
// seen one, which is why the ordering here is the whole sequence.
sda_req <= 1'b1;
sda_bit <= 1'b0;
busy <= 1'b1;
cnt <= {CNT_W{1'b0}};
state <= S_STA_HOLD;
end else if (do_restart) begin
// Mid-transfer, SCL is low. Release SDA first so it can be pulled
// low again later -- a line that is already low cannot produce a
// HIGH-to-LOW transition, so a repeated START from a zero data bit
// needs SDA lifted before anything else.
sda_req <= 1'b1;
sda_bit <= 1'b1;
busy <= 1'b1;
cnt <= {CNT_W{1'b0}};
state <= S_RS_SETUP;
end else if (do_stop) begin
// SDA must be LOW before SCL is released, so that releasing SDA
// afterwards is a LOW-to-HIGH transition while SCL is high.
sda_req <= 1'b1;
sda_bit <= 1'b0;
busy <= 1'b1;
cnt <= {CNT_W{1'b0}};
state <= S_STO_SDA;
end
end
// ---- START from an idle bus -------------------------------------
S_STA_HOLD: begin
// tHD;STA: hold SDA low with SCL still high.
if (cnt + 1 >= N_HD_STA) begin
scl_drive_low <= 1'b1; // now pull SCL low: the transfer begins
cnt <= {CNT_W{1'b0}};
state <= S_STA_SCL;
end else begin
cnt <= cnt + 1'b1;
end
end
S_STA_SCL: begin
// The START is complete and SCL is low. Hand SDA to the datapath.
sda_req <= 1'b0;
started <= 1'b1;
bus_free <= 1'b0;
starts <= starts + 1'b1;
done <= 1'b1;
busy <= 1'b0;
state <= S_IDLE;
end
// ---- repeated START ---------------------------------------------
S_RS_SETUP: begin
// tSU;DAT with SDA released and SCL still low, so SDA is set up before
// the clock rises. Footnote [5] of Table 10 is explicit that this
// set-up applies even when the clock is being stretched.
if (cnt + 1 >= N_SU_DAT) begin
scl_drive_low <= 1'b0; // release SCL
cnt <= {CNT_W{1'b0}};
state <= S_RS_SCLHI;
end else begin
cnt <= cnt + 1'b1;
end
end
S_RS_SCLHI: begin
// Released. WAIT for the line. A target stretching here is legal and
// common -- §3.1.6's byte-level handshake happens exactly at this
// boundary -- and tSU;STA cannot begin until SCL is really high.
//
// Note the counter's entry value. THIS cycle already has both lines
// high, so it is the first cycle of tSU;STA and is counted as such.
// Entering the setup state with a zero would deliver N_SU_STA + 1
// cycles of setup, which is legal -- tSU;STA has only a minimum -- but
// would mean the parameter did not mean the Table 10 number it is named
// after, and every period derived from it would be a cycle out.
if (scl_in) begin
cnt <= {{(CNT_W-1){1'b0}}, 1'b1};
state <= S_RS_SU;
end else begin
stretch_wait <= 1'b1;
end
end
S_RS_SU: begin
// tSU;STA with both lines high.
if (cnt + 1 >= N_SU_STA) begin
sda_bit <= 1'b0; // SDA falls: the repeated START edge
cnt <= {CNT_W{1'b0}};
state <= S_RS_HOLD;
end else begin
cnt <= cnt + 1'b1;
end
end
S_RS_HOLD: begin
if (cnt + 1 >= N_HD_STA) begin
scl_drive_low <= 1'b1;
sda_req <= 1'b0;
started <= 1'b1;
bus_free <= 1'b0;
restarts <= restarts + 1'b1;
done <= 1'b1;
busy <= 1'b0;
cnt <= {CNT_W{1'b0}};
state <= S_IDLE;
end else begin
cnt <= cnt + 1'b1;
end
end
// ---- STOP --------------------------------------------------------
S_STO_SDA: begin
// SDA low, SCL low. Give SDA a moment to settle before lifting SCL,
// for the same tSU;DAT reason as the repeated START.
if (cnt + 1 >= N_SU_DAT) begin
scl_drive_low <= 1'b0;
cnt <= {CNT_W{1'b0}};
state <= S_STO_SCLHI;
end else begin
cnt <= cnt + 1'b1;
end
end
S_STO_SCLHI: begin
// Same counting rule as the repeated START above: this cycle already
// has SCL high with SDA low, so it is the first cycle of tSU;STO.
if (scl_in) begin
cnt <= {{(CNT_W-1){1'b0}}, 1'b1};
state <= S_STO_SU;
end else begin
stretch_wait <= 1'b1;
end
end
S_STO_SU: begin
// tSU;STO with SCL high and SDA still low.
if (cnt + 1 >= N_SU_STO) begin
sda_bit <= 1'b1; // release SDA: the STOP edge
cnt <= {CNT_W{1'b0}};
state <= S_STO_RISE;
end else begin
cnt <= cnt + 1'b1;
end
end
S_STO_RISE: begin
// The STOP is on the wire. Hold SDA released and start counting tBUF.
sda_req <= 1'b0;
started <= 1'b0;
bus_free <= 1'b0; // not free until tBUF has elapsed
stops <= stops + 1'b1;
// Both lines are released in THIS cycle, so it is the first cycle of
// tBUF. The same counting rule as the two setup states above, and the
// third place in this block where it applies: the cycle that makes a
// transition is already the first cycle of the interval that follows
// it, and starting the count at zero delivers one cycle too many.
cnt <= {{(CNT_W-1){1'b0}}, 1'b1};
state <= S_BUF;
end
S_BUF: begin
// tBUF. A master that issues a START before this has elapsed produces a
// START the receiving devices may not recognise, and on a multi-master
// bus it is also how two masters collide without arbitrating.
if (cnt + 1 >= N_BUF) begin
bus_free <= 1'b1;
done <= 1'b1;
busy <= 1'b0;
state <= S_IDLE;
end else begin
cnt <= cnt + 1'b1;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule // -----------------------------------------------------------------------------
// i2c_framer.sv
// START, repeated START and STOP generation: the edges the bit engine cannot produce.
//
// WHY THIS IS A SEPARATE BLOCK, and not a mode of the bit engine. UM10204 §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"
//
// and §3.1.2, the data rule: "The data on the SDA line must be stable during the HIGH
// period of the clock. ... The HIGH or LOW state of the data line can only change when
// the clock signal on the SCL line is LOW."
//
// Those are contradictory, deliberately. Framing is recognisable precisely BECAUSE it
// is the one thing a data bit may never do. The bit engine's whole job is to guarantee
// SDA is stable while SCL is high; framing requires the opposite. So the two cannot be
// the same block, and the separation is structural rather than stylistic.
//
// THE FOUR SEQUENCES, and the Table 10 parameter each one is built from:
//
// START from idle SDA falls while SCL high, then tHD;STA before SCL falls
// repeated START SDA released, SCL released, tSU;STA, then SDA falls, tHD;STA
// STOP SDA low, SCL released, tSU;STO, then SDA rises
// bus free tBUF after a STOP before the next START may be issued
//
// EVERY SCL RELEASE IN THOSE SEQUENCES WAITS FOR THE READBACK. A repeated START
// releases SCL and must not measure tSU;STA until the line is actually high, because a
// target may be stretching -- and tSU;STA is a setup time against the SDA falling edge,
// which is meaningless if SCL has not risen. A framer that counted from its own release
// would emit a START while SCL was still low, which is not a START at all: it is an
// ordinary data-bit change, and every device on the bus would read it as one.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_framer #(
parameter N_HD_STA = 5, // tHD;STA, system-clock cycles
parameter N_SU_STA = 6, // tSU;STA
parameter N_SU_STO = 5, // tSU;STO
parameter N_BUF = 6, // tBUF, bus free between STOP and START
parameter N_SU_DAT = 3, // tSU;DAT, used before releasing SCL
parameter CNT_W = 16
) (
input wire clk,
input wire rst_n,
// Commands. One-cycle pulses; only one may be asserted at a time.
input wire do_start, // START from an idle bus
input wire do_restart, // repeated START from mid-transfer
input wire do_stop,
// The bus, read back.
input wire scl_in,
input wire sda_in,
// THE HANDOVER. Between its own sequences the framer holds SCL low whenever a
// transfer is open, because somebody has to: a released SCL with a transfer in
// progress is a clock pulse nobody meant to send. `scl_yield` is the controller
// saying "the SCL generator has it now".
//
// The handover has to OVERLAP. If the framer released SCL in one cycle and the
// generator began driving it in the next, the line would rise for a cycle in
// between -- a spurious clock pulse that every device on the bus would count as a
// bit. So `scl_yield` and the generator's `enable` are asserted in the SAME cycle,
// and the two blocks then drive SCL low simultaneously for that cycle.
input wire scl_yield,
// What the framer wants. These go to the SDA owner arbiter (framer has priority)
// and to the SCL merge in Chapter 17.12.
output reg sda_req, // framer wants SDA
output reg sda_bit, // the bit it wants to transmit
output reg scl_drive_low, // framer's SCL contribution
output reg busy,
output reg done, // one cycle when a sequence completes
output reg bus_free, // tBUF has elapsed since the last STOP
output reg started, // a START has been issued and no STOP since
output reg stretch_wait, // waiting for SCL to actually rise
output reg [CNT_W-1:0] starts,
output reg [CNT_W-1:0] restarts,
output reg [CNT_W-1:0] stops,
output reg [3:0] state
);
localparam [3:0] S_IDLE = 4'd0, // nothing in progress
// START from idle: SDA falls while SCL is high, then hold.
S_STA_FALL = 4'd1, // pull SDA low -- THIS is the START edge
S_STA_HOLD = 4'd2, // tHD;STA, then pull SCL low
S_STA_SCL = 4'd3,
// repeated START: release SDA, release SCL, wait, then fall.
S_RS_SDA = 4'd4, // release SDA while SCL is low
S_RS_SETUP = 4'd5, // tSU;DAT before releasing SCL
S_RS_SCLHI = 4'd6, // release SCL and WAIT for the readback
S_RS_SU = 4'd7, // tSU;STA with both lines high
S_RS_FALL = 4'd8, // pull SDA low -- the repeated START edge
S_RS_HOLD = 4'd9, // tHD;STA, then pull SCL low
// STOP: SDA low, release SCL, wait, then release SDA.
S_STO_SDA = 4'd10, // ensure SDA is low while SCL is low
S_STO_SCLHI= 4'd11, // release SCL and WAIT for the readback
S_STO_SU = 4'd12, // tSU;STO
S_STO_RISE = 4'd13, // release SDA -- THIS is the STOP edge
S_BUF = 4'd14; // tBUF
reg [CNT_W-1:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
cnt <= {CNT_W{1'b0}};
sda_req <= 1'b0;
sda_bit <= 1'b1;
scl_drive_low <= 1'b0;
busy <= 1'b0;
done <= 1'b0;
bus_free <= 1'b1; // a reset master must assume nothing is in flight
started <= 1'b0;
stretch_wait <= 1'b0;
starts <= {CNT_W{1'b0}};
restarts <= {CNT_W{1'b0}};
stops <= {CNT_W{1'b0}};
end else begin
done <= 1'b0;
stretch_wait <= 1'b0;
case (state)
S_IDLE: begin
busy <= 1'b0;
// Hold SCL low while a transfer is open, unless yielding to the clock
// generator. With no transfer open, release it.
scl_drive_low <= started && !scl_yield;
if (do_start && bus_free) begin
// Take SDA and pull it low while SCL is high. That transition IS the
// START condition; nothing else has to happen for the bus to have
// seen one, which is why the ordering here is the whole sequence.
sda_req <= 1'b1;
sda_bit <= 1'b0;
busy <= 1'b1;
cnt <= {CNT_W{1'b0}};
state <= S_STA_HOLD;
end else if (do_restart) begin
// Mid-transfer, SCL is low. Release SDA first so it can be pulled
// low again later -- a line that is already low cannot produce a
// HIGH-to-LOW transition, so a repeated START from a zero data bit
// needs SDA lifted before anything else.
sda_req <= 1'b1;
sda_bit <= 1'b1;
busy <= 1'b1;
cnt <= {CNT_W{1'b0}};
state <= S_RS_SETUP;
end else if (do_stop) begin
// SDA must be LOW before SCL is released, so that releasing SDA
// afterwards is a LOW-to-HIGH transition while SCL is high.
sda_req <= 1'b1;
sda_bit <= 1'b0;
busy <= 1'b1;
cnt <= {CNT_W{1'b0}};
state <= S_STO_SDA;
end
end
// ---- START from an idle bus -------------------------------------
S_STA_HOLD: begin
// tHD;STA: hold SDA low with SCL still high.
if (cnt + 1 >= N_HD_STA) begin
scl_drive_low <= 1'b1; // now pull SCL low: the transfer begins
cnt <= {CNT_W{1'b0}};
state <= S_STA_SCL;
end else begin
cnt <= cnt + 1'b1;
end
end
S_STA_SCL: begin
// The START is complete and SCL is low. Hand SDA to the datapath.
sda_req <= 1'b0;
started <= 1'b1;
bus_free <= 1'b0;
starts <= starts + 1'b1;
done <= 1'b1;
busy <= 1'b0;
state <= S_IDLE;
end
// ---- repeated START ---------------------------------------------
S_RS_SETUP: begin
// tSU;DAT with SDA released and SCL still low, so SDA is set up before
// the clock rises. Footnote [5] of Table 10 is explicit that this
// set-up applies even when the clock is being stretched.
if (cnt + 1 >= N_SU_DAT) begin
scl_drive_low <= 1'b0; // release SCL
cnt <= {CNT_W{1'b0}};
state <= S_RS_SCLHI;
end else begin
cnt <= cnt + 1'b1;
end
end
S_RS_SCLHI: begin
// Released. WAIT for the line. A target stretching here is legal and
// common -- §3.1.6's byte-level handshake happens exactly at this
// boundary -- and tSU;STA cannot begin until SCL is really high.
//
// Note the counter's entry value. THIS cycle already has both lines
// high, so it is the first cycle of tSU;STA and is counted as such.
// Entering the setup state with a zero would deliver N_SU_STA + 1
// cycles of setup, which is legal -- tSU;STA has only a minimum -- but
// would mean the parameter did not mean the Table 10 number it is named
// after, and every period derived from it would be a cycle out.
if (scl_in) begin
cnt <= {{(CNT_W-1){1'b0}}, 1'b1};
state <= S_RS_SU;
end else begin
stretch_wait <= 1'b1;
end
end
S_RS_SU: begin
// tSU;STA with both lines high.
if (cnt + 1 >= N_SU_STA) begin
sda_bit <= 1'b0; // SDA falls: the repeated START edge
cnt <= {CNT_W{1'b0}};
state <= S_RS_HOLD;
end else begin
cnt <= cnt + 1'b1;
end
end
S_RS_HOLD: begin
if (cnt + 1 >= N_HD_STA) begin
scl_drive_low <= 1'b1;
sda_req <= 1'b0;
started <= 1'b1;
bus_free <= 1'b0;
restarts <= restarts + 1'b1;
done <= 1'b1;
busy <= 1'b0;
cnt <= {CNT_W{1'b0}};
state <= S_IDLE;
end else begin
cnt <= cnt + 1'b1;
end
end
// ---- STOP --------------------------------------------------------
S_STO_SDA: begin
// SDA low, SCL low. Give SDA a moment to settle before lifting SCL,
// for the same tSU;DAT reason as the repeated START.
if (cnt + 1 >= N_SU_DAT) begin
scl_drive_low <= 1'b0;
cnt <= {CNT_W{1'b0}};
state <= S_STO_SCLHI;
end else begin
cnt <= cnt + 1'b1;
end
end
S_STO_SCLHI: begin
// Same counting rule as the repeated START above: this cycle already
// has SCL high with SDA low, so it is the first cycle of tSU;STO.
if (scl_in) begin
cnt <= {{(CNT_W-1){1'b0}}, 1'b1};
state <= S_STO_SU;
end else begin
stretch_wait <= 1'b1;
end
end
S_STO_SU: begin
// tSU;STO with SCL high and SDA still low.
if (cnt + 1 >= N_SU_STO) begin
sda_bit <= 1'b1; // release SDA: the STOP edge
cnt <= {CNT_W{1'b0}};
state <= S_STO_RISE;
end else begin
cnt <= cnt + 1'b1;
end
end
S_STO_RISE: begin
// The STOP is on the wire. Hold SDA released and start counting tBUF.
sda_req <= 1'b0;
started <= 1'b0;
bus_free <= 1'b0; // not free until tBUF has elapsed
stops <= stops + 1'b1;
// Both lines are released in THIS cycle, so it is the first cycle of
// tBUF. The same counting rule as the two setup states above, and the
// third place in this block where it applies: the cycle that makes a
// transition is already the first cycle of the interval that follows
// it, and starting the count at zero delivers one cycle too many.
cnt <= {{(CNT_W-1){1'b0}}, 1'b1};
state <= S_BUF;
end
S_BUF: begin
// tBUF. A master that issues a START before this has elapsed produces a
// START the receiving devices may not recognise, and on a multi-master
// bus it is also how two masters collide without arbitrating.
if (cnt + 1 >= N_BUF) begin
bus_free <= 1'b1;
done <= 1'b1;
busy <= 1'b0;
state <= S_IDLE;
end else begin
cnt <= cnt + 1'b1;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_framer.vhd
-- START, repeated START and STOP generation: the edges the bit engine cannot produce.
-- Behavioural twin of i2c_framer.sv / .v.
--
-- WHY THIS IS A SEPARATE BLOCK. §3.1.1 defines START as "a HIGH to LOW transition on the
-- SDA line while SCL is HIGH" and STOP as the reverse. §3.1.2, the data rule, says "The
-- HIGH or LOW state of the data line can only change when the clock signal on the SCL line
-- is LOW."
--
-- Those are contradictory, deliberately: framing is recognisable precisely BECAUSE it is
-- the one thing a data bit may never do. The bit engine's whole job is to guarantee SDA is
-- stable while SCL is high; framing requires the opposite. So the two cannot be the same
-- block, and the separation is structural rather than stylistic.
--
-- EVERY SCL RELEASE IN THESE SEQUENCES WAITS FOR THE READBACK. A repeated START releases
-- SCL and must not measure tSU;STA until the line is actually high, because tSU;STA is a
-- setup time against the SDA falling edge and is meaningless if SCL has not risen. A framer
-- that counted from its own release would emit a START while SCL was still low -- which is
-- not a START at all but an ordinary data-bit change, and every device would read it as one.
--
-- AND THE COUNTING RULE, which cost three off-by-one errors before it was stated: THE CYCLE
-- THAT MAKES A TRANSITION IS ALREADY THE FIRST CYCLE OF THE INTERVAL THAT FOLLOWS IT. It
-- applies to tSU;STA, to tSU;STO and to tBUF, and starting any of those counts at zero
-- delivers one cycle too many.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_framer is
generic (
N_HD_STA : integer := 5; -- tHD;STA
N_SU_STA : integer := 6; -- tSU;STA
N_SU_STO : integer := 5; -- tSU;STO
N_BUF : integer := 6; -- tBUF
N_SU_DAT : integer := 3; -- tSU;DAT
CNT_W : integer := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
do_start : in std_logic;
do_restart : in std_logic;
do_stop : in std_logic;
scl_in : in std_logic;
sda_in : in std_logic;
-- THE HANDOVER. Between its own sequences the framer holds SCL low whenever a
-- transfer is open, because somebody has to: a released SCL with a transfer in
-- progress is a clock pulse nobody meant to send. `scl_yield` is the controller
-- saying "the SCL generator has it now", and it is asserted in the SAME cycle as the
-- generator's enable so the two drive SCL low together for that cycle and the line
-- never rises in between.
scl_yield : in std_logic;
sda_req : out std_logic;
sda_bit : out std_logic;
scl_drive_low : out std_logic;
busy : out std_logic;
done : out std_logic;
bus_free : out std_logic;
started : out std_logic;
stretch_wait : out std_logic;
starts : out unsigned(CNT_W-1 downto 0);
restarts : out unsigned(CNT_W-1 downto 0);
stops : out unsigned(CNT_W-1 downto 0);
state : out unsigned(3 downto 0)
);
end entity i2c_framer;
architecture rtl of i2c_framer is
constant S_IDLE : integer := 0;
constant S_STA_HOLD : integer := 2; -- tHD;STA, then pull SCL low
constant S_STA_SCL : integer := 3;
constant S_RS_SETUP : integer := 5; -- tSU;DAT before releasing SCL
constant S_RS_SCLHI : integer := 6; -- release SCL and WAIT for the readback
constant S_RS_SU : integer := 7; -- tSU;STA with both lines high
constant S_RS_HOLD : integer := 9; -- tHD;STA, then pull SCL low
constant S_STO_SDA : integer := 10; -- SDA low while SCL is low
constant S_STO_SCLHI: integer := 11; -- release SCL and WAIT for the readback
constant S_STO_SU : integer := 12; -- tSU;STO
constant S_STO_RISE : integer := 13; -- release SDA: THIS is the STOP edge
constant S_BUF : integer := 14; -- tBUF
signal st : integer range 0 to 14 := S_IDLE;
signal cnt : integer range 0 to 65535 := 0;
signal sreq, sbit, sclow, bsy, dn, bfree, strt, swait : std_logic;
signal nsta, nrs, nsto : unsigned(CNT_W-1 downto 0);
begin
sda_req <= sreq;
sda_bit <= sbit;
scl_drive_low <= sclow;
busy <= bsy;
done <= dn;
bus_free <= bfree;
started <= strt;
stretch_wait <= swait;
starts <= nsta;
restarts <= nrs;
stops <= nsto;
state <= to_unsigned(st, 4);
process (clk, rst_n)
begin
if rst_n = '0' then
st <= S_IDLE;
cnt <= 0;
sreq <= '0';
sbit <= '1';
sclow <= '0';
bsy <= '0';
dn <= '0';
bfree <= '1'; -- a reset master must assume nothing is in flight
strt <= '0';
swait <= '0';
nsta <= (others => '0');
nrs <= (others => '0');
nsto <= (others => '0');
elsif rising_edge(clk) then
dn <= '0';
swait <= '0';
case st is
when S_IDLE =>
bsy <= '0';
-- Hold SCL low while a transfer is open, unless yielding to the generator.
if strt = '1' and scl_yield = '0' then sclow <= '1';
else sclow <= '0';
end if;
if do_start = '1' and bfree = '1' then
-- Take SDA and pull it low while SCL is high. That transition IS the
-- START condition; nothing else has to happen for the bus to have seen
-- one, which is why the ordering here is the whole sequence.
sreq <= '1';
sbit <= '0';
bsy <= '1';
cnt <= 0;
st <= S_STA_HOLD;
elsif do_restart = '1' then
-- Mid-transfer, SCL is low. Release SDA first: a line that is already low
-- cannot produce a HIGH-to-LOW transition, so a repeated START after a
-- zero data bit needs SDA lifted before anything else.
sreq <= '1';
sbit <= '1';
bsy <= '1';
cnt <= 0;
st <= S_RS_SETUP;
elsif do_stop = '1' then
-- SDA must be LOW before SCL is released, so that releasing SDA
-- afterwards is a LOW-to-HIGH transition while SCL is high.
sreq <= '1';
sbit <= '0';
bsy <= '1';
cnt <= 0;
st <= S_STO_SDA;
end if;
-- ---- START from an idle bus -------------------------------------
when S_STA_HOLD =>
if cnt + 1 >= N_HD_STA then
sclow <= '1'; -- now pull SCL low: the transfer begins
cnt <= 0;
st <= S_STA_SCL;
else
cnt <= cnt + 1;
end if;
when S_STA_SCL =>
sreq <= '0'; -- hand SDA to the datapath
strt <= '1';
bfree <= '0';
nsta <= nsta + 1;
dn <= '1';
bsy <= '0';
st <= S_IDLE;
-- ---- repeated START ---------------------------------------------
when S_RS_SETUP =>
-- tSU;DAT with SDA released and SCL still low. Footnote [5] of Table 10 is
-- explicit that this set-up applies even when the clock is being stretched.
if cnt + 1 >= N_SU_DAT then
sclow <= '0'; -- release SCL
cnt <= 0;
st <= S_RS_SCLHI;
else
cnt <= cnt + 1;
end if;
when S_RS_SCLHI =>
-- Released. WAIT for the line. A target stretching here is legal and common
-- -- §3.1.6's byte-level handshake happens exactly at this boundary.
--
-- Note the counter's entry value: THIS cycle already has both lines high, so
-- it is the first cycle of tSU;STA. Entering with zero would deliver
-- N_SU_STA + 1 cycles, and the parameter would not mean the Table 10 number
-- it is named after.
if scl_in = '1' then
cnt <= 1;
st <= S_RS_SU;
else
swait <= '1';
end if;
when S_RS_SU =>
if cnt + 1 >= N_SU_STA then
sbit <= '0'; -- SDA falls: the repeated START edge
cnt <= 0;
st <= S_RS_HOLD;
else
cnt <= cnt + 1;
end if;
when S_RS_HOLD =>
if cnt + 1 >= N_HD_STA then
sclow <= '1';
sreq <= '0';
strt <= '1';
bfree <= '0';
nrs <= nrs + 1;
dn <= '1';
bsy <= '0';
cnt <= 0;
st <= S_IDLE;
else
cnt <= cnt + 1;
end if;
-- ---- STOP --------------------------------------------------------
when S_STO_SDA =>
if cnt + 1 >= N_SU_DAT then
sclow <= '0';
cnt <= 0;
st <= S_STO_SCLHI;
else
cnt <= cnt + 1;
end if;
when S_STO_SCLHI =>
-- Same counting rule as the repeated START: this cycle already has SCL high
-- with SDA low, so it is the first cycle of tSU;STO.
if scl_in = '1' then
cnt <= 1;
st <= S_STO_SU;
else
swait <= '1';
end if;
when S_STO_SU =>
if cnt + 1 >= N_SU_STO then
sbit <= '1'; -- release SDA: the STOP edge
cnt <= 0;
st <= S_STO_RISE;
else
cnt <= cnt + 1;
end if;
when S_STO_RISE =>
sreq <= '0';
strt <= '0';
bfree <= '0'; -- not free until tBUF has elapsed
nsto <= nsto + 1;
-- Both lines are released in THIS cycle, so it is the first cycle of tBUF --
-- the third place the counting rule applies.
cnt <= 1;
st <= S_BUF;
when S_BUF =>
-- A master that issues a START before tBUF has elapsed produces a START the
-- receiving devices may not recognise, and on a multi-master bus it is also
-- how two masters collide without arbitrating.
if cnt + 1 >= N_BUF then
bfree <= '1';
dn <= '1';
bsy <= '0';
st <= S_IDLE;
else
cnt <= cnt + 1;
end if;
when others =>
st <= S_IDLE;
end case;
end if;
end process;
end architecture rtl;5a. The testbenches
Twelve tests, and the bench instantiates a protocol monitor that sees only the two wires. That is deliberate: a framer that sets its internal flags correctly while producing nothing on the bus must fail, and only a wire-level observer can establish that.
| # | Test | Property |
|---|---|---|
| T1 | reset releases both lines, bus assumed free | a reset master must assume nothing is in flight |
| T2 | a START — the monitor reports exactly one | flags are not evidence; the wire is |
| T3 | tHD;STA — the hold precedes SCL falling | measured, not asserted |
| T4 | a STOP — SDA rises while SCL is high, tSU;STO precedes it | |
| T5 | tBUF — the bus is not free the instant the STOP happens | |
| T6 | a START before tBUF is refused, not queued, not rushed | plus the counter cross-check below |
| T7 | a repeated START from mid-transfer — SDA lifted first | |
| T8 | the stretch, on a repeated START | the framer waits for the real rise |
| T9 | the stretch, on a STOP | same requirement, same mechanism |
| T10 | the framer produces no other edges | over a full S, Sr and P sequence |
| T11 | counters distinguish an initial START from a repeated one | |
| T12 | idle means silent | no command, nothing driven |
Every wait is bounded — wait_done(200), wait_state(…, 400) — so a framer that never completes a sequence reports a failure instead of hanging the regression.
`timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_framer_tb.sv
// Independent oracle for i2c_framer.
//
// The framer drives a real wired-AND bus, and the checks are made by i2c_proto_mon --
// which sees only the two wires and applies §3.1.1 as written. So a framer that reaches
// the right states by the wrong edges fails here, which is the only interesting way for
// a framer to be wrong.
//
// The bench is the second device on the bus and can stretch SCL, so the two sequences
// that release SCL mid-way -- the repeated START and the STOP -- are tested against a
// target that does not let go immediately.
// -----------------------------------------------------------------------------
module i2c_framer_tb;
localparam integer NHD = 5; // tHD;STA
localparam integer NSUA = 6; // tSU;STA
localparam integer NSUO = 5; // tSU;STO
localparam integer NBUF = 6; // tBUF
localparam integer NSUD = 3; // tSU;DAT
localparam [3:0] S_IDLE = 4'd0, S_RS_SCLHI = 4'd6, S_STO_SCLHI = 4'd11;
logic clk = 1'b0, rst_n = 1'b0;
logic do_start = 1'b0, do_restart = 1'b0, do_stop = 1'b0;
// the bench as a second device
logic tgt_scl_low = 1'b0, tgt_sda_low = 1'b0;
logic f_sda_req, f_sda_bit, f_scl_low;
logic f_busy, f_done, f_bus_free, f_started, f_stretch;
logic [15:0] n_sta, n_rs, n_sto;
logic [3:0] fstate;
// The framer owns SDA alone in this bench, so the inversion is applied here the way
// i2c_sda_ctrl applies it: a transmitted one releases, a zero pulls down.
wire f_sda_low = f_sda_req ? ~f_sda_bit : 1'b0;
logic scl, sda;
logic [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
logic [7:0] scl_h, sda_h;
i2c_line_model #(.N_DEV(2)) bus (
.scl_drive_low({tgt_scl_low, f_scl_low}),
.sda_drive_low({tgt_sda_low, f_sda_low}),
.scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
.scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
.scl_holders(scl_h), .sda_holders(sda_h));
i2c_framer #(.N_HD_STA(NHD), .N_SU_STA(NSUA), .N_SU_STO(NSUO),
.N_BUF(NBUF), .N_SU_DAT(NSUD), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n),
.do_start(do_start), .do_restart(do_restart), .do_stop(do_stop),
.scl_in(scl_in[0]), .sda_in(sda_in[0]), .scl_yield(1'b0),
.sda_req(f_sda_req), .sda_bit(f_sda_bit), .scl_drive_low(f_scl_low),
.busy(f_busy), .done(f_done), .bus_free(f_bus_free), .started(f_started),
.stretch_wait(f_stretch),
.starts(n_sta), .restarts(n_rs), .stops(n_sto), .state(fstate));
// The oracle: it sees only the wires.
logic m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv, m_intr, m_midbyte;
logic [7:0] m_byteval;
logic [3:0] m_bidx;
logic [15:0] m_nsta, m_nsto, m_nbyte, m_nmid;
i2c_proto_mon #(.CNT_W(16)) mon (
.clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
.start_seen(m_start), .stop_seen(m_stop),
.bit_seen(m_bit), .bit_val(m_bitv),
.byte_seen(m_byte), .byte_val(m_byteval),
.ack_seen(m_ack), .ack_val(m_ackv),
.in_transfer(m_intr), .framing_midbyte(m_midbyte), .bit_index(m_bidx),
.n_starts(m_nsta), .n_stops(m_nsto), .n_bytes(m_nbyte), .n_midbyte(m_nmid));
always #5 clk = ~clk;
integer errors = 0;
integer n, k, j;
// Measure the hold between the START edge on the wire and SCL going low.
integer hd_run, meas_hd; reg arm_hd;
// Measure tSU;STO: how long SCL was high with SDA low before the STOP edge.
integer su_run, meas_su;
// Measure tBUF: cycles from the STOP edge until bus_free.
integer buf_run, meas_buf; reg arm_buf;
// The bench detects the framing edges ITSELF, from the lines, rather than using the
// monitor's outputs. The monitor is registered, so its start_seen arrives a cycle
// after the edge and an arming cycle after that -- which would understate every
// measured interval by two cycles. A measurement of a Table 10 parameter cannot
// afford that, so the edge detection is local and combinational in intent.
logic scl_l, sda_l;
always @(negedge clk) begin
if (rst_n) begin
// tHD;STA: from the START edge (SDA falls while SCL high) until SCL falls.
if (sda_l && !sda && scl) begin arm_hd = 1'b1; hd_run = 1; end
else if (arm_hd) begin
if (scl) hd_run = hd_run + 1;
else begin meas_hd = hd_run; arm_hd = 1'b0; end
end
// tSU;STO: cycles with SCL high and SDA low, ending at the STOP edge.
if (scl && !sda) su_run = su_run + 1;
else if (!scl) su_run = 0;
if (!sda_l && sda && scl) meas_su = su_run;
// tBUF: from the STOP edge until bus_free is asserted.
if (!sda_l && sda && scl) begin arm_buf = 1'b1; buf_run = 0; end
else if (arm_buf) begin
buf_run = buf_run + 1;
if (f_bus_free) begin meas_buf = buf_run; arm_buf = 1'b0; end
end
scl_l = scl; sda_l = sda;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; do_start = 1'b0; do_restart = 1'b0; do_stop = 1'b0;
tgt_scl_low = 1'b0; tgt_sda_low = 1'b0;
hd_run = 0; meas_hd = 0; arm_hd = 1'b0;
su_run = 0; meas_su = 0;
buf_run = 0; meas_buf = 0; arm_buf = 1'b0;
scl_l = 1'b1; sda_l = 1'b1;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
task pulse_start; begin @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0; end endtask
task pulse_restart; begin @(negedge clk); do_restart = 1'b1; @(posedge clk); @(negedge clk); do_restart = 1'b0; end endtask
task pulse_stop; begin @(negedge clk); do_stop = 1'b1; @(posedge clk); @(negedge clk); do_stop = 1'b0; end endtask
task wait_done (input integer max_cycles);
begin
n = 0;
while (!f_done && n < max_cycles) begin step; n = n + 1; end
if (n >= max_cycles) begin
$display(" FAIL wait_done: no completion after %0d cycles (state %0d)",
max_cycles, fstate);
errors = errors + 1;
end
end
endtask
task wait_state (input [3:0] want, input integer max_cycles);
begin
n = 0;
while (fstate != want && n < max_cycles) begin step; n = n + 1; end
if (n >= max_cycles) begin
$display(" FAIL wait_state: state %0d not reached (now %0d)", want, fstate);
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
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
initial begin
$display("=== i2c_framer: the edges a data bit is forbidden to make ===");
// ----------------------------------------------------------------
// T1. Reset releases both lines, and the bus is assumed free.
// ----------------------------------------------------------------
do_reset;
$display("T1 a reset framer releases both lines");
ck_bit("T1 SCL released", f_scl_low, 1'b0);
ck_bit("T1 SDA released", f_sda_low, 1'b0);
ck_bit("T1 both lines high", scl & sda, 1'b1);
ck_bit("T1 the bus is free", f_bus_free, 1'b1);
ck_bit("T1 not in a transfer", m_intr, 1'b0);
ck_int("T1 no framing seen", m_nsta + m_nsto, 0);
// ----------------------------------------------------------------
// T2. A START. The monitor -- which sees only the wires -- must report one, and
// the transfer must be open afterwards.
// ----------------------------------------------------------------
pulse_start;
wait_done(200);
$display("T2 the monitor sees a START on the wire, having seen only the wires");
ck_int("T2 exactly one START", m_nsta, 1);
ck_int("T2 no STOP", m_nsto, 0);
ck_bit("T2 the transfer is open", m_intr, 1'b1);
ck_bit("T2 the framer says it started", f_started, 1'b1);
ck_bit("T2 SCL is now low, ready for the first bit", scl, 1'b0);
ck_bit("T2 and SDA has been handed back", f_sda_req, 1'b0);
// ----------------------------------------------------------------
// T3. tHD;STA. The START edge is followed by the hold time BEFORE SCL falls.
// A framer that pulled SCL low immediately would produce an edge the
// receiving devices are not obliged to recognise.
// ----------------------------------------------------------------
$display("T3 tHD;STA elapses between the START edge and SCL falling");
ck_int("T3 the hold is N_HD_STA cycles", meas_hd, NHD);
// ----------------------------------------------------------------
// T4. A STOP. SDA rises while SCL is high, and tSU;STO precedes it.
// ----------------------------------------------------------------
pulse_stop;
wait_done(200);
$display("T4 a STOP: SDA rises while SCL is high, after tSU;STO");
ck_int("T4 exactly one STOP", m_nsto, 1);
ck_bit("T4 the transfer is closed", m_intr, 1'b0);
ck_bit("T4 the framer says so", f_started, 1'b0);
ck_int("T4 tSU;STO before the edge", meas_su, NSUO);
ck_bit("T4 both lines released", f_scl_low | f_sda_low, 1'b0);
// ----------------------------------------------------------------
// T5. tBUF. The bus is not free the instant the STOP happens.
// ----------------------------------------------------------------
// One step before reading the measurement. SystemVerilog does not need it -- the
// measuring block uses blocking assignments, which are visible within the same time
// step -- but the VHDL twin does, because a VHDL signal read in the delta it is
// assigned still holds its old value. The step is kept in all three benches so the
// three files stay line-for-line comparable and finish at the same time, which is
// the evidence that they are running the same test.
step;
$display("T5 tBUF elapses after the STOP before the bus is free again");
ck_int("T5 the bus-free delay is N_BUF cycles", meas_buf, NBUF);
ck_bit("T5 and then it is free", f_bus_free, 1'b1);
// ----------------------------------------------------------------
// T6. A START requested before tBUF has elapsed is REFUSED, not queued and not
// issued early. Issuing it early is how two masters collide without ever
// arbitrating, because neither saw the other's START.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
// THE CROSS-CHECK. With a transfer open the bus is not free, and the framer is
// back in S_IDLE able to sample a command -- the only reachable state in which
// `bus_free` is 0 and a command can be accepted, so the only place the guard
// can be tested. Asking after tBUF has elapsed tests nothing, because
// `bus_free` is 1 by then.
//
// But refusing is not observable on the WIRE here: mid-transfer the framer is
// holding SCL low, so a wrongly-accepted START pulls SDA low while SCL is low,
// which is an ordinary data change and produces no START for the monitor to
// see. The defect is therefore invisible to any bus-level check.
//
// What catches it is the invariant between the two counters: every START the
// framer BELIEVES it issued must have been visible on the bus. A framer that
// accepts a command it should have refused increments its own counter without
// the monitor seeing anything, and the two diverge.
ck_bit("T6 with a transfer open the bus is not free", f_bus_free, 1'b0);
k = m_nsta;
j = n_sta;
@(negedge clk); do_start = 1'b1;
n = 0;
while (n < 30) begin step; n = n + 1; end
@(negedge clk); do_start = 1'b0;
ck_int("T6 a START with the bus not free is refused", m_nsta, k);
ck_int("T6 and the framer did not count one either", n_sta, j);
ck_int("T6 every START counted was seen on the wire", n_sta, m_nsta);
pulse_stop;
// Ask for a START while the STOP sequence is still running out tBUF.
wait_state(S_IDLE, 400);
k = m_nsta;
@(negedge clk); do_start = 1'b1;
// Hold the request through the whole bus-free period.
n = 0;
while (!f_bus_free && n < 200) begin step; n = n + 1; end
$display("T6 a START asked for before tBUF has elapsed is refused, not rushed");
ck_int("T6 no extra START was issued during tBUF", m_nsta, k);
// and once the bus IS free, the held request is honoured
wait_done(200);
@(negedge clk); do_start = 1'b0;
ck_int("T6 and it is honoured once the bus is free", m_nsta, k + 1);
// ----------------------------------------------------------------
// T7. A REPEATED START from mid-transfer. SDA must be lifted first: a line that
// is already low cannot make a HIGH-to-LOW transition, so a repeated START
// after a zero data bit needs SDA released before anything else.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
// Simulate a data bit having left SDA low: the bench holds it down, then lets go
// when the framer takes over.
k = m_nsta;
pulse_restart;
wait_done(400);
$display("T7 a repeated START from mid-transfer, with SDA lifted first");
ck_int("T7 a second START was seen on the wire", m_nsta, k + 1);
ck_int("T7 and no STOP", m_nsto, 0);
ck_bit("T7 the transfer is still open", m_intr, 1'b1);
ck_int("T7 counted as a repeated START", n_rs, 1);
ck_int("T7 and not as an initial one", n_sta, 1);
ck_bit("T7 SCL is low afterwards", scl, 1'b0);
// ----------------------------------------------------------------
// T8. THE STRETCH, on a repeated START. The framer releases SCL and must wait
// for the LINE before timing tSU;STA. A framer that counted from its own
// release would drop SDA while SCL was still low -- which is not a START at
// all but an ordinary data-bit change.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
@(negedge clk); tgt_scl_low = 1'b1; // the target stretches
k = m_nsta;
pulse_restart;
wait_state(S_RS_SCLHI, 200);
$display("T8 a repeated START waits for SCL to really rise before tSU;STA");
ck_bit("T8 the framer has released SCL", f_scl_low, 1'b0);
ck_bit("T8 the line is still low", scl, 1'b0);
step;
ck_bit("T8 and it reports waiting", f_stretch, 1'b1);
for (n = 0; n < 40; n = n + 1) begin
step;
ck_int("T8 no START was emitted while SCL was held", m_nsta, k);
end
@(negedge clk); tgt_scl_low = 1'b0;
wait_done(400);
ck_int("T8 and the START appears once SCL rises", m_nsta, k + 1);
// ----------------------------------------------------------------
// T9. THE STRETCH, on a STOP. Same requirement, same mechanism: releasing SDA
// while SCL is low is not a STOP.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
@(negedge clk); tgt_scl_low = 1'b1;
k = m_nsto;
pulse_stop;
wait_state(S_STO_SCLHI, 200);
$display("T9 a STOP waits for SCL to really rise before tSU;STO");
ck_bit("T9 SCL released by the framer", f_scl_low, 1'b0);
ck_bit("T9 the line is held low by the target", scl, 1'b0);
for (n = 0; n < 40; n = n + 1) begin
step;
ck_int("T9 no STOP was emitted while SCL was held", m_nsto, k);
end
@(negedge clk); tgt_scl_low = 1'b0;
wait_done(400);
ck_int("T9 and the STOP appears once SCL rises", m_nsto, k + 1);
ck_bit("T9 the transfer is closed", m_intr, 1'b0);
// ----------------------------------------------------------------
// T10. The framer produces no OTHER edges. Over a full START, repeated START and
// STOP sequence the monitor must see exactly three framing events and no
// no bytes, because nothing was clocked.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(400);
pulse_restart; wait_done(400);
pulse_stop; wait_done(400);
$display("T10 three sequences produce exactly three framing events");
ck_int("T10 two STARTs", m_nsta, 2);
ck_int("T10 one STOP", m_nsto, 1);
ck_int("T10 no bytes, because nothing was clocked", m_nbyte, 0);
// No mid-byte reports. Each of the repeated START and the STOP does produce one
// clock pulse of its own -- releasing SCL is a rising edge, and a receiver samples
// there -- but one pulse is what every conforming framing sequence produces, so
// the monitor's threshold is two. See i2c_proto_mon's header.
ck_int("T10 no mid-byte framing reports", m_nmid, 0);
// ----------------------------------------------------------------
// T11. Counters distinguish an initial START from a repeated one, which the
// WIRE cannot: both are the same edge. Only the framer knows which it
// issued, and a design that conflated them could not report a protocol
// error that depends on the difference.
// ----------------------------------------------------------------
$display("T11 the framer distinguishes an initial START from a repeated one");
ck_int("T11 one initial START", n_sta, 1);
ck_int("T11 one repeated START", n_rs, 1);
ck_int("T11 one STOP", n_sto, 1);
ck_int("T11 and the wire saw two STARTs, indistinguishably", m_nsta, 2);
// ----------------------------------------------------------------
// T12. Idle means silent. With no command, the framer drives nothing for as long
// as you care to wait.
// ----------------------------------------------------------------
do_reset;
$display("T12 an idle framer drives nothing at all");
for (n = 0; n < 60; n = n + 1) begin
step;
ck_bit("T12 SCL untouched", f_scl_low, 1'b0);
ck_bit("T12 SDA untouched", f_sda_req, 1'b0);
end
ck_int("T12 and nothing was seen on the wire", m_nsta + m_nsto, 0);
ck_int("T12 still idle", fstate, S_IDLE);
if (errors == 0)
$display("=== i2c_framer: ALL CHECKS PASSED ===");
else
$display("=== i2c_framer: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_framer_tb.sv
// Independent oracle for i2c_framer.
//
// The framer drives a real wired-AND bus, and the checks are made by i2c_proto_mon --
// which sees only the two wires and applies §3.1.1 as written. So a framer that reaches
// the right states by the wrong edges fails here, which is the only interesting way for
// a framer to be wrong.
//
// The bench is the second device on the bus and can stretch SCL, so the two sequences
// that release SCL mid-way -- the repeated START and the STOP -- are tested against a
// target that does not let go immediately.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_framer_tb;
localparam integer NHD = 5; // tHD;STA
localparam integer NSUA = 6; // tSU;STA
localparam integer NSUO = 5; // tSU;STO
localparam integer NBUF = 6; // tBUF
localparam integer NSUD = 3; // tSU;DAT
localparam [3:0] S_IDLE = 4'd0, S_RS_SCLHI = 4'd6, S_STO_SCLHI = 4'd11;
reg clk = 1'b0, rst_n = 1'b0;
reg do_start = 1'b0, do_restart = 1'b0, do_stop = 1'b0;
// the bench as a second device
reg tgt_scl_low = 1'b0, tgt_sda_low = 1'b0;
wire f_sda_req, f_sda_bit, f_scl_low;
wire f_busy, f_done, f_bus_free, f_started, f_stretch;
wire [15:0] n_sta, n_rs, n_sto;
wire [3:0] fstate;
// The framer owns SDA alone in this bench, so the inversion is applied here the way
// i2c_sda_ctrl applies it: a transmitted one releases, a zero pulls down.
wire f_sda_low = f_sda_req ? ~f_sda_bit : 1'b0;
wire scl, sda;
wire [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
wire [7:0] scl_h, sda_h;
i2c_line_model #(.N_DEV(2)) bus (
.scl_drive_low({tgt_scl_low, f_scl_low}),
.sda_drive_low({tgt_sda_low, f_sda_low}),
.scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
.scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
.scl_holders(scl_h), .sda_holders(sda_h));
i2c_framer #(.N_HD_STA(NHD), .N_SU_STA(NSUA), .N_SU_STO(NSUO),
.N_BUF(NBUF), .N_SU_DAT(NSUD), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n),
.do_start(do_start), .do_restart(do_restart), .do_stop(do_stop),
.scl_in(scl_in[0]), .sda_in(sda_in[0]), .scl_yield(1'b0),
.sda_req(f_sda_req), .sda_bit(f_sda_bit), .scl_drive_low(f_scl_low),
.busy(f_busy), .done(f_done), .bus_free(f_bus_free), .started(f_started),
.stretch_wait(f_stretch),
.starts(n_sta), .restarts(n_rs), .stops(n_sto), .state(fstate));
// The oracle: it sees only the wires.
wire m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv, m_intr, m_midbyte;
wire [7:0] m_byteval;
wire [3:0] m_bidx;
wire [15:0] m_nsta, m_nsto, m_nbyte, m_nmid;
i2c_proto_mon #(.CNT_W(16)) mon (
.clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
.start_seen(m_start), .stop_seen(m_stop),
.bit_seen(m_bit), .bit_val(m_bitv),
.byte_seen(m_byte), .byte_val(m_byteval),
.ack_seen(m_ack), .ack_val(m_ackv),
.in_transfer(m_intr), .framing_midbyte(m_midbyte), .bit_index(m_bidx),
.n_starts(m_nsta), .n_stops(m_nsto), .n_bytes(m_nbyte), .n_midbyte(m_nmid));
always #5 clk = ~clk;
integer errors = 0;
integer n, k, j;
// Measure the hold between the START edge on the wire and SCL going low.
integer hd_run, meas_hd; reg arm_hd;
// Measure tSU;STO: how long SCL was high with SDA low before the STOP edge.
integer su_run, meas_su;
// Measure tBUF: cycles from the STOP edge until bus_free.
integer buf_run, meas_buf; reg arm_buf;
// The bench detects the framing edges ITSELF, from the lines, rather than using the
// monitor's outputs. The monitor is registered, so its start_seen arrives a cycle
// after the edge and an arming cycle after that -- which would understate every
// measured interval by two cycles. A measurement of a Table 10 parameter cannot
// afford that, so the edge detection is local and combinational in intent.
reg scl_l, sda_l;
always @(negedge clk) begin
if (rst_n) begin
// tHD;STA: from the START edge (SDA falls while SCL high) until SCL falls.
if (sda_l && !sda && scl) begin arm_hd = 1'b1; hd_run = 1; end
else if (arm_hd) begin
if (scl) hd_run = hd_run + 1;
else begin meas_hd = hd_run; arm_hd = 1'b0; end
end
// tSU;STO: cycles with SCL high and SDA low, ending at the STOP edge.
if (scl && !sda) su_run = su_run + 1;
else if (!scl) su_run = 0;
if (!sda_l && sda && scl) meas_su = su_run;
// tBUF: from the STOP edge until bus_free is asserted.
if (!sda_l && sda && scl) begin arm_buf = 1'b1; buf_run = 0; end
else if (arm_buf) begin
buf_run = buf_run + 1;
if (f_bus_free) begin meas_buf = buf_run; arm_buf = 1'b0; end
end
scl_l = scl; sda_l = sda;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; do_start = 1'b0; do_restart = 1'b0; do_stop = 1'b0;
tgt_scl_low = 1'b0; tgt_sda_low = 1'b0;
hd_run = 0; meas_hd = 0; arm_hd = 1'b0;
su_run = 0; meas_su = 0;
buf_run = 0; meas_buf = 0; arm_buf = 1'b0;
scl_l = 1'b1; sda_l = 1'b1;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
task pulse_start; begin @(negedge clk); do_start = 1'b1; @(posedge clk); @(negedge clk); do_start = 1'b0; end endtask
task pulse_restart; begin @(negedge clk); do_restart = 1'b1; @(posedge clk); @(negedge clk); do_restart = 1'b0; end endtask
task pulse_stop; begin @(negedge clk); do_stop = 1'b1; @(posedge clk); @(negedge clk); do_stop = 1'b0; end endtask
task wait_done (input integer max_cycles);
begin
n = 0;
while (!f_done && n < max_cycles) begin step; n = n + 1; end
if (n >= max_cycles) begin
$display(" FAIL wait_done: no completion after %0d cycles (state %0d)",
max_cycles, fstate);
errors = errors + 1;
end
end
endtask
task wait_state (input [3:0] want, input integer max_cycles);
begin
n = 0;
while (fstate != want && n < max_cycles) begin step; n = n + 1; end
if (n >= max_cycles) begin
$display(" FAIL wait_state: state %0d not reached (now %0d)", want, fstate);
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
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
initial begin
$display("=== i2c_framer: the edges a data bit is forbidden to make ===");
// ----------------------------------------------------------------
// T1. Reset releases both lines, and the bus is assumed free.
// ----------------------------------------------------------------
do_reset;
$display("T1 a reset framer releases both lines");
ck_bit("T1 SCL released", f_scl_low, 1'b0);
ck_bit("T1 SDA released", f_sda_low, 1'b0);
ck_bit("T1 both lines high", scl & sda, 1'b1);
ck_bit("T1 the bus is free", f_bus_free, 1'b1);
ck_bit("T1 not in a transfer", m_intr, 1'b0);
ck_int("T1 no framing seen", m_nsta + m_nsto, 0);
// ----------------------------------------------------------------
// T2. A START. The monitor -- which sees only the wires -- must report one, and
// the transfer must be open afterwards.
// ----------------------------------------------------------------
pulse_start;
wait_done(200);
$display("T2 the monitor sees a START on the wire, having seen only the wires");
ck_int("T2 exactly one START", m_nsta, 1);
ck_int("T2 no STOP", m_nsto, 0);
ck_bit("T2 the transfer is open", m_intr, 1'b1);
ck_bit("T2 the framer says it started", f_started, 1'b1);
ck_bit("T2 SCL is now low, ready for the first bit", scl, 1'b0);
ck_bit("T2 and SDA has been handed back", f_sda_req, 1'b0);
// ----------------------------------------------------------------
// T3. tHD;STA. The START edge is followed by the hold time BEFORE SCL falls.
// A framer that pulled SCL low immediately would produce an edge the
// receiving devices are not obliged to recognise.
// ----------------------------------------------------------------
$display("T3 tHD;STA elapses between the START edge and SCL falling");
ck_int("T3 the hold is N_HD_STA cycles", meas_hd, NHD);
// ----------------------------------------------------------------
// T4. A STOP. SDA rises while SCL is high, and tSU;STO precedes it.
// ----------------------------------------------------------------
pulse_stop;
wait_done(200);
$display("T4 a STOP: SDA rises while SCL is high, after tSU;STO");
ck_int("T4 exactly one STOP", m_nsto, 1);
ck_bit("T4 the transfer is closed", m_intr, 1'b0);
ck_bit("T4 the framer says so", f_started, 1'b0);
ck_int("T4 tSU;STO before the edge", meas_su, NSUO);
ck_bit("T4 both lines released", f_scl_low | f_sda_low, 1'b0);
// ----------------------------------------------------------------
// T5. tBUF. The bus is not free the instant the STOP happens.
// ----------------------------------------------------------------
// One step before reading the measurement. SystemVerilog does not need it -- the
// measuring block uses blocking assignments, which are visible within the same time
// step -- but the VHDL twin does, because a VHDL signal read in the delta it is
// assigned still holds its old value. The step is kept in all three benches so the
// three files stay line-for-line comparable and finish at the same time, which is
// the evidence that they are running the same test.
step;
$display("T5 tBUF elapses after the STOP before the bus is free again");
ck_int("T5 the bus-free delay is N_BUF cycles", meas_buf, NBUF);
ck_bit("T5 and then it is free", f_bus_free, 1'b1);
// ----------------------------------------------------------------
// T6. A START requested before tBUF has elapsed is REFUSED, not queued and not
// issued early. Issuing it early is how two masters collide without ever
// arbitrating, because neither saw the other's START.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
// THE CROSS-CHECK. With a transfer open the bus is not free, and the framer is
// back in S_IDLE able to sample a command -- the only reachable state in which
// `bus_free` is 0 and a command can be accepted, so the only place the guard
// can be tested. Asking after tBUF has elapsed tests nothing.
//
// Refusing is not observable on the WIRE here: mid-transfer the framer holds SCL
// low, so a wrongly-accepted START pulls SDA low while SCL is low -- an ordinary
// data change, producing no START for the monitor to see. What catches it is the
// invariant between the two counters: every START the framer BELIEVES it issued
// must have been visible on the bus.
ck_bit("T6 with a transfer open the bus is not free", f_bus_free, 1'b0);
k = m_nsta;
j = n_sta;
@(negedge clk); do_start = 1'b1;
n = 0;
while (n < 30) begin step; n = n + 1; end
@(negedge clk); do_start = 1'b0;
ck_int("T6 a START with the bus not free is refused", m_nsta, k);
ck_int("T6 and the framer did not count one either", n_sta, j);
ck_int("T6 every START counted was seen on the wire", n_sta, m_nsta);
pulse_stop;
// Ask for a START while the STOP sequence is still running out tBUF.
wait_state(S_IDLE, 400);
k = m_nsta;
@(negedge clk); do_start = 1'b1;
// Hold the request through the whole bus-free period.
n = 0;
while (!f_bus_free && n < 200) begin step; n = n + 1; end
$display("T6 a START asked for before tBUF has elapsed is refused, not rushed");
ck_int("T6 no extra START was issued during tBUF", m_nsta, k);
// and once the bus IS free, the held request is honoured
wait_done(200);
@(negedge clk); do_start = 1'b0;
ck_int("T6 and it is honoured once the bus is free", m_nsta, k + 1);
// ----------------------------------------------------------------
// T7. A REPEATED START from mid-transfer. SDA must be lifted first: a line that
// is already low cannot make a HIGH-to-LOW transition, so a repeated START
// after a zero data bit needs SDA released before anything else.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
// Simulate a data bit having left SDA low: the bench holds it down, then lets go
// when the framer takes over.
k = m_nsta;
pulse_restart;
wait_done(400);
$display("T7 a repeated START from mid-transfer, with SDA lifted first");
ck_int("T7 a second START was seen on the wire", m_nsta, k + 1);
ck_int("T7 and no STOP", m_nsto, 0);
ck_bit("T7 the transfer is still open", m_intr, 1'b1);
ck_int("T7 counted as a repeated START", n_rs, 1);
ck_int("T7 and not as an initial one", n_sta, 1);
ck_bit("T7 SCL is low afterwards", scl, 1'b0);
// ----------------------------------------------------------------
// T8. THE STRETCH, on a repeated START. The framer releases SCL and must wait
// for the LINE before timing tSU;STA. A framer that counted from its own
// release would drop SDA while SCL was still low -- which is not a START at
// all but an ordinary data-bit change.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
@(negedge clk); tgt_scl_low = 1'b1; // the target stretches
k = m_nsta;
pulse_restart;
wait_state(S_RS_SCLHI, 200);
$display("T8 a repeated START waits for SCL to really rise before tSU;STA");
ck_bit("T8 the framer has released SCL", f_scl_low, 1'b0);
ck_bit("T8 the line is still low", scl, 1'b0);
step;
ck_bit("T8 and it reports waiting", f_stretch, 1'b1);
for (n = 0; n < 40; n = n + 1) begin
step;
ck_int("T8 no START was emitted while SCL was held", m_nsta, k);
end
@(negedge clk); tgt_scl_low = 1'b0;
wait_done(400);
ck_int("T8 and the START appears once SCL rises", m_nsta, k + 1);
// ----------------------------------------------------------------
// T9. THE STRETCH, on a STOP. Same requirement, same mechanism: releasing SDA
// while SCL is low is not a STOP.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(200);
@(negedge clk); tgt_scl_low = 1'b1;
k = m_nsto;
pulse_stop;
wait_state(S_STO_SCLHI, 200);
$display("T9 a STOP waits for SCL to really rise before tSU;STO");
ck_bit("T9 SCL released by the framer", f_scl_low, 1'b0);
ck_bit("T9 the line is held low by the target", scl, 1'b0);
for (n = 0; n < 40; n = n + 1) begin
step;
ck_int("T9 no STOP was emitted while SCL was held", m_nsto, k);
end
@(negedge clk); tgt_scl_low = 1'b0;
wait_done(400);
ck_int("T9 and the STOP appears once SCL rises", m_nsto, k + 1);
ck_bit("T9 the transfer is closed", m_intr, 1'b0);
// ----------------------------------------------------------------
// T10. The framer produces no OTHER edges. Over a full START, repeated START and
// STOP sequence the monitor must see exactly three framing events and no
// no bytes, because nothing was clocked.
// ----------------------------------------------------------------
do_reset;
pulse_start; wait_done(400);
pulse_restart; wait_done(400);
pulse_stop; wait_done(400);
$display("T10 three sequences produce exactly three framing events");
ck_int("T10 two STARTs", m_nsta, 2);
ck_int("T10 one STOP", m_nsto, 1);
ck_int("T10 no bytes, because nothing was clocked", m_nbyte, 0);
// No mid-byte reports. Each of the repeated START and the STOP does produce one
// clock pulse of its own -- releasing SCL is a rising edge, and a receiver samples
// there -- but one pulse is what every conforming framing sequence produces, so
// the monitor's threshold is two. See i2c_proto_mon's header.
ck_int("T10 no mid-byte framing reports", m_nmid, 0);
// ----------------------------------------------------------------
// T11. Counters distinguish an initial START from a repeated one, which the
// WIRE cannot: both are the same edge. Only the framer knows which it
// issued, and a design that conflated them could not report a protocol
// error that depends on the difference.
// ----------------------------------------------------------------
$display("T11 the framer distinguishes an initial START from a repeated one");
ck_int("T11 one initial START", n_sta, 1);
ck_int("T11 one repeated START", n_rs, 1);
ck_int("T11 one STOP", n_sto, 1);
ck_int("T11 and the wire saw two STARTs, indistinguishably", m_nsta, 2);
// ----------------------------------------------------------------
// T12. Idle means silent. With no command, the framer drives nothing for as long
// as you care to wait.
// ----------------------------------------------------------------
do_reset;
$display("T12 an idle framer drives nothing at all");
for (n = 0; n < 60; n = n + 1) begin
step;
ck_bit("T12 SCL untouched", f_scl_low, 1'b0);
ck_bit("T12 SDA untouched", f_sda_req, 1'b0);
end
ck_int("T12 and nothing was seen on the wire", m_nsta + m_nsto, 0);
ck_int("T12 still idle", fstate, S_IDLE);
if (errors == 0)
$display("=== i2c_framer: ALL CHECKS PASSED ===");
else
$display("=== i2c_framer: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_framer_tb.vhd
-- Independent oracle for i2c_framer. Behavioural twin of the SV and Verilog benches.
--
-- The framer drives a real wired-AND bus, and the checks are made by i2c_proto_mon, which
-- sees only the two wires and applies §3.1.1 as written. So a framer that reaches the right
-- states by the wrong edges fails here, which is the only interesting way for a framer to be
-- wrong.
--
-- The bench is the second device on the bus and can stretch SCL, so the two sequences that
-- release SCL mid-way -- the repeated START and the STOP -- are tested against a target that
-- does not let go immediately.
--
-- Note that the intervals are measured LOCALLY from the lines rather than from the monitor's
-- outputs. The monitor is registered, so its start_seen arrives a cycle after the edge and an
-- arming cycle after that, which would understate every measured interval by two cycles. A
-- measurement of a Table 10 parameter cannot afford that.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_framer_tb is
end entity i2c_framer_tb;
architecture sim of i2c_framer_tb is
constant TCLK : time := 10 ns;
constant NHD : integer := 5;
constant NSUA : integer := 6;
constant NSUO : integer := 5;
constant NBUF : integer := 6;
constant NSUD : integer := 3;
constant S_IDLE : integer := 0;
constant S_RS_SCLHI : integer := 6;
constant S_STO_SCLHI : integer := 11;
signal clk, rst_n : std_logic := '0';
signal do_start, do_restart, do_stop : std_logic := '0';
signal tgt_scl_low, tgt_sda_low : std_logic := '0';
signal f_sda_req, f_sda_bit, f_scl_low : std_logic;
signal f_busy, f_done, f_bus_free, f_started, f_stretch : std_logic;
signal n_sta, n_rs, n_sto : unsigned(15 downto 0);
signal fstate : unsigned(3 downto 0);
-- The framer owns SDA alone here, so the inversion is applied the way i2c_sda_ctrl does.
signal f_sda_low : std_logic;
signal scl_drv, sda_drv : std_logic_vector(1 downto 0);
signal scl, sda : std_logic;
signal scl_in, sda_in, scl_rbl, sda_rbl : std_logic_vector(1 downto 0);
signal scl_h, sda_h : unsigned(7 downto 0);
signal m_start, m_stop, m_bit, m_bitv, m_byte, m_ack, m_ackv : std_logic;
signal m_intr, m_midbyte : std_logic;
signal m_byteval : std_logic_vector(7 downto 0);
signal m_bidx : unsigned(3 downto 0);
signal m_nsta, m_nsto, m_nbyte, m_nmid : unsigned(15 downto 0);
signal meas_hd, meas_su, meas_buf : integer := 0;
signal halt : boolean := false;
begin
f_sda_low <= (not f_sda_bit) when f_sda_req = '1' else '0';
scl_drv <= tgt_scl_low & f_scl_low;
sda_drv <= tgt_sda_low & f_sda_low;
bus_m : entity work.i2c_line_model
generic map (N_DEV => 2)
port map (scl_drive_low => scl_drv, sda_drive_low => sda_drv,
scl => scl, sda => sda, scl_in => scl_in, sda_in => sda_in,
scl_released_but_low => scl_rbl, sda_released_but_low => sda_rbl,
scl_holders => scl_h, sda_holders => sda_h);
dut : entity work.i2c_framer
generic map (N_HD_STA => NHD, N_SU_STA => NSUA, N_SU_STO => NSUO,
N_BUF => NBUF, N_SU_DAT => NSUD, CNT_W => 16)
port map (clk => clk, rst_n => rst_n,
do_start => do_start, do_restart => do_restart, do_stop => do_stop,
scl_in => scl_in(0), sda_in => sda_in(0), scl_yield => '0',
sda_req => f_sda_req, sda_bit => f_sda_bit, scl_drive_low => f_scl_low,
busy => f_busy, done => f_done, bus_free => f_bus_free, started => f_started,
stretch_wait => f_stretch, starts => n_sta, restarts => n_rs, stops => n_sto,
state => fstate);
mon : entity work.i2c_proto_mon
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl => scl, sda => sda,
start_seen => m_start, stop_seen => m_stop, bit_seen => m_bit,
bit_val => m_bitv, byte_seen => m_byte, byte_val => m_byteval,
ack_seen => m_ack, ack_val => m_ackv, in_transfer => m_intr,
framing_midbyte => m_midbyte, bit_index => m_bidx,
n_starts => m_nsta, n_stops => m_nsto, n_bytes => m_nbyte,
n_midbyte => m_nmid);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for TCLK/2;
clk <= '1'; wait for TCLK/2;
end loop;
wait;
end process;
-- Local edge detection and interval measurement, with running counters as VARIABLES so
-- they behave like the SystemVerilog bench's blocking assignments.
meas : process (clk, rst_n)
variable hd_run, su_run, buf_run : integer := 0;
variable arm_hd, arm_buf : std_logic := '0';
variable scl_l, sda_l : std_logic := '1';
begin
if rst_n = '0' then
hd_run := 0; su_run := 0; buf_run := 0;
arm_hd := '0'; arm_buf := '0'; scl_l := '1'; sda_l := '1';
meas_hd <= 0; meas_su <= 0; meas_buf <= 0;
elsif falling_edge(clk) then
-- tHD;STA: from the START edge (SDA falls while SCL high) until SCL falls.
if sda_l = '1' and sda = '0' and scl = '1' then
arm_hd := '1'; hd_run := 1;
elsif arm_hd = '1' then
if scl = '1' then hd_run := hd_run + 1;
else meas_hd <= hd_run; arm_hd := '0';
end if;
end if;
-- tSU;STO: cycles with SCL high and SDA low, ending at the STOP edge.
if scl = '1' and sda = '0' then su_run := su_run + 1;
elsif scl = '0' then su_run := 0;
end if;
if sda_l = '0' and sda = '1' and scl = '1' then meas_su <= su_run; end if;
-- tBUF: from the STOP edge until bus_free is asserted.
if sda_l = '0' and sda = '1' and scl = '1' then
arm_buf := '1'; buf_run := 0;
elsif arm_buf = '1' then
buf_run := buf_run + 1;
if f_bus_free = '1' then meas_buf <= buf_run; arm_buf := '0'; end if;
end if;
scl_l := scl; sda_l := sda;
end if;
end process;
stim : process
variable err : integer := 0;
variable n, k, j : integer;
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;
procedure ck_bit (what : string; g : std_logic; e : std_logic) is
begin
if g /= e then
report " FAIL " & what & ": got " & std_logic'image(g)
& " expected " & std_logic'image(e) severity note;
err := err + 1;
end if;
end procedure;
procedure step is
begin
wait until rising_edge(clk); wait until falling_edge(clk);
end procedure;
procedure do_reset is
begin
wait until falling_edge(clk);
rst_n <= '0'; do_start <= '0'; do_restart <= '0'; do_stop <= '0';
tgt_scl_low <= '0'; tgt_sda_low <= '0';
for i in 0 to 2 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk); rst_n <= '1';
step;
end procedure;
procedure pulse_start is
begin
wait until falling_edge(clk); do_start <= '1';
wait until rising_edge(clk); wait until falling_edge(clk); do_start <= '0';
end procedure;
procedure pulse_restart is
begin
wait until falling_edge(clk); do_restart <= '1';
wait until rising_edge(clk); wait until falling_edge(clk); do_restart <= '0';
end procedure;
procedure pulse_stop is
begin
wait until falling_edge(clk); do_stop <= '1';
wait until rising_edge(clk); wait until falling_edge(clk); do_stop <= '0';
end procedure;
procedure wait_done (max_cycles : integer) is
begin
n := 0;
while f_done = '0' and n < max_cycles loop step; n := n + 1; end loop;
if n >= max_cycles then
report " FAIL wait_done: framer stuck in state "
& integer'image(to_integer(fstate)) severity note;
err := err + 1;
end if;
end procedure;
procedure wait_state (want : integer; max_cycles : integer) is
begin
n := 0;
while to_integer(fstate) /= want and n < max_cycles loop step; n := n + 1; end loop;
if n >= max_cycles then
report " FAIL wait_state: state " & integer'image(want) & " not reached"
severity note;
err := err + 1;
end if;
end procedure;
begin
report "=== i2c_framer: the edges a data bit is forbidden to make ===" severity note;
-- T1. Reset releases both lines, and the bus is assumed free.
do_reset;
report "T1 a reset framer releases both lines" severity note;
ck_bit("T1 SCL released", f_scl_low, '0');
ck_bit("T1 SDA released", f_sda_low, '0');
ck_bit("T1 both lines high", scl and sda, '1');
ck_bit("T1 the bus is free", f_bus_free, '1');
ck_bit("T1 not in a transfer", m_intr, '0');
ck_int("T1 no framing seen", to_integer(m_nsta) + to_integer(m_nsto), 0);
-- T2. A START, reported by a monitor that has seen only the wires.
pulse_start;
wait_done(200);
report "T2 the monitor sees a START on the wire, having seen only the wires"
severity note;
ck_int("T2 exactly one START", to_integer(m_nsta), 1);
ck_int("T2 no STOP", to_integer(m_nsto), 0);
ck_bit("T2 the transfer is open", m_intr, '1');
ck_bit("T2 the framer says it started", f_started, '1');
ck_bit("T2 SCL is now low, ready for the first bit", scl, '0');
ck_bit("T2 and SDA has been handed back", f_sda_req, '0');
-- T3. tHD;STA elapses between the START edge and SCL falling.
report "T3 tHD;STA elapses between the START edge and SCL falling" severity note;
ck_int("T3 the hold is N_HD_STA cycles", meas_hd, NHD);
-- T4. A STOP: SDA rises while SCL is high, after tSU;STO.
pulse_stop;
wait_done(200);
report "T4 a STOP: SDA rises while SCL is high, after tSU;STO" severity note;
ck_int("T4 exactly one STOP", to_integer(m_nsto), 1);
ck_bit("T4 the transfer is closed", m_intr, '0');
ck_bit("T4 the framer says so", f_started, '0');
ck_int("T4 tSU;STO before the edge", meas_su, NSUO);
ck_bit("T4 both lines released", f_scl_low or f_sda_low, '0');
-- T5. tBUF elapses after the STOP before the bus is free.
--
-- One step first: `meas_buf` is assigned by the measuring process at the very falling
-- edge on which bus_free is asserted -- which is the edge wait_done returned on -- and
-- a VHDL signal read in the same delta it is assigned still holds its old value.
step;
report "T5 tBUF elapses after the STOP before the bus is free again" severity note;
ck_int("T5 the bus-free delay is N_BUF cycles", meas_buf, NBUF);
ck_bit("T5 and then it is free", f_bus_free, '1');
-- T6. A START requested before tBUF has elapsed is REFUSED, not issued early.
-- Issuing it early is how two masters collide without ever arbitrating.
do_reset;
pulse_start; wait_done(200);
-- THE CROSS-CHECK. With a transfer open the bus is not free and the framer is
-- back in S_IDLE able to sample a command -- the only reachable state in which
-- bus_free is '0' and a command can be accepted, so the only place the guard can
-- be tested. Refusing is not observable on the WIRE: mid-transfer the framer
-- holds SCL low, so a wrongly-accepted START pulls SDA low while SCL is low, an
-- ordinary data change that produces no START. The invariant between the two
-- counters is what catches it.
ck_bit("T6 with a transfer open the bus is not free", f_bus_free, '0');
k := to_integer(m_nsta);
j := to_integer(n_sta);
wait until falling_edge(clk); do_start <= '1';
n := 0;
while n < 30 loop step; n := n + 1; end loop;
wait until falling_edge(clk); do_start <= '0';
ck_int("T6 a START with the bus not free is refused", to_integer(m_nsta), k);
ck_int("T6 and the framer did not count one either", to_integer(n_sta), j);
ck_int("T6 every START counted was seen on the wire",
to_integer(n_sta), to_integer(m_nsta));
pulse_stop;
wait_state(S_IDLE, 400);
k := to_integer(m_nsta);
wait until falling_edge(clk); do_start <= '1';
n := 0;
while f_bus_free = '0' and n < 200 loop step; n := n + 1; end loop;
report "T6 a START asked for before tBUF has elapsed is refused, not rushed"
severity note;
ck_int("T6 no extra START was issued during tBUF", to_integer(m_nsta), k);
wait_done(200);
wait until falling_edge(clk); do_start <= '0';
ck_int("T6 and it is honoured once the bus is free", to_integer(m_nsta), k + 1);
-- T7. A REPEATED START from mid-transfer, with SDA lifted first.
do_reset;
pulse_start; wait_done(200);
k := to_integer(m_nsta);
pulse_restart;
wait_done(400);
report "T7 a repeated START from mid-transfer, with SDA lifted first" severity note;
ck_int("T7 a second START was seen on the wire", to_integer(m_nsta), k + 1);
ck_int("T7 and no STOP", to_integer(m_nsto), 0);
ck_bit("T7 the transfer is still open", m_intr, '1');
ck_int("T7 counted as a repeated START", to_integer(n_rs), 1);
ck_int("T7 and not as an initial one", to_integer(n_sta), 1);
ck_bit("T7 SCL is low afterwards", scl, '0');
-- T8. THE STRETCH, on a repeated START. A framer that counted from its own release
-- would drop SDA while SCL was still low -- an ordinary data-bit change, not a
-- START.
do_reset;
pulse_start; wait_done(200);
wait until falling_edge(clk); tgt_scl_low <= '1';
k := to_integer(m_nsta);
pulse_restart;
wait_state(S_RS_SCLHI, 200);
report "T8 a repeated START waits for SCL to really rise before tSU;STA"
severity note;
ck_bit("T8 the framer has released SCL", f_scl_low, '0');
ck_bit("T8 the line is still low", scl, '0');
step;
ck_bit("T8 and it reports waiting", f_stretch, '1');
for i in 0 to 39 loop
step;
ck_int("T8 no START was emitted while SCL was held", to_integer(m_nsta), k);
end loop;
wait until falling_edge(clk); tgt_scl_low <= '0';
wait_done(400);
ck_int("T8 and the START appears once SCL rises", to_integer(m_nsta), k + 1);
-- T9. THE STRETCH, on a STOP. Same requirement: releasing SDA while SCL is low is
-- not a STOP.
do_reset;
pulse_start; wait_done(200);
wait until falling_edge(clk); tgt_scl_low <= '1';
k := to_integer(m_nsto);
pulse_stop;
wait_state(S_STO_SCLHI, 200);
report "T9 a STOP waits for SCL to really rise before tSU;STO" severity note;
ck_bit("T9 SCL released by the framer", f_scl_low, '0');
ck_bit("T9 the line is held low by the target", scl, '0');
for i in 0 to 39 loop
step;
ck_int("T9 no STOP was emitted while SCL was held", to_integer(m_nsto), k);
end loop;
wait until falling_edge(clk); tgt_scl_low <= '0';
wait_done(400);
ck_int("T9 and the STOP appears once SCL rises", to_integer(m_nsto), k + 1);
ck_bit("T9 the transfer is closed", m_intr, '0');
-- T10. Three sequences produce exactly three framing events and nothing else. Each of
-- the repeated START and the STOP does produce one clock pulse of its own, but
-- one pulse is what every conforming framing sequence produces, so the monitor's
-- mid-byte threshold is two.
do_reset;
pulse_start; wait_done(400);
pulse_restart; wait_done(400);
pulse_stop; wait_done(400);
report "T10 three sequences produce exactly three framing events" severity note;
ck_int("T10 two STARTs", to_integer(m_nsta), 2);
ck_int("T10 one STOP", to_integer(m_nsto), 1);
ck_int("T10 no bytes, because nothing was clocked", to_integer(m_nbyte), 0);
ck_int("T10 no mid-byte framing reports", to_integer(m_nmid), 0);
-- T11. Counters distinguish an initial START from a repeated one, which the WIRE
-- cannot: both are the same edge.
report "T11 the framer distinguishes an initial START from a repeated one"
severity note;
ck_int("T11 one initial START", to_integer(n_sta), 1);
ck_int("T11 one repeated START", to_integer(n_rs), 1);
ck_int("T11 one STOP", to_integer(n_sto), 1);
ck_int("T11 and the wire saw two STARTs, indistinguishably",
to_integer(m_nsta), 2);
-- T12. Idle means silent.
do_reset;
report "T12 an idle framer drives nothing at all" severity note;
for i in 0 to 59 loop
step;
ck_bit("T12 SCL untouched", f_scl_low, '0');
ck_bit("T12 SDA untouched", f_sda_req, '0');
end loop;
ck_int("T12 and nothing was seen on the wire",
to_integer(m_nsta) + to_integer(m_nsto), 0);
ck_int("T12 still idle", to_integer(fstate), S_IDLE);
if err = 0 then
report "=== i2c_framer: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_framer: " & integer'image(err)
& " CHECK(S) FAILED ===" severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;5b. Execution
| Design | SystemVerilog | Verilog-2001 | VHDL | Finish |
|---|---|---|---|---|
i2c_framer | PASS 12/12 | PASS 12/12 | PASS 12/12 | 3820 ns, all three |
6. Mutation Testing — and the Defect No Bus Check Could See
Nine defects, one per claim in this chapter.
| # | Injected defect | Expected detection | Result |
|---|---|---|---|
| M1 | STOP released while SCL is still low | T4, T9 | KILLED (4) |
| M2 | tHD;STA one cycle short | T3 | KILLED (2) |
| M3 | tSU;STO one cycle short | T4 | KILLED (2) |
| M4 | repeated START ignores the stretch | T8 | KILLED (4) |
| M5 | tBUF dropped entirely | T5, T6 | KILLED (2) |
| M6 | START edge polarity reversed | T2, T10 | KILLED (8) |
| M7 | the STOP edge drives SDA low instead of releasing | T4 | KILLED (3) |
| M8 | a START accepted when the bus is not free | T6 after strengthening | KILLED (4) |
| M9 | the repeated-START edge never falls | T7, T11 | KILLED (6) |
baseline: PASS (verified before injecting anything)
killed: 9 survived: 0 score: 9/9
restored: PASSM8 survived first, and the reason is worth the space
The mutation removes the bus_free guard so a START command is accepted whenever it arrives. It survived the original suite, and diagnosing why produced two findings.
First, the test was asking at the wrong moment. T6 originally did wait_state(S_IDLE, …) before asserting the request — but the framer enters S_IDLE from S_BUF in the same cycle it sets bus_free, so by the time the test asked, the bus was already free and the guard was irrelevant. The test's intent was right and its synchronisation defeated it.
The only reachable state in which bus_free is 0 and the framer can sample a command is S_IDLE mid-transfer, after a START, while the byte engine works. So that is the only place the guard can be tested at all.
Second — and this is the interesting part — refusing is not observable on the wire. Mid-transfer the framer holds SCL low. A wrongly accepted START therefore pulls SDA low while SCL is low, which is an ordinary data change. The monitor sees nothing. No bus-level check, however thorough, can detect this defect.
What catches it is an invariant between two counters:
every START the framer BELIEVES it issued must have been visible on the bus
ck_int("T6 every START counted was seen on the wire", n_sta, m_nsta);A framer that accepts a command it should have refused increments its own counter while the monitor sees nothing, and the two diverge. With that check added — ported to all three languages, which moved the finish time from 3500 ns to 3820 ns in all three — M8 dies with four failure lines.
7. Verification Connection — A Monitor That Distinguishes Framing From Data
// A framing monitor's entire job is a single classification, made on every SDA
// transition, and the rule is the contradiction of section 1 read backwards:
//
// SDA changes while SCL is LOW -> ordinary data preparation. Ignore.
// SDA falls while SCL is HIGH -> START or repeated START.
// SDA rises while SCL is HIGH -> STOP.
//
// WHICH of START and repeated START it is cannot be read from the edge: the two
// are the same transition with the same shape. It depends on whether a transfer
// is already open, which is MONITOR STATE, not a bus observation:
//
// no transfer open -> START
// transfer open -> repeated START
//
// So even this minimal monitor is a state machine, and Chapter 5.4 made the same
// point at the protocol level. A monitor written as a pure sampler cannot report
// the difference, and the difference is what a scoreboard needs to know whether
// the bus was released between two transfers.
//
// WHAT THIS MONITOR MUST NOT DO, and it is tempting: count framing events and
// compare them against the DUT's own counters as its only check. Section 6 shows
// why that comparison is necessary -- but it is a CROSS-check, an additional
// observation, not the monitor's primary duty. A monitor that reports "the DUT's
// count matches mine" has verified agreement, not correctness: both could be
// wrong in the same direction if the monitor took its timing from the DUT.
//
// COVERAGE the framer makes reachable, and which a transfer-level bench misses:
//
// cover: a repeated START whose SCL release was stretched (T8)
// cover: a STOP whose SCL release was stretched (T9)
// cover: a START requested during tBUF (T6)
//
// All three require another device to interfere at a precise moment. None occurs
// in a regression built only from clean transfers.8. FPGA and ASIC Implications
On an FPGA, the framer is the block most exposed to readback latency. It makes three decisions that depend on observing SCL high — the repeated START's setup, the STOP's setup, and leaving a stretch — and each sees the line two cycles late through the synchroniser. The direction is safe for the same reason as in 17.3: tSU;STA and tSU;STO have only minimums, so a late start to the interval makes it longer. But the hold parameter is different in character — tHD;STA is measured from an edge the framer produces itself, so no readback is involved and no latency applies. Knowing which intervals are anchored to observations and which to the master's own outputs tells you exactly which parameters synchroniser latency can and cannot affect.
On an ASIC, the same analysis applies with the pad's input filter added in series, and tSP — the 50 ns spike the filter must suppress — has a specific consequence here: a stretch shorter than the filter delay is invisible, so the framer will not wait for it. That is harmless, because a stretch shorter than tSP is also shorter than the rise time and the line was never meaningfully low. The framer's counters (starts, restarts, stops, stretch_wait) are cheap and belong in a status register: they are the only way firmware can distinguish "the bus is slow" from "the bus is stuck", and Chapter 17.11 uses them.
9. Debugging — The Repeated START That Was an Ordinary Data Bit
A master implements combined transactions to read a register-map sensor: write the pointer, repeated START, then read. It works against two sensors and fails against a third, which returns the wrong register every time -- consistently the register the pointer had been set to on the PREVIOUS transaction. All three sensors are from different vendors and all three datasheets describe the same combined-transfer sequence.
The framer measured a setup interval from its own drive intent instead of from the observed line, so on a board with a slower SCL rise the repeated START was emitted while SCL was still low. A transition on SDA while SCL is low is not framing -- it is ordinary data preparation, and every conforming device reads it as such. The master and the sensor then held incompatible beliefs about what phase the transfer was in, and because the sensor kept acknowledging the bytes it was receiving, nothing in the master's view of the bus looked wrong. The two working boards were not evidence of correctness; their rise times simply happened to beat the master's fixed delay.
Gate the setup interval on the readback: enter the tSU;STA state only when scl_in actually reads high, which is mutation M4 in reverse. Then note what class of bug this was -- not a wrong number but a wrong ANCHOR, an interval measured from intent rather than observation, and the same defect shape as the SCL generator's in Chapter 17.3. For the regression: test T8, which holds SCL low across the framer's release and asserts that the repeated START edge does not appear until the line has risen. A bench whose bus model has zero rise time cannot fail the original design, which is why the environment needs a target that can hold the line rather than merely a wire that settles instantly.Three generalisations.
A wrong anchor, not a wrong number. The interval length was correct; it was measured from the wrong event. This is the third appearance of that shape in the module — 17.3's generator, 17.4's arbitration gate, and now the framer — and in every case the fix is to anchor on the observation rather than the intent.
Acknowledges throughout are not evidence of agreement. The sensor acknowledged every byte, including the one the master intended as an address. Both devices were behaving correctly for the phase each believed it was in. An acknowledge confirms a byte was received, never that it was understood as the same kind of byte.
Two working boards were a coincidence of rise time. The defect was present on all three and latent on two. That is the same structure as 17.3 §10's 100 MHz qualification: a configuration in which the correct and incorrect designs are indistinguishable is not a passing test.
10. Common Misconceptions
"Framing is just another kind of bit." It is the one transition a data bit may never make. The bit engine guarantees SDA stability during SCL-high; framing requires the opposite, so they cannot be one block. §1.
"A START is an SDA falling edge." It is an SDA falling edge while SCL is high. With SCL low the identical edge is ordinary data preparation, and every device reads it that way. §1 and §9.
"A repeated START can just pull SDA low." Only if SDA is high first. Mid-transfer it may be a data zero, and a line already low cannot produce a falling edge — so the sequence must lift SDA before anything else. §2.
"tBUF applies to a transfer." It applies to the gap between two of them, and it is the only Table 10 parameter that constrains a master while it is doing nothing. §2.
"A setup interval can be counted from releasing the line." Not if a target may be stretching. tSU;STA is a setup time against the SDA edge, and it is meaningless if SCL never rose. §3 and §9.
"A short setup time is a marginal START." A START with SCL low is not a marginal START; it is not a START. The failure mode is categorical, not marginal. §3.
"If the master's counters say it issued a START, it issued one." Its counters record intent. Only a wire-level observer records the bus, and M8 is precisely the case where the two disagree. §6.
"A wire-level monitor catches everything." It cannot catch a defect whose only symptom is disagreement between intent and observation — mid-transfer, a wrongly accepted START produces no bus edge at all. §6.
"Acknowledges throughout mean both sides agree." Both sides were correct for the phase each believed it was in. An ACK confirms receipt, not shared interpretation. §9.
"It works on two boards, so the timing is right." Two boards whose rise time beat a fixed delay. The defect was present on all three. §9.
11. Reason It Through
Why is the framing sequencer's separateness a proof rather than a preference?
Because the bit engine's postcondition is "SDA stable while SCL high" and framing's precondition is "SDA moves while SCL high". A single block would have to guarantee and violate the same property on the same wire in the same phase. No coding style avoids that. §1.
A master releases SCL, waits a fixed 200 ns, then pulls SDA low. On which boards does this produce a repeated START?
Only those where SCL's rise completes inside 200 ns. Anywhere slower, SDA falls while SCL is still low and the bus sees ordinary data preparation. The design is correct for a subset of boards and wrong in general. §3 and §9.
Why must a repeated START lift SDA before doing anything else?
Because mid-transfer SDA may be low from a data zero, and a line that is already low cannot produce the HIGH-to-LOW transition a START is defined as. §2.
Which of tHD;STA, tSU;STA and tSU;STO are affected by synchroniser latency, and why not the others?
tSU;STA and tSU;STO are, because both are anchored on observing SCL high. tHD;STA is not, because it is measured from an edge the framer produces itself — no readback is involved. §8.
M8 removed the bus-free guard and no bus-level check could see it. Why, and what did?
Because mid-transfer the framer holds SCL low, so a wrongly accepted START pulls SDA low while SCL is low — an ordinary data change producing no framing edge. What caught it was comparing the framer's own START count against the monitor's: intent diverged from observation. §6.
A test intended to check that a START is refused before tBUF passed against a design with no such guard. What was wrong with the test?
It waited for the framer to reach S_IDLE first, and S_IDLE is entered from S_BUF in the same cycle bus_free is set. By the time the request was made the bus was already free, so the guard was never exercised. §6.
Why can a bench whose bus model has zero rise time never fail the §9 design?
Because the defect only appears when the line is still low after the master released it. A model in which release means instantly high makes drive intent and observation identical, which is exactly the distinction the defect violates. §9.
12. Understanding Check
13. Summary
The framing sequencer cannot be a mode of the bit engine, and that is a proof rather than a preference. The bit engine must keep SDA stable while SCL is high; framing requires SDA to move while SCL is high. Contradictory postconditions on one wire in one phase.
That contradiction is deliberate — framing is recognisable precisely because it is the one thing a data bit may never do.
Four sequences, each built from its Table 10 interval: START with tHD;STA, repeated START with tSU;STA then tHD;STA, STOP with tSU;STO, and tBUF between a STOP and the next START.
A repeated START must lift SDA first, because a line already low cannot produce a falling edge, and mid-transfer SDA may be a data zero.
tBUF is the only parameter that constrains an idle master. It governs the gap between transfers, not a transfer.
Every SCL release inside a framing sequence waits for the readback. A framer that counts a setup interval from its own release emits SDA falling while SCL is low — which is not a marginal START but an ordinary data change, read as such by every device.
Nine mutants, nine killed — but M8 survived the original suite twice over, and both reasons were findings.
A test can have the right intent and the wrong synchronisation. T6 waited for S_IDLE, which is entered in the same cycle bus_free is set, so the guard it meant to test was never exercised.
And a wire-level monitor has one blind spot. Mid-transfer, a wrongly accepted START produces no bus edge at all, so no bus-level check can see it. Only comparing the framer's own count against the monitor's — intent against observation — reveals the divergence.
The recurring defect shape in this module is a wrong anchor, not a wrong number. Three chapters, three intervals measured from intent instead of observation, and the same fix each time.
14. What Comes Next
The framer produces the edges that open and close a transfer. Between them, bytes have to move.
Chapter 17.6 builds exactly one bit — no more — using the two strobes 17.3 exposed and the ownership interface 17.4 defined. Its single obligation is the one the framer exists to violate: SDA stable through the whole of SCL-high.
It is also where transmitting a one stops being an abstraction. The bit engine releases the line and must not assume the line went high, because between its release and the pull-up there is a rise time, another device, and possibly a competitor.
Continue learning
Related tutorials
- Related topic
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.
- Related topic
The START Condition
START is SDA falling while SCL is high, it is generated only by the controller, and it makes the bus busy. Derive what every device must do in response, then build a detector in three languages and find out why its two guard terms and its reset value are all load-bearing.
- Related topic
Repeated START and Combined Transactions in RTL
A combined transfer is not a write, a STOP and a read — it is one transaction whose direction reverses without the bus ever going free, and the difference is what stops another master moving the pointer in between. Builds the two-phase sequencer, and shows why the case most often got wrong is a failure in phase one, where the bus is still held.
- 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.
