I²C · Module 18
Repeated START, Transaction State and Clock Stretching
The layer that knows where in a transaction the slave is. Separates protocol state from application state, starts a read inside the acknowledge slot, and builds the wait state — the target's only legitimate way to say not yet.
Every block built so far knows how to do one thing and has no idea when to do it. 18.6 can receive a byte but cannot know whether that byte is a register pointer or data. 18.7 can send a byte but cannot know whether a read has begun or ended. This chapter builds the layer that knows.
1. The State Split
§3.1.10 note 4 requires a device to reset its bus logic on a START. 18.9 §2 argued that this cannot mean all its state, because a device that cleared the register pointer on a START could not implement the combined transfer the specification itself describes.
So there are two kinds of state, and the boundary between them is a module boundary:
| cleared by a START | where it lives | |
|---|---|---|
| protocol state | yes — note 4 | here: the phase, the direction, the byte index |
| application state | no | 18.9: the pointer, the register contents |
2. A Repeated START Restarts the Framing, Not the Application
This is the property the whole module has been building towards, and it is now one test. The bench writes a register through a real bus, issues a repeated START, and asserts both halves:
before the restart pointer = 6, register 5 = 0x7C
after the restart phase gone, direction gone, byte index gone
pointer STILL 6, register 5 STILL 0x7C3. Starting a Read Inside the Acknowledge Slot
Here is a defect that a unit testbench cannot find, and the reason this chapter's bench is an integration bench.
On a read, the slave must have its first bit on SDA before the master's first rising edge after the address acknowledge. 18.7 places that bit on the first falling edge after it is started — and the fall that must carry it is the one terminating the ninth pulse.
tx_start at the END of the slot -> next fall is a bit time later
the master clocks a bit nobody drove
tx_start at the BEGINNING -> the transmitter is loaded and waiting
the terminating fall carries bit 7This is also why ack_active is an input rather than a one-cycle ack_done: this block needs the slot's leading edge, and reconstructing an edge from a level it already receives is cheaper than asking 18.5 for a new output.
4. Clock Stretching — The Only Way to Say "Not Yet"
§3.1.9: the slave may hold the SCL line LOW to force the master into a wait state. It is the only mechanism a target has for needing more time, and it has exactly two rules.
A stretch is a pull-down, never a drive-high
There is no drive-high of SCL anywhere in this design. The slave adds its own low to a line the master has already taken low, and when it is ready it releases — the master's next rising edge then happens on the master's own schedule.
scl_drive_low = stretching the only SCL output this block hasA stretch may only begin in the low phase
stretching has exactly one assignment that can set it, and its condition contains scl_fall:
if (ack_last_fall && stall_req) stretching <= 1;The master releases SCL and nothing happens, because the slave has not finished
10 cyclesWhere the stretch goes
At the end of the acknowledge slot, which is the one place in a byte where both directions have slack: the master has finished the ninth pulse and has not yet started the next byte. Stretching mid-byte would be legal and pointless — the slave already has a whole bit time of notice for every bit.
5. Three Things This Design Does Not Contain
A mutation pass on the first draft produced six survivors. Three were missing checks, and three were code defending against cases that cannot happen — so the fix was to delete the code, not to test it.
| removed | why it was unreachable |
|---|---|
stretching <= 0 on framing | a stretch holds SCL low; framing is an SDA edge qualified by SCL high. They cannot coexist. |
a !framing guard on the stretch logic | scl_fall means scl_q is already low this cycle, and a framing pulse requires it high. Mutually exclusive. |
!stretching in rx_enable | 18.6 shifts on scl_rise, and while stretching there is no rising edge to shift on. |
!read_done && data_index == 0 on tx_start | 18.5's slot is armed only by a byte the slave must answer, and a read phase has exactly one — the address. ack_begin occurs once by construction. |
Removing the fourth left read_done with no reader at all, so that register went too.
6. The Transaction Layer, in Three Languages
One clocked block, four combinational outputs, and a four-way priority: reset, framing, address match, then the per-byte bookkeeping.
// -----------------------------------------------------------------------------
// i2c_slave_txn.sv
// The transaction layer: which byte is this, whose turn is it, and may the master have
// the next bit yet?
//
// WHAT THIS BLOCK IS FOR. Every block built so far knows how to do one thing and has no
// idea when to do it. Chapter 18.6 can receive a byte but cannot know whether that byte
// is a register pointer or data. Chapter 18.7 can send a byte but cannot know whether a
// read has begun or ended. This block is the layer that knows WHERE IN A TRANSACTION the
// slave is, and it exists because that knowledge is genuinely separate from every
// datapath that consumes it.
//
// THE STATE SPLIT, which is the reason Chapter 18.9 has no framing ports:
//
// PROTOCOL STATE lives here cleared by a START -- §3.1.10 note 4
// the phase, the direction, the byte index
// APPLICATION STATE lives in 18.9 survives a START
// the register pointer, the register contents
//
// A repeated START therefore RESTARTS THE FRAMING WITHOUT DESTROYING THE APPLICATION
// STATE, and that is not a subtlety -- it is the only reason the combined transfer works.
// Everything this block holds is deliberately disposable.
//
// CLOCK STRETCHING IS THE TARGET'S ONLY WAY TO SAY "NOT YET". §3.1.9: "the slave may hold
// the SCL line LOW to force the master into a wait state". Two rules follow, and both are
// structural here rather than remembered:
//
// 1. A STRETCH IS ONLY EVER A PULL-DOWN. There is no drive-high anywhere in this file.
// The slave adds its own low to a line the master has already taken low; it never
// releases SCL to make it rise, because the master owns the rise.
//
// 2. A STRETCH MAY ONLY BEGIN IN THE LOW PHASE. `stretching` can be set at exactly one
// instant -- `scl_fall` -- so asserting a pull-down while SCL is high is not a bug
// this block can have. Doing it at the rising edge instead would shorten the master's
// HIGH time below tHIGH and corrupt the bit every other device on the bus is sampling.
//
// WHERE THE STRETCH GOES. At the end of the acknowledge slot, which is the one place in a
// byte where both directions have slack: the master has finished the ninth pulse and has
// not yet started the next byte. Stretching anywhere inside a byte would be legal but
// pointless -- the slave already has a whole bit time of notice.
// -----------------------------------------------------------------------------
module i2c_slave_txn #(
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
// From Chapter 18.2.
input logic scl_q,
input logic scl_fall,
// From Chapter 18.3.
input logic start_pulse,
input logic restart_pulse,
input logic stop_pulse,
// From Chapter 18.4.
input logic addr_done,
input logic match,
input logic dir_read,
// From Chapter 18.5 -- the acknowledge slot, as a level. Its END is the stretch point,
// so this block detects the fall itself rather than asking 18.5 for a new output.
input logic ack_active,
// From Chapter 18.6 and Chapter 18.7.
input logic rx_valid,
input logic byte_sent,
// From Chapter 18.8 -- the master's answer to a read byte.
input logic mack_valid,
input logic mack_ack,
// From the application: a level meaning "I need more time".
input logic stall_req,
// ---- what the datapaths need to know ------------------------------------
output logic in_phase, // we are the addressed device in the current phase
output logic dir_q, // latched direction for this phase
output logic rx_is_pointer, // this received byte is the register pointer -- 18.9
output logic rx_enable, // 18.6 may shift
output logic tx_start, // one cycle: begin a read -- 18.7
output logic tx_continue, // one cycle: the master acknowledged, send another
output logic [3:0] data_index, // data bytes completed in this phase, saturating
// ---- the wait state -----------------------------------------------------
output logic scl_drive_low, // pull SCL down. NEVER a drive-high.
output logic stretching,
output logic [CNT_W-1:0] n_phases,
output logic [CNT_W-1:0] n_restarts,
output logic [CNT_W-1:0] n_stretch
);
// The acknowledge slot's two edges. Both matter, and they mean different things.
//
// ack_begin the slot has just started. The slave is acknowledging, and it has a
// whole bit time before it must produce anything else. THIS is where a
// read is started -- see below.
// ack_end the slot is over. Used only to notice that a byte boundary passed.
logic ack_q;
wire ack_begin = ack_active && !ack_q;
wire ack_end = ack_q && !ack_active;
// The falling edge that TERMINATES the acknowledge slot. It is the master's ninth pulse
// ending, and it is the only falling edge available to the transmitter for its first
// bit -- so it is also the one instant at which a wait state may be engaged.
wire ack_last_fall = ack_active && scl_fall;
// NOTE what is not here: a `read_done` flag. It existed while `tx_start` was guarded by
// it, and removing that guard left it with no reader -- so it went too. A NACK ends a
// read because no `tx_continue` is issued, not because a flag says so.
// Any framing event ends the phase. A repeated START and a plain START are the same
// thing to this block: both mean an address byte is coming and everything below is
// stale. `restart_pulse` is taken separately only to count it.
wire framing = start_pulse || restart_pulse || stop_pulse;
assign rx_is_pointer = in_phase && !dir_q && (data_index == 4'd0);
// 18.6 must not shift during the acknowledge slot -- the ninth bit is not a data bit.
//
// AND THERE IS NO `!stretching` TERM, which an earlier draft had. 18.6 shifts on
// `scl_rise`, and while this block is stretching it is holding SCL LOW -- so there is no
// rising edge to shift on, and the term could never change an outcome. A mutation
// removing it survived a full pass, which is what a redundant term looks like from the
// outside: not a bug, but a line claiming to prevent something that cannot happen.
assign rx_enable = in_phase && !dir_q && !ack_active;
assign scl_drive_low = stretching;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ack_q <= 1'b0;
in_phase <= 1'b0;
dir_q <= 1'b0;
data_index <= 4'd0;
tx_start <= 1'b0;
tx_continue <= 1'b0;
stretching <= 1'b0;
n_phases <= {CNT_W{1'b0}};
n_restarts <= {CNT_W{1'b0}};
n_stretch <= {CNT_W{1'b0}};
end else begin
ack_q <= ack_active;
tx_start <= 1'b0;
tx_continue <= 1'b0;
// ---- the phase ----------------------------------------------------
if (framing) begin
// EVERYTHING HERE IS DISPOSABLE. This is §3.1.10 note 4's "reset its bus
// logic", and it is the whole of what a repeated START destroys.
in_phase <= 1'b0;
dir_q <= 1'b0;
data_index <= 4'd0;
// NO `stretching <= 0` HERE, and that is a deliberate removal rather than an
// omission. A stretch and a framing event CANNOT COEXIST:
//
// `stretching` is only ever set at `scl_fall`, and while it is set this block
// holds SCL low -- so `scl_q` is low for the whole of it.
// A START or a STOP is an SDA edge qualified by `scl_q` HIGH (Chapter 18.3).
//
// So no framing pulse can arrive while `stretching` is set, and a clear here
// would be code for a case that does not exist. The bench asserts the property
// over every cycle of every test rather than trusting the argument.
if (restart_pulse) n_restarts <= n_restarts + 1'b1;
end else if (addr_done && match) begin
in_phase <= 1'b1;
dir_q <= dir_read;
data_index <= 4'd0;
n_phases <= n_phases + 1'b1;
end else begin
// ---- the byte index --------------------------------------------
// Saturating at two, because the only distinction that matters is
// "first data byte" versus "not the first". Wrapping would make byte
// sixteen a pointer again, which is the kind of defect that ships.
if (in_phase && (rx_valid || byte_sent) && data_index != 4'd2)
data_index <= data_index + 4'd1;
// ---- the read handshake ----------------------------------------
if (in_phase && dir_q && mack_valid) begin
// A NACK issues nothing. That is the whole mechanism: the transmitter is
// only ever continued by an explicit acknowledgement, so silence ends the
// read. Chapter 18.8 §4 is why that matters.
if (mack_ack) tx_continue <= 1'b1;
end
end
// ---- starting a read ----------------------------------------------
// AT THE BEGINNING OF THE ADDRESS ACKNOWLEDGE SLOT, and the reason is worth
// stating because the obvious alternative is wrong.
//
// Chapter 18.7 loads its first bit on the FIRST FALLING EDGE AFTER being started,
// and the fall that must carry that bit is the one terminating the ninth pulse.
// So the transmitter has to be started BEFORE that fall -- which means during the
// acknowledge slot, not at its end. Starting at `ack_end` leaves the transmitter
// one bit late, and the master clocks a bit the slave never drove.
//
// Not at `addr_done` either: the ninth bit is still 18.5's acknowledge of the
// address, and this only says "be ready", it does not drive anything.
//
// THREE CONDITIONS, NOT FIVE. An earlier draft also required `data_index == 0` and
// a not-yet-ended read, and both were unreachable: 18.5's slot is armed only by a
// byte THE SLAVE must answer, and in a read phase there is exactly one of those --
// the address. So `ack_begin` occurs once per read phase by construction, and the
// extra guards were defending against a second occurrence that cannot exist. Two
// mutants proved it by surviving.
if (ack_begin && in_phase && dir_q)
tx_start <= 1'b1;
// ---- the wait state -----------------------------------------------
// ENGAGE ONLY ON THE FALL THAT ENDS THE ACKNOWLEDGE SLOT. This is the one
// assignment that can set `stretching`, and its condition contains `scl_fall` --
// which is why "never pull SCL down while it is high" is a property of the
// structure rather than of the author's memory.
//
// The instant is also the right one on protocol grounds: the master has finished
// the ninth pulse and has not begun the next byte, so this is the one byte
// boundary where a wait costs nothing and interrupts nothing.
//
// There is no `!framing` guard, for the reason given above: `scl_fall` means
// `scl_q` is already low this cycle, and a framing pulse requires it high. The two
// conditions are mutually exclusive, so a guard would be unreachable.
if (ack_last_fall && stall_req) begin
stretching <= 1'b1;
n_stretch <= n_stretch + 1'b1;
end
// RELEASE WHEN THE APPLICATION IS READY. The master's next rising edge then
// happens on its own schedule -- the slave does not produce it, it only stops
// preventing it.
if (stretching && !stall_req) stretching <= 1'b0;
end
end
endmodule // -----------------------------------------------------------------------------
// i2c_slave_txn.v
// The transaction layer: which byte is this, whose turn is it, and may the master have
// the next bit yet?
//
// WHAT THIS BLOCK IS FOR. Every block built so far knows how to do one thing and has no
// idea when to do it. Chapter 18.6 can receive a byte but cannot know whether that byte
// is a register pointer or data. Chapter 18.7 can send a byte but cannot know whether a
// read has begun or ended. This block is the layer that knows WHERE IN A TRANSACTION the
// slave is, and it exists because that knowledge is genuinely separate from every
// datapath that consumes it.
//
// THE STATE SPLIT, which is the reason Chapter 18.9 has no framing ports:
//
// PROTOCOL STATE lives here cleared by a START -- §3.1.10 note 4
// the phase, the direction, the byte index
// APPLICATION STATE lives in 18.9 survives a START
// the register pointer, the register contents
//
// A repeated START therefore RESTARTS THE FRAMING WITHOUT DESTROYING THE APPLICATION
// STATE, and that is not a subtlety -- it is the only reason the combined transfer works.
// Everything this block holds is deliberately disposable.
//
// CLOCK STRETCHING IS THE TARGET'S ONLY WAY TO SAY "NOT YET". §3.1.9: "the slave may hold
// the SCL line LOW to force the master into a wait state". Two rules follow, and both are
// structural here rather than remembered:
//
// 1. A STRETCH IS ONLY EVER A PULL-DOWN. There is no drive-high anywhere in this file.
// The slave adds its own low to a line the master has already taken low; it never
// releases SCL to make it rise, because the master owns the rise.
//
// 2. A STRETCH MAY ONLY BEGIN IN THE LOW PHASE. `stretching` can be set at exactly one
// instant -- `scl_fall` -- so asserting a pull-down while SCL is high is not a bug
// this block can have. Doing it at the rising edge instead would shorten the master's
// HIGH time below tHIGH and corrupt the bit every other device on the bus is sampling.
//
// WHERE THE STRETCH GOES. At the end of the acknowledge slot, which is the one place in a
// byte where both directions have slack: the master has finished the ninth pulse and has
// not yet started the next byte. Stretching anywhere inside a byte would be legal but
// pointless -- the slave already has a whole bit time of notice.
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
// -----------------------------------------------------------------------------
module i2c_slave_txn #(
parameter integer CNT_W = 16
) (
input wire clk,
input wire rst_n,
// From Chapter 18.2.
input wire scl_q,
input wire scl_fall,
// From Chapter 18.3.
input wire start_pulse,
input wire restart_pulse,
input wire stop_pulse,
// From Chapter 18.4.
input wire addr_done,
input wire match,
input wire dir_read,
// From Chapter 18.5 -- the acknowledge slot, as a level. Its END is the stretch point,
// so this block detects the fall itself rather than asking 18.5 for a new output.
input wire ack_active,
// From Chapter 18.6 and Chapter 18.7.
input wire rx_valid,
input wire byte_sent,
// From Chapter 18.8 -- the master's answer to a read byte.
input wire mack_valid,
input wire mack_ack,
// From the application: a level meaning "I need more time".
input wire stall_req,
// ---- what the datapaths need to know ------------------------------------
output reg in_phase, // we are the addressed device in the current phase
output reg dir_q, // latched direction for this phase
output wire rx_is_pointer, // this received byte is the register pointer -- 18.9
output wire rx_enable, // 18.6 may shift
output reg tx_start, // one cycle: begin a read -- 18.7
output reg tx_continue, // one cycle: the master acknowledged, send another
output reg [3:0] data_index, // data bytes completed in this phase, saturating
// ---- the wait state -----------------------------------------------------
output wire scl_drive_low, // pull SCL down. NEVER a drive-high.
output reg stretching,
output reg [CNT_W-1:0] n_phases,
output reg [CNT_W-1:0] n_restarts,
output reg [CNT_W-1:0] n_stretch
);
// The acknowledge slot's two edges. Both matter, and they mean different things.
//
// ack_begin the slot has just started. The slave is acknowledging, and it has a
// whole bit time before it must produce anything else. THIS is where a
// read is started -- see below.
// ack_end the slot is over. Used only to notice that a byte boundary passed.
reg ack_q;
wire ack_begin = ack_active && !ack_q;
wire ack_end = ack_q && !ack_active;
// The falling edge that TERMINATES the acknowledge slot. It is the master's ninth pulse
// ending, and it is the only falling edge available to the transmitter for its first
// bit -- so it is also the one instant at which a wait state may be engaged.
wire ack_last_fall = ack_active && scl_fall;
// NOTE what is not here: a `read_done` flag. It existed while `tx_start` was guarded by
// it, and removing that guard left it with no reader -- so it went too. A NACK ends a
// read because no `tx_continue` is issued, not because a flag says so.
// Any framing event ends the phase. A repeated START and a plain START are the same
// thing to this block: both mean an address byte is coming and everything below is
// stale. `restart_pulse` is taken separately only to count it.
wire framing = start_pulse || restart_pulse || stop_pulse;
assign rx_is_pointer = in_phase && !dir_q && (data_index == 4'd0);
// 18.6 must not shift during the acknowledge slot -- the ninth bit is not a data bit.
//
// AND THERE IS NO `!stretching` TERM, which an earlier draft had. 18.6 shifts on
// `scl_rise`, and while this block is stretching it is holding SCL LOW -- so there is no
// rising edge to shift on, and the term could never change an outcome. A mutation
// removing it survived a full pass, which is what a redundant term looks like from the
// outside: not a bug, but a line claiming to prevent something that cannot happen.
assign rx_enable = in_phase && !dir_q && !ack_active;
assign scl_drive_low = stretching;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
ack_q <= 1'b0;
in_phase <= 1'b0;
dir_q <= 1'b0;
data_index <= 4'd0;
tx_start <= 1'b0;
tx_continue <= 1'b0;
stretching <= 1'b0;
n_phases <= {CNT_W{1'b0}};
n_restarts <= {CNT_W{1'b0}};
n_stretch <= {CNT_W{1'b0}};
end else begin
ack_q <= ack_active;
tx_start <= 1'b0;
tx_continue <= 1'b0;
// ---- the phase ----------------------------------------------------
if (framing) begin
// EVERYTHING HERE IS DISPOSABLE. This is §3.1.10 note 4's "reset its bus
// logic", and it is the whole of what a repeated START destroys.
in_phase <= 1'b0;
dir_q <= 1'b0;
data_index <= 4'd0;
// NO `stretching <= 0` HERE, and that is a deliberate removal rather than an
// omission. A stretch and a framing event CANNOT COEXIST:
//
// `stretching` is only ever set at `scl_fall`, and while it is set this block
// holds SCL low -- so `scl_q` is low for the whole of it.
// A START or a STOP is an SDA edge qualified by `scl_q` HIGH (Chapter 18.3).
//
// So no framing pulse can arrive while `stretching` is set, and a clear here
// would be code for a case that does not exist. The bench asserts the property
// over every cycle of every test rather than trusting the argument.
if (restart_pulse) n_restarts <= n_restarts + 1'b1;
end else if (addr_done && match) begin
in_phase <= 1'b1;
dir_q <= dir_read;
data_index <= 4'd0;
n_phases <= n_phases + 1'b1;
end else begin
// ---- the byte index --------------------------------------------
// Saturating at two, because the only distinction that matters is
// "first data byte" versus "not the first". Wrapping would make byte
// sixteen a pointer again, which is the kind of defect that ships.
if (in_phase && (rx_valid || byte_sent) && data_index != 4'd2)
data_index <= data_index + 4'd1;
// ---- the read handshake ----------------------------------------
if (in_phase && dir_q && mack_valid) begin
// A NACK issues nothing. That is the whole mechanism: the transmitter is
// only ever continued by an explicit acknowledgement, so silence ends the
// read. Chapter 18.8 §4 is why that matters.
if (mack_ack) tx_continue <= 1'b1;
end
end
// ---- starting a read ----------------------------------------------
// AT THE BEGINNING OF THE ADDRESS ACKNOWLEDGE SLOT, and the reason is worth
// stating because the obvious alternative is wrong.
//
// Chapter 18.7 loads its first bit on the FIRST FALLING EDGE AFTER being started,
// and the fall that must carry that bit is the one terminating the ninth pulse.
// So the transmitter has to be started BEFORE that fall -- which means during the
// acknowledge slot, not at its end. Starting at `ack_end` leaves the transmitter
// one bit late, and the master clocks a bit the slave never drove.
//
// Not at `addr_done` either: the ninth bit is still 18.5's acknowledge of the
// address, and this only says "be ready", it does not drive anything.
//
// THREE CONDITIONS, NOT FIVE. An earlier draft also required `data_index == 0` and
// a not-yet-ended read, and both were unreachable: 18.5's slot is armed only by a
// byte THE SLAVE must answer, and in a read phase there is exactly one of those --
// the address. So `ack_begin` occurs once per read phase by construction, and the
// extra guards were defending against a second occurrence that cannot exist. Two
// mutants proved it by surviving.
if (ack_begin && in_phase && dir_q)
tx_start <= 1'b1;
// ---- the wait state -----------------------------------------------
// ENGAGE ONLY ON THE FALL THAT ENDS THE ACKNOWLEDGE SLOT. This is the one
// assignment that can set `stretching`, and its condition contains `scl_fall` --
// which is why "never pull SCL down while it is high" is a property of the
// structure rather than of the author's memory.
//
// The instant is also the right one on protocol grounds: the master has finished
// the ninth pulse and has not begun the next byte, so this is the one byte
// boundary where a wait costs nothing and interrupts nothing.
//
// There is no `!framing` guard, for the reason given above: `scl_fall` means
// `scl_q` is already low this cycle, and a framing pulse requires it high. The two
// conditions are mutually exclusive, so a guard would be unreachable.
if (ack_last_fall && stall_req) begin
stretching <= 1'b1;
n_stretch <= n_stretch + 1'b1;
end
// RELEASE WHEN THE APPLICATION IS READY. The master's next rising edge then
// happens on its own schedule -- the slave does not produce it, it only stops
// preventing it.
if (stretching && !stall_req) stretching <= 1'b0;
end
end
endmodule -- -----------------------------------------------------------------------------
-- i2c_slave_txn.vhd
-- The transaction layer -- the same design in VHDL.
--
-- The state split is the whole point, and it is unchanged across the three languages:
--
-- PROTOCOL STATE lives here cleared by a START -- §3.1.10 note 4
-- APPLICATION STATE lives in 18.9 survives a START
--
-- A repeated START therefore restarts the framing without destroying the application
-- state, which is the only reason the combined transfer works.
--
-- Clock stretching is a PULL-DOWN ONLY, and it may only begin on a falling edge. There is
-- no drive-high of SCL anywhere in this file, and `stretching` has exactly one assignment
-- that can set it -- so both rules are structural rather than remembered.
-- -----------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_slave_txn is
generic (
CNT_W : positive := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
-- from 18.2
scl_q : in std_logic;
scl_fall : in std_logic;
-- from 18.3
start_pulse : in std_logic;
restart_pulse : in std_logic;
stop_pulse : in std_logic;
-- from 18.4
addr_done : in std_logic;
match : in std_logic;
dir_read : in std_logic;
-- from 18.5 -- the acknowledge slot as a level; this block finds its edges
ack_active : in std_logic;
-- from 18.6 and 18.7
rx_valid : in std_logic;
byte_sent : in std_logic;
-- from 18.8
mack_valid : in std_logic;
mack_ack : in std_logic;
-- from the application
stall_req : in std_logic;
in_phase : out std_logic;
dir_q : out std_logic;
rx_is_pointer : out std_logic;
rx_enable : out std_logic;
tx_start : out std_logic;
tx_continue : out std_logic;
data_index : out unsigned(3 downto 0);
scl_drive_low : out std_logic;
stretching : out std_logic;
n_phases : out unsigned(CNT_W-1 downto 0);
n_restarts : out unsigned(CNT_W-1 downto 0);
n_stretch : out unsigned(CNT_W-1 downto 0)
);
end entity i2c_slave_txn;
architecture rtl of i2c_slave_txn is
-- `out` ports are not readable in VHDL-93, so the state lives in internal signals.
signal ack_q : std_logic := '0';
signal phase_r : std_logic := '0';
signal dir_r : std_logic := '0';
signal idx_r : unsigned(3 downto 0) := (others => '0');
signal txs_r : std_logic := '0';
signal txc_r : std_logic := '0';
signal str_r : std_logic := '0';
signal nph : unsigned(CNT_W-1 downto 0) := (others => '0');
signal nrs : unsigned(CNT_W-1 downto 0) := (others => '0');
signal nst : unsigned(CNT_W-1 downto 0) := (others => '0');
signal ack_begin : std_logic;
signal ack_end : std_logic;
signal ack_last_fall : std_logic;
signal framing : std_logic;
begin
ack_begin <= ack_active and not ack_q;
ack_end <= ack_q and not ack_active;
ack_last_fall <= ack_active and scl_fall;
framing <= start_pulse or restart_pulse or stop_pulse;
in_phase <= phase_r;
dir_q <= dir_r;
data_index <= idx_r;
tx_start <= txs_r;
tx_continue <= txc_r;
stretching <= str_r;
n_phases <= nph;
n_restarts <= nrs;
n_stretch <= nst;
rx_is_pointer <= '1' when (phase_r = '1' and dir_r = '0' and idx_r = 0) else '0';
-- No `str_r = '0'` term: 18.6 shifts on scl_rise, and while this block stretches it holds
-- SCL LOW, so there is no rising edge to shift on. A mutation removing the term survived
-- a full pass, which is what a redundant condition looks like from the outside.
rx_enable <= '1' when (phase_r = '1' and dir_r = '0' and
ack_active = '0') else '0';
scl_drive_low <= str_r;
process (clk, rst_n)
begin
if rst_n = '0' then
ack_q <= '0';
phase_r <= '0';
dir_r <= '0';
idx_r <= (others => '0');
txs_r <= '0';
txc_r <= '0';
str_r <= '0';
nph <= (others => '0');
nrs <= (others => '0');
nst <= (others => '0');
elsif rising_edge(clk) then
ack_q <= ack_active;
txs_r <= '0';
txc_r <= '0';
-- the phase
if framing = '1' then
-- Everything here is disposable: this is note 4's "reset its bus logic".
phase_r <= '0';
dir_r <= '0';
idx_r <= (others => '0');
-- NO `str_r <= '0'` HERE, and that is a deliberate removal. A stretch and a
-- framing event cannot coexist: `str_r` is only ever set at `scl_fall` and holds
-- SCL low for the whole of its life, while a START or a STOP is an SDA edge
-- qualified by `scl_q` HIGH. A clear here would be code for a case that does not
-- exist, and the bench asserts the impossibility on every cycle instead.
if restart_pulse = '1' then nrs <= nrs + 1; end if;
elsif addr_done = '1' and match = '1' then
phase_r <= '1';
dir_r <= dir_read;
idx_r <= (others => '0');
nph <= nph + 1;
else
-- The byte index, saturating at two: the only distinction that matters is
-- "first data byte" versus "not the first".
if phase_r = '1' and (rx_valid = '1' or byte_sent = '1') and idx_r /= 2 then
idx_r <= idx_r + 1;
end if;
-- A NACK issues nothing. That is the whole mechanism: the transmitter is only
-- ever continued by an explicit acknowledgement, so silence ends the read.
if phase_r = '1' and dir_r = '1' and mack_valid = '1'
and mack_ack = '1' then
txc_r <= '1';
end if;
end if;
-- Starting a read AT THE BEGINNING of the address acknowledge slot: 18.7 loads on
-- the first falling edge after being started, and the fall that must carry the
-- first bit is the one terminating the ninth pulse.
--
-- Three conditions, not five: 18.5's slot is armed only by a byte the slave must
-- answer, and a read phase has exactly one of those -- the address. So `ack_begin`
-- occurs once per read phase by construction, and the guards an earlier draft had
-- were defending against a second occurrence that cannot exist.
if ack_begin = '1' and phase_r = '1' and dir_r = '1' then
txs_r <= '1';
end if;
-- the wait state. The one assignment that can set `stretching`, and its condition
-- contains scl_fall -- so a pull-down while SCL is high is not a bug this block can
-- have. There is no `framing = '0'` guard for the reason above: `scl_fall` means
-- `scl_q` is already low, and a framing pulse requires it high.
if ack_last_fall = '1' and stall_req = '1' then
str_r <= '1';
nst <= nst + 1;
end if;
if str_r = '1' and stall_req = '0' then
str_r <= '0';
end if;
end if;
end process;
end architecture rtl;7. The Testbench Is an Integration Testbench
Every output of this block is an instruction to another block, and an instruction with no recipient cannot be checked. "An output with no consumer instantiated" is the shape that let two mutants survive in 18.7, so this bench instantiates the whole slave — 18.2 through 18.9 — and drives it from a master model across 17.1's wired-AND bus.
Fifteen tests:
| test | what it establishes |
|---|---|
| T1 | a target held in reset does not hold the bus down — and a reset target is in no phase |
| T2 | our address with the write bit opens a write phase |
| T3 | the first data byte is the pointer and the second is not |
| T4 | another device's address opens no phase and enables no shifting |
| T5 | rx_enable drops inside the acknowledge slot |
| T6 | a repeated START clears the phase and keeps the pointer |
| T7 | a read is started inside the address acknowledge slot, so the first bit is on time |
| T8 | an acknowledged read byte is followed by another; a NACK ends it |
| T9 | the application asks for time and the master blocks until it is given |
| T10 | across every test, SCL was never pulled down while it was high |
| T11 | a stretch and a framing event never coexist |
| T12 | the byte index saturates: a later byte never becomes a pointer again |
| T13 | a STOP ends the phase |
| T14 | reset clears the phase, the index and every counter |
| T15 | a NACKed read ends even against a master that keeps clocking |
Three of those need their reasoning stated.
T9's stall is a countdown, not a level. A test that raises stall_req and lowers it by hand can only ever stretch while nothing is happening, so the master never has to wait:
raise a level, do nothing, lower it the stretch is invisible to the master
raise a countdown, then clock the master BLOCKS inside its own bit taskThe second form is the only one that makes the wait an observable fact about the bus rather than a flag inside the DUT. T9 asserts stretch_waits == 1 — the master waited exactly once, at exactly the point the slave asked it to.
T15 is 18.8 §4 at the transaction level. After a NACK the master keeps clocking eight more bits, and the slave must drive nothing. The register it would have sourced holds 0xFF, so every bit of an unwanted byte would be a pull-down — which is exactly what prevents the master's STOP. The test then issues the STOP and asserts it happened.
T1 checks the bus while reset is asserted, not after it. A mutant whose reset value was stretching cleared itself on the first clock after release, so every check taken after reset passed — and the defect, a slave that pulls SCL low for the whole of its reset, was invisible.
// -----------------------------------------------------------------------------
// i2c_slave_txn_tb.sv
// Independent oracle for i2c_slave_txn -- and the first bench in this module that is an
// INTEGRATION bench rather than a unit bench.
//
// WHY IT HAS TO BE. Every output of this block is an instruction to another block: 18.6
// may shift, 18.7 may start, 18.9 may treat this byte as a pointer. An instruction with no
// recipient cannot be checked, and "an output with no consumer instantiated" is the shape
// that let two mutants survive earlier in this module. So the bench builds the real stack
// -- 18.2, 18.3, 18.4, 18.5, 18.6, 18.7, 18.8, 18.9 -- and drives it from a master model
// across the wired-AND bus of Chapter 17.1.
//
// AND THE MASTER MODEL MUST OBEY A STRETCH. A master that clocks on a fixed schedule cannot
// test clock stretching at all: it would simply drive SCL high while the slave held it low,
// which is electrically impossible and would make the stretch invisible. So this master
// RELEASES SCL and then waits for the line to actually rise, counting how many times it had
// to wait. That count is the only direct evidence a stretch had an effect on anybody.
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module i2c_slave_txn_tb;
localparam int HALF = 8; // clocks per SCL half-phase
localparam [6:0] ADDR = 7'h50;
localparam int N_REG = 8;
localparam int RO_MASK = 8'h04;
logic clk = 1'b0;
logic rst_n = 1'b0;
// ---- the bus: device 0 is the master model, device 1 is the slave --------
logic [1:0] scl_dl, sda_dl;
logic scl, sda;
logic [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
logic [7:0] scl_holders, sda_holders;
logic m_scl_low = 1'b0, m_sda_low = 1'b0;
i2c_line_model #(.N_DEV(2)) bus (
.scl_drive_low(scl_dl), .sda_drive_low(sda_dl),
.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_holders), .sda_holders(sda_holders)
);
// ---- the slave stack ----------------------------------------------------
logic scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall;
i2c_slave_sync #(.SYNC_DEPTH(2)) u_sync (
.clk(clk), .rst_n(rst_n), .scl_pin(scl), .sda_pin(sda),
.scl_q(scl_q), .sda_q(sda_q), .scl_rise(scl_rise), .scl_fall(scl_fall),
.sda_rise(sda_rise), .sda_fall(sda_fall)
);
logic start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte;
logic [15:0] n_sta, n_rs, n_sto;
logic acquiring, addr_done, match, selected, dir_read;
logic [3:0] a_bit_index;
logic [7:0] addr_byte;
logic [15:0] n_match, n_miss;
logic receiving, rx_valid, rx_byte_done;
logic [3:0] r_bit_index;
logic [7:0] rx_byte;
logic [15:0] n_bytes_rx, n_partial;
wire mid_byte = acquiring || receiving;
i2c_slave_framing #(.CNT_W(16)) u_frm (
.clk(clk), .rst_n(rst_n), .scl_q(scl_q), .sda_rise(sda_rise), .sda_fall(sda_fall),
.start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
.bus_active(bus_active), .mid_byte(mid_byte), .framing_midbyte(framing_midbyte),
.n_starts(n_sta), .n_restarts(n_rs), .n_stops(n_sto)
);
i2c_slave_addr #(.MY_ADDR(ADDR), .CNT_W(16)) u_addr (
.clk(clk), .rst_n(rst_n), .scl_rise(scl_rise), .sda_q(sda_q),
.start_pulse(start_pulse), .stop_pulse(stop_pulse),
.acquiring(acquiring), .bit_index(a_bit_index), .addr_done(addr_done),
.addr_byte(addr_byte), .match(match), .selected(selected), .dir_read(dir_read),
.n_match(n_match), .n_miss(n_miss)
);
// ---- the DUT ------------------------------------------------------------
logic in_phase, dir_q, rx_is_pointer, rx_enable, tx_start, tx_continue;
logic [3:0] data_index;
logic txn_scl_low, stretching;
logic [15:0] n_phases, n_restarts_txn, n_stretch;
// THE APPLICATION'S STALL, AS A COUNTDOWN. A test that raises a level and lowers it by
// hand can only ever stretch while nothing is happening -- so the master never has to
// wait, and the stretch is invisible to it. A countdown that expires on its own lets the
// master BLOCK inside its own bit task, which is the only way the wait becomes an
// observable fact about the bus rather than a flag inside the DUT.
localparam int STALL_N = 40;
logic stall_load = 1'b0;
int stall_hold = 0;
wire stall_req = (stall_hold > 0);
always @(posedge clk) begin
if (!rst_n) stall_hold <= 0;
else if (stall_load) stall_hold <= STALL_N;
else if (stall_hold > 0) stall_hold <= stall_hold - 1;
end
logic ack_active, ack_armed, ack_sda_low;
logic [15:0] n_acks, n_nacks;
logic tx_req, tx_sda_low, driving, byte_sent;
logic [3:0] t_bit_index;
logic [15:0] n_bytes_tx, n_bits_tx;
logic [7:0] rd_data;
logic awaiting, mack_valid, mack_ack, keep_sourcing;
logic [15:0] n_m_ack, n_m_nack;
logic wr_accept;
logic [8*N_REG-1:0] reg_flat;
logic [7:0] pointer;
logic [15:0] n_writes, n_refused, n_reads;
i2c_slave_txn #(.CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n), .scl_q(scl_q), .scl_fall(scl_fall),
.start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
.addr_done(addr_done), .match(match), .dir_read(dir_read),
.ack_active(ack_active), .rx_valid(rx_valid), .byte_sent(byte_sent),
.mack_valid(mack_valid), .mack_ack(mack_ack), .stall_req(stall_req),
.in_phase(in_phase), .dir_q(dir_q), .rx_is_pointer(rx_is_pointer),
.rx_enable(rx_enable), .tx_start(tx_start), .tx_continue(tx_continue),
.data_index(data_index),
.scl_drive_low(txn_scl_low), .stretching(stretching),
.n_phases(n_phases), .n_restarts(n_restarts_txn), .n_stretch(n_stretch)
);
// The acknowledge slot: armed by any completed byte we owe an answer for. The address
// byte is always acknowledged when it matched; a data byte is acknowledged only if the
// register file will take it, which is 18.9's wr_accept arriving as an ack policy.
wire byte_done_any = rx_byte_done || (addr_done && match);
wire ack_en = (addr_done && match) ? 1'b1 : wr_accept;
i2c_slave_ack #(.CNT_W(16)) u_ack (
.clk(clk), .rst_n(rst_n), .scl_fall(scl_fall), .byte_done(byte_done_any),
.ack_en(ack_en), .start_pulse(start_pulse), .stop_pulse(stop_pulse),
.sda_drive_low(ack_sda_low), .ack_active(ack_active), .ack_armed(ack_armed),
.n_acks(n_acks), .n_nacks(n_nacks)
);
i2c_slave_rx #(.CNT_W(16)) u_rx (
.clk(clk), .rst_n(rst_n), .scl_rise(scl_rise), .sda_q(sda_q),
.start_pulse(start_pulse), .stop_pulse(stop_pulse), .rx_enable(rx_enable),
.receiving(receiving), .bit_index(r_bit_index), .rx_byte(rx_byte),
.rx_valid(rx_valid), .byte_done(rx_byte_done),
.n_bytes(n_bytes_rx), .n_partial(n_partial)
);
// ---- the transmit side, so tx_start and tx_continue have a consumer -----
i2c_slave_tx #(.CNT_W(16)) u_tx (
.clk(clk), .rst_n(rst_n), .scl_fall(scl_fall),
.start_pulse(start_pulse), .stop_pulse(stop_pulse),
.tx_start(tx_start), .tx_continue(tx_continue),
.tx_req(tx_req), .tx_byte(rd_data), .sda_drive_low(tx_sda_low),
.driving(driving), .bit_index(t_bit_index), .byte_sent(byte_sent),
.n_bytes(n_bytes_tx), .n_bits(n_bits_tx)
);
i2c_slave_mack #(.CNT_W(16)) u_mack (
.clk(clk), .rst_n(rst_n), .scl_rise(scl_rise), .sda_q(sda_q),
.start_pulse(start_pulse), .stop_pulse(stop_pulse), .byte_sent(byte_sent),
.awaiting(awaiting), .mack_valid(mack_valid), .mack_ack(mack_ack),
.keep_sourcing(keep_sourcing), .n_ack(n_m_ack), .n_nack(n_m_nack)
);
// ---- the register file, whose pointer must survive a repeated START ----
i2c_slave_regs #(.N_REG(N_REG), .RO_MASK(RO_MASK), .CNT_W(16)) u_regs (
.clk(clk), .rst_n(rst_n),
.rx_valid(rx_valid), .rx_byte(rx_byte), .rx_is_pointer(rx_is_pointer),
.wr_accept(wr_accept), .rd_data(rd_data), .rd_taken(tx_req),
.reg_flat(reg_flat), .pointer(pointer),
.n_writes(n_writes), .n_refused(n_refused), .n_reads(n_reads)
);
assign scl_dl = {txn_scl_low, m_scl_low};
assign sda_dl = {ack_sda_low | tx_sda_low, m_sda_low};
always #5 clk = ~clk;
int errors = 0;
// `k` belongs to the bus tasks -- m_byte drives it -- so a test that loops must use its
// own variable. Sharing one cost an afternoon: the outer loop never terminated, because
// m_byte left k at -1 every time round.
int k;
int j;
// ---- observers ---------------------------------------------------------
// The rule of §3.1.9 that cannot be checked from the DUT's ports alone: a stretch is a
// pull-down that begins in the LOW phase. So watch for the drive ASSERTING while the
// resolved line is still high -- which would shorten the master's high time below tHIGH
// and corrupt the bit every device on the bus is sampling.
int drive_while_high = 0;
int stretch_waits = 0; // how many times the master had to wait for the line
int framing_in_stretch = 0; // must stay zero: see T11
int ptr_out_of_phase = 0; // must stay zero: see T15
int scl_held_in_reset = 0; // must stay zero: see T1
logic scl_low_d = 1'b0;
// NOT GUARDED BY rst_n, and that is the point: a device held in reset must not be holding
// the bus down. A mutant whose reset value was "stretching" cleared itself on the first
// clock after release, so every check taken after reset passed -- and the defect, a slave
// that pulls SCL low for the whole of its reset, was invisible.
always @(posedge clk) if (!rst_n && txn_scl_low) scl_held_in_reset++;
// AN OUTPUT MUST BE CORRECT IN ITSELF. `rx_is_pointer` is only consumed together with
// rx_valid, so a version that asserted it outside any phase was harmless in this
// integration -- and a mutant removing the phase gate survived a full pass. Downstream
// masking is not correctness: the next integration may not mask it.
always @(posedge clk) if (rst_n && !in_phase && rx_is_pointer) ptr_out_of_phase++;
always @(posedge clk) if (rst_n) begin
if (txn_scl_low && !scl_low_d && scl === 1'b1) drive_while_high++;
if (stretching && (start_pulse || restart_pulse || stop_pulse))
framing_in_stretch++;
scl_low_d <= txn_scl_low;
end
initial begin
repeat (400000) @(posedge clk);
$display(" FAIL watchdog: the bench did not finish");
$fatal(1);
end
// ---- the master model --------------------------------------------------
task automatic hp; // one half-phase of idling
begin repeat (HALF) @(posedge clk); end
endtask
// Release SCL and WAIT FOR IT TO RISE. A master that skipped the wait could not observe
// a stretch, because it would be driving against the slave's pull-down.
task automatic m_scl_release;
int guard;
begin
@(negedge clk); m_scl_low = 1'b0;
// SETTLE BEFORE READING. Reading `scl` in the same delta as releasing it returns
// the stale value, so the wait would be counted whether or not anybody was
// holding the line -- a check that passes for the wrong reason. The VHDL port of
// this bench is what found it.
#1;
guard = 0;
if (scl !== 1'b1) begin
stretch_waits++;
while (scl !== 1'b1 && guard < 20000) begin @(posedge clk); guard++; end
if (guard >= 20000) begin
$display(" FAIL the slave never released SCL"); errors++;
end
end
hp();
end
endtask
task automatic m_scl_pull;
begin @(negedge clk); m_scl_low = 1'b1; hp(); end
endtask
task automatic m_start;
begin
@(negedge clk); m_scl_low = 1'b0; m_sda_low = 1'b0; hp();
@(negedge clk); m_sda_low = 1'b1; hp(); // SDA falls while SCL is high
m_scl_pull();
end
endtask
task automatic m_restart;
begin
@(negedge clk); m_sda_low = 1'b0; hp(); // release SDA in the low phase
m_scl_release(); // SCL rises
@(negedge clk); m_sda_low = 1'b1; hp(); // SDA falls while SCL is high
m_scl_pull();
end
endtask
task automatic m_stop;
begin
@(negedge clk); m_sda_low = 1'b1; hp(); // SDA low in the low phase
m_scl_release();
@(negedge clk); m_sda_low = 1'b0; hp(); // SDA rises while SCL is high
end
endtask
// One bit clocked by the master. `sda_low` is what the MASTER drives; pass 0 to release
// so the slave can own the line.
task automatic m_bit (input bit sda_low);
begin
@(negedge clk); m_sda_low = sda_low; hp();
m_scl_release();
m_scl_pull();
end
endtask
task automatic m_byte (input [7:0] d);
begin
for (k = 7; k >= 0; k--) m_bit(~d[k]); // a zero is a pull-down
end
endtask
// The ninth slot with the master releasing: the slave answers.
task automatic m_ack_slot_listen;
begin m_bit(1'b0); end
endtask
// The ninth slot with the master answering: a pull-down means ACK.
task automatic m_ack_slot_drive (input bit ack);
begin m_bit(ack); end
endtask
task automatic do_reset;
begin
@(negedge clk);
rst_n = 1'b0; m_scl_low = 1'b0; m_sda_low = 1'b0;
drive_while_high = 0; stretch_waits = 0;
framing_in_stretch = 0; stall_load = 1'b0;
ptr_out_of_phase = 0; scl_held_in_reset = 0;
repeat (4) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
hp(); #1;
end
endtask
task automatic ck (input string what, input int got, input int exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors++;
end
end
endtask
// ---- tests --------------------------------------------------------------
initial begin
$display("=== i2c_slave_txn: transaction state, a surviving pointer, and a wait state ===");
// ---- T1. Reset, including what the slave does WHILE held in reset.
@(negedge clk); rst_n = 1'b0; m_scl_low = 1'b0; m_sda_low = 1'b0;
scl_held_in_reset = 0;
repeat (20) @(posedge clk);
$display("T1 a target held in reset does not hold the bus down");
ck("T1 SCL untouched throughout reset", scl_held_in_reset, 0);
ck("T1 and the line is idle high", scl, 1);
do_reset();
$display("T1 a reset target is in no phase, stretching nothing, driving nothing");
ck("T1 no phase", in_phase, 0);
ck("T1 not stretching", stretching, 0);
ck("T1 SCL not pulled", txn_scl_low, 0);
ck("T1 rx disabled", rx_enable, 0);
// ---- T2. An addressed write opens a phase.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
m_ack_slot_listen();
$display("T2 our address with the write bit opens a write phase");
ck("T2 in a phase", in_phase, 1);
ck("T2 direction is write", dir_q, 0);
ck("T2 the next byte is the pointer", rx_is_pointer, 1);
ck("T2 one phase counted", n_phases, 1);
// ---- T3. The first data byte is the pointer, the rest are not.
m_byte(8'h03); m_ack_slot_listen();
$display("T3 the first data byte is the pointer and the second is not");
ck("T3 pointer loaded", pointer, 3);
ck("T3 index advanced", data_index, 1);
ck("T3 no longer the pointer byte", rx_is_pointer, 0);
m_byte(8'h9E); m_ack_slot_listen();
ck("T3 the second byte is data", reg_flat[8*3 +: 8], 8'h9E);
ck("T3 index saturated", data_index, 2);
// ---- T4. Somebody else's address opens nothing.
do_reset();
m_start();
m_byte({7'h21, 1'b0});
m_ack_slot_listen();
$display("T4 another device's address opens no phase and enables no shifting");
ck("T4 no phase", in_phase, 0);
ck("T4 rx disabled", rx_enable, 0);
ck("T4 no phase counted", n_phases, 0);
// ---- T5. The acknowledge slot is not a data bit.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
@(negedge clk); m_sda_low = 1'b0; hp();
m_scl_release();
$display("T5 rx_enable drops inside the acknowledge slot: the ninth bit is not data");
ck("T5 the slot is active", ack_active, 1);
ck("T5 so shifting is disabled", rx_enable, 0);
m_scl_pull();
// ---- T6. THE HEADLINE: a repeated START keeps the pointer.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h05); m_ack_slot_listen(); // pointer = 5
m_byte(8'h7C); m_ack_slot_listen(); // reg 5 = 0x7C, pointer = 6
ck("T6 written before the restart", reg_flat[8*5 +: 8], 8'h7C);
ck("T6 pointer before the restart", pointer, 6);
m_restart();
$display("T6 a repeated START clears the phase and KEEPS the pointer");
ck("T6 the phase is gone", in_phase, 0);
ck("T6 the direction is gone", dir_q, 0);
ck("T6 the byte index is gone", data_index, 0);
ck("T6 the pointer SURVIVED", pointer, 6);
ck("T6 and so did the data", reg_flat[8*5 +: 8], 8'h7C);
ck("T6 one restart counted", n_restarts_txn, 1);
// ---- T7. A read is started INSIDE the address acknowledge slot, so the first bit
// lands on the fall that terminates it. Starting at the slot's end leaves the
// transmitter one bit late, which this check is what found.
do_reset();
m_start();
m_byte({ADDR, 1'b1});
ck("T7 no bit has been driven at the eighth bit", n_bits_tx, 0);
m_ack_slot_listen();
$display("T7 a read is started inside the address acknowledge slot, so the first bit is on time");
ck("T7 in a read phase", in_phase, 1);
ck("T7 direction is read", dir_q, 1);
ck("T7 the first bit is already driven", n_bits_tx, 1);
ck("T7 and the transmitter owns SDA", driving, 1);
// ---- T8. The master's answer decides whether another byte follows.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h00); m_ack_slot_listen();
m_byte(8'hC3); m_ack_slot_listen(); // reg 0 = 0xC3
m_restart();
m_byte({ADDR, 1'b1}); m_ack_slot_listen();
for (j = 0; j < 8; j++) m_bit(1'b0); // the slave sources a byte
m_ack_slot_drive(1'b1); // the master ACKs: send another
$display("T8 an acknowledged read byte is followed by another; a NACK ends it");
ck("T8 the master acknowledged", n_m_ack, 1);
ck("T8 so sourcing continues", keep_sourcing, 1);
for (j = 0; j < 8; j++) m_bit(1'b0);
m_ack_slot_drive(1'b0); // the master NACKs
ck("T8 the master NACKed", n_m_nack, 1);
ck("T8 so sourcing stopped", keep_sourcing, 0);
ck("T8 two bytes were served", n_bytes_tx, 2);
m_stop();
// ---- T9. Clock stretching, observed by a master that had to wait.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
@(negedge clk); stall_load = 1'b1; // the application needs time
@(posedge clk);
@(negedge clk); stall_load = 1'b0;
m_ack_slot_listen();
$display("T9 the application asks for time, and the master BLOCKS until it is given");
ck("T9 the slave is stretching", stretching, 1);
ck("T9 and is pulling SCL down", txn_scl_low, 1);
ck("T9 SCL is low", scl, 0);
ck("T9 the master has not waited yet", stretch_waits, 0);
// The next byte cannot begin until the slave lets go, so this call BLOCKS.
m_byte(8'h01);
ck("T9 the master had to wait for the line", stretch_waits, 1);
ck("T9 and the stretch is over", stretching, 0);
m_ack_slot_listen();
ck("T9 the transfer continued afterwards", pointer, 1);
ck("T9 one stretch counted", n_stretch, 1);
// ---- T10. A stretch is never a pull-down in the high phase.
$display("T10 across every test so far, SCL was never pulled down while it was high");
ck("T10 no drive in a high phase", drive_while_high, 0);
// ---- T11. A stretch and a framing event cannot coexist, and the design therefore
// carries no code for the combination. This is the check that justifies the removal:
// a stretch holds SCL low, and framing is an SDA edge qualified by SCL HIGH.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
@(negedge clk); stall_load = 1'b1;
@(posedge clk);
@(negedge clk); stall_load = 1'b0;
m_ack_slot_listen();
ck("T11 stretching", stretching, 1);
m_byte(8'h02); // blocks, then completes
m_ack_slot_listen();
m_stop();
$display("T11 a stretch and a framing event never coexist: SCL cannot be low and high");
ck("T11 no framing arrived during a stretch", framing_in_stretch, 0);
ck("T11 the phase closed on the STOP", in_phase, 0);
ck("T11 not stretching", stretching, 0);
ck("T11 SCL released", txn_scl_low, 0);
// ---- T12. The byte index saturates.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h00); m_ack_slot_listen();
for (j = 0; j < 4; j++) begin m_byte(8'h20); m_ack_slot_listen(); end
$display("T12 the byte index saturates: a later byte never becomes a pointer again");
ck("T12 index pinned at two", data_index, 2);
ck("T12 not a pointer byte", rx_is_pointer, 0);
// ---- T13. A STOP ends the phase.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
ck("T13 in a phase", in_phase, 1);
m_stop();
$display("T13 a STOP ends the phase");
ck("T13 phase closed", in_phase, 0);
ck("T13 index cleared", data_index, 0);
// ---- T14. Reset clears the transaction state and every counter.
do_reset();
$display("T14 reset clears the phase, the index and every counter");
ck("T14 no phase", in_phase, 0);
ck("T14 index zero", data_index, 0);
ck("T14 phases zero", n_phases, 0);
ck("T14 restarts zero", n_restarts_txn, 0);
ck("T14 stretches zero", n_stretch, 0);
// ---- T15. A NACKed read must END, even if the master keeps clocking. This is
// Chapter 18.8 §4 at the transaction level: a slave that sources another byte after a
// NACK holds SDA low for every zero in it and prevents the master's STOP.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h00); m_ack_slot_listen();
m_byte(8'hFF); m_ack_slot_listen(); // reg 0 = 0xFF: every bit a pull-down
m_restart();
m_byte({ADDR, 1'b1}); m_ack_slot_listen();
for (j = 0; j < 8; j++) m_bit(1'b0);
m_ack_slot_drive(1'b0); // NACK: that was the last byte
ck("T15 one byte was served", n_bytes_tx, 1);
// The master now keeps clocking anyway. Nothing may come back.
for (j = 0; j < 8; j++) begin
m_bit(1'b0);
if (tx_sda_low) begin
$display(" FAIL T15 the slave drove SDA after a NACK"); errors++;
end
end
$display("T15 a NACKed read ends even against a master that keeps clocking");
ck("T15 still one byte", n_bytes_tx, 1);
ck("T15 the transmitter is idle", driving, 0);
ck("T15 and SDA was left to the master", tx_sda_low, 0);
m_stop();
ck("T15 so the STOP happened", n_sto > 0, 1);
ck("T15 rx_is_pointer never asserted outside a phase", ptr_out_of_phase, 0);
if (errors == 0) $display("=== i2c_slave_txn: ALL CHECKS PASSED ===");
else $display("=== i2c_slave_txn: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule // -----------------------------------------------------------------------------
// i2c_slave_txn_tb.v
// Independent oracle for i2c_slave_txn -- and the first bench in this module that is an
// INTEGRATION bench rather than a unit bench.
//
// WHY IT HAS TO BE. Every output of this block is an instruction to another block: 18.6
// may shift, 18.7 may start, 18.9 may treat this byte as a pointer. An instruction with no
// recipient cannot be checked, and "an output with no consumer instantiated" is the shape
// that let two mutants survive earlier in this module. So the bench builds the real stack
// -- 18.2, 18.3, 18.4, 18.5, 18.6, 18.7, 18.8, 18.9 -- and drives it from a master model
// across the wired-AND bus of Chapter 17.1.
//
// AND THE MASTER MODEL MUST OBEY A STRETCH. A master that clocks on a fixed schedule cannot
// test clock stretching at all: it would simply drive SCL high while the slave held it low,
// which is electrically impossible and would make the stretch invisible. So this master
// RELEASES SCL and then waits for the line to actually rise, counting how many times it had
// to wait. That count is the only direct evidence a stretch had an effect on anybody.
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module i2c_slave_txn_tb;
localparam integer HALF = 8; // clocks per SCL half-phase
localparam [6:0] ADDR = 7'h50;
localparam integer N_REG = 8;
localparam integer RO_MASK = 8'h04;
reg clk = 1'b0;
reg rst_n = 1'b0;
// ---- the bus: device 0 is the master model, device 1 is the slave --------
wire [1:0] scl_dl, sda_dl;
wire scl, sda;
wire [1:0] scl_in, sda_in, scl_rbl, sda_rbl;
wire [7:0] scl_holders, sda_holders;
reg m_scl_low = 1'b0, m_sda_low = 1'b0;
i2c_line_model #(.N_DEV(2)) bus (
.scl_drive_low(scl_dl), .sda_drive_low(sda_dl),
.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_holders), .sda_holders(sda_holders)
);
// ---- the slave stack ----------------------------------------------------
wire scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall;
i2c_slave_sync #(.SYNC_DEPTH(2)) u_sync (
.clk(clk), .rst_n(rst_n), .scl_pin(scl), .sda_pin(sda),
.scl_q(scl_q), .sda_q(sda_q), .scl_rise(scl_rise), .scl_fall(scl_fall),
.sda_rise(sda_rise), .sda_fall(sda_fall)
);
wire start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte;
wire [15:0] n_sta, n_rs, n_sto;
wire acquiring, addr_done, match, selected, dir_read;
wire [3:0] a_bit_index;
wire [7:0] addr_byte;
wire [15:0] n_match, n_miss;
wire receiving, rx_valid, rx_byte_done;
wire [3:0] r_bit_index;
wire [7:0] rx_byte;
wire [15:0] n_bytes_rx, n_partial;
wire mid_byte = acquiring || receiving;
i2c_slave_framing #(.CNT_W(16)) u_frm (
.clk(clk), .rst_n(rst_n), .scl_q(scl_q), .sda_rise(sda_rise), .sda_fall(sda_fall),
.start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
.bus_active(bus_active), .mid_byte(mid_byte), .framing_midbyte(framing_midbyte),
.n_starts(n_sta), .n_restarts(n_rs), .n_stops(n_sto)
);
i2c_slave_addr #(.MY_ADDR(ADDR), .CNT_W(16)) u_addr (
.clk(clk), .rst_n(rst_n), .scl_rise(scl_rise), .sda_q(sda_q),
.start_pulse(start_pulse), .stop_pulse(stop_pulse),
.acquiring(acquiring), .bit_index(a_bit_index), .addr_done(addr_done),
.addr_byte(addr_byte), .match(match), .selected(selected), .dir_read(dir_read),
.n_match(n_match), .n_miss(n_miss)
);
// ---- the DUT ------------------------------------------------------------
wire in_phase, dir_q, rx_is_pointer, rx_enable, tx_start, tx_continue;
wire [3:0] data_index;
wire txn_scl_low, stretching;
wire [15:0] n_phases, n_restarts_txn, n_stretch;
// THE APPLICATION'S STALL, AS A COUNTDOWN. A test that raises a level and lowers it by
// hand can only ever stretch while nothing is happening -- so the master never has to
// wait, and the stretch is invisible to it. A countdown that expires on its own lets the
// master BLOCK inside its own bit task, which is the only way the wait becomes an
// observable fact about the bus rather than a flag inside the DUT.
localparam integer STALL_N = 40;
reg stall_load = 1'b0;
integer stall_hold = 0;
wire stall_req = (stall_hold > 0);
always @(posedge clk) begin
if (!rst_n) stall_hold <= 0;
else if (stall_load) stall_hold <= STALL_N;
else if (stall_hold > 0) stall_hold <= stall_hold - 1;
end
wire ack_active, ack_armed, ack_sda_low;
wire [15:0] n_acks, n_nacks;
wire tx_req, tx_sda_low, driving, byte_sent;
wire [3:0] t_bit_index;
wire [15:0] n_bytes_tx, n_bits_tx;
wire [7:0] rd_data;
wire awaiting, mack_valid, mack_ack, keep_sourcing;
wire [15:0] n_m_ack, n_m_nack;
wire wr_accept;
wire [8*N_REG-1:0] reg_flat;
wire [7:0] pointer;
wire [15:0] n_writes, n_refused, n_reads;
i2c_slave_txn #(.CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n), .scl_q(scl_q), .scl_fall(scl_fall),
.start_pulse(start_pulse), .restart_pulse(restart_pulse), .stop_pulse(stop_pulse),
.addr_done(addr_done), .match(match), .dir_read(dir_read),
.ack_active(ack_active), .rx_valid(rx_valid), .byte_sent(byte_sent),
.mack_valid(mack_valid), .mack_ack(mack_ack), .stall_req(stall_req),
.in_phase(in_phase), .dir_q(dir_q), .rx_is_pointer(rx_is_pointer),
.rx_enable(rx_enable), .tx_start(tx_start), .tx_continue(tx_continue),
.data_index(data_index),
.scl_drive_low(txn_scl_low), .stretching(stretching),
.n_phases(n_phases), .n_restarts(n_restarts_txn), .n_stretch(n_stretch)
);
// The acknowledge slot: armed by any completed byte we owe an answer for. The address
// byte is always acknowledged when it matched; a data byte is acknowledged only if the
// register file will take it, which is 18.9's wr_accept arriving as an ack policy.
wire byte_done_any = rx_byte_done || (addr_done && match);
wire ack_en = (addr_done && match) ? 1'b1 : wr_accept;
i2c_slave_ack #(.CNT_W(16)) u_ack (
.clk(clk), .rst_n(rst_n), .scl_fall(scl_fall), .byte_done(byte_done_any),
.ack_en(ack_en), .start_pulse(start_pulse), .stop_pulse(stop_pulse),
.sda_drive_low(ack_sda_low), .ack_active(ack_active), .ack_armed(ack_armed),
.n_acks(n_acks), .n_nacks(n_nacks)
);
i2c_slave_rx #(.CNT_W(16)) u_rx (
.clk(clk), .rst_n(rst_n), .scl_rise(scl_rise), .sda_q(sda_q),
.start_pulse(start_pulse), .stop_pulse(stop_pulse), .rx_enable(rx_enable),
.receiving(receiving), .bit_index(r_bit_index), .rx_byte(rx_byte),
.rx_valid(rx_valid), .byte_done(rx_byte_done),
.n_bytes(n_bytes_rx), .n_partial(n_partial)
);
// ---- the transmit side, so tx_start and tx_continue have a consumer -----
i2c_slave_tx #(.CNT_W(16)) u_tx (
.clk(clk), .rst_n(rst_n), .scl_fall(scl_fall),
.start_pulse(start_pulse), .stop_pulse(stop_pulse),
.tx_start(tx_start), .tx_continue(tx_continue),
.tx_req(tx_req), .tx_byte(rd_data), .sda_drive_low(tx_sda_low),
.driving(driving), .bit_index(t_bit_index), .byte_sent(byte_sent),
.n_bytes(n_bytes_tx), .n_bits(n_bits_tx)
);
i2c_slave_mack #(.CNT_W(16)) u_mack (
.clk(clk), .rst_n(rst_n), .scl_rise(scl_rise), .sda_q(sda_q),
.start_pulse(start_pulse), .stop_pulse(stop_pulse), .byte_sent(byte_sent),
.awaiting(awaiting), .mack_valid(mack_valid), .mack_ack(mack_ack),
.keep_sourcing(keep_sourcing), .n_ack(n_m_ack), .n_nack(n_m_nack)
);
// ---- the register file, whose pointer must survive a repeated START ----
i2c_slave_regs #(.N_REG(N_REG), .RO_MASK(RO_MASK), .CNT_W(16)) u_regs (
.clk(clk), .rst_n(rst_n),
.rx_valid(rx_valid), .rx_byte(rx_byte), .rx_is_pointer(rx_is_pointer),
.wr_accept(wr_accept), .rd_data(rd_data), .rd_taken(tx_req),
.reg_flat(reg_flat), .pointer(pointer),
.n_writes(n_writes), .n_refused(n_refused), .n_reads(n_reads)
);
assign scl_dl = {txn_scl_low, m_scl_low};
assign sda_dl = {ack_sda_low | tx_sda_low, m_sda_low};
always #5 clk = ~clk;
integer errors = 0;
// `k` belongs to the bus tasks -- m_byte drives it -- so a test that loops must use its
// own variable. Sharing one cost an afternoon: the outer loop never terminated, because
// m_byte left k at -1 every time round.
integer k;
integer j;
// ---- observers ---------------------------------------------------------
// The rule of §3.1.9 that cannot be checked from the DUT's ports alone: a stretch is a
// pull-down that begins in the LOW phase. So watch for the drive ASSERTING while the
// resolved line is still high -- which would shorten the master's high time below tHIGH
// and corrupt the bit every device on the bus is sampling.
integer drive_while_high = 0;
integer stretch_waits = 0; // how many times the master had to wait for the line
integer framing_in_stretch = 0; // must stay zero: see T11
integer ptr_out_of_phase = 0; // must stay zero: see T15
integer scl_held_in_reset = 0; // must stay zero: see T1
reg scl_low_d = 1'b0;
// NOT GUARDED BY rst_n, and that is the point: a device held in reset must not be holding
// the bus down. A mutant whose reset value was "stretching" cleared itself on the first
// clock after release, so every check taken after reset passed -- and the defect, a slave
// that pulls SCL low for the whole of its reset, was invisible.
always @(posedge clk) if (!rst_n && txn_scl_low) scl_held_in_reset = scl_held_in_reset + 1;
// AN OUTPUT MUST BE CORRECT IN ITSELF. `rx_is_pointer` is only consumed together with
// rx_valid, so a version that asserted it outside any phase was harmless in this
// integration -- and a mutant removing the phase gate survived a full pass. Downstream
// masking is not correctness: the next integration may not mask it.
always @(posedge clk) if (rst_n && !in_phase && rx_is_pointer) ptr_out_of_phase = ptr_out_of_phase + 1;
always @(posedge clk) if (rst_n) begin
if (txn_scl_low && !scl_low_d && scl === 1'b1) drive_while_high = drive_while_high + 1;
if (stretching && (start_pulse || restart_pulse || stop_pulse))
framing_in_stretch = framing_in_stretch + 1;
scl_low_d <= txn_scl_low;
end
initial begin
repeat (400000) @(posedge clk);
$display(" FAIL watchdog: the bench did not finish");
$fatal(1);
end
// ---- the master model --------------------------------------------------
task hp; // one half-phase of idling
begin repeat (HALF) @(posedge clk); end
endtask
// Release SCL and WAIT FOR IT TO RISE. A master that skipped the wait could not observe
// a stretch, because it would be driving against the slave's pull-down.
task m_scl_release;
integer guard;
begin
@(negedge clk); m_scl_low = 1'b0;
// SETTLE BEFORE READING. Reading `scl` in the same delta as releasing it returns
// the stale value, so the wait would be counted whether or not anybody was
// holding the line -- a check that passes for the wrong reason. The VHDL port of
// this bench is what found it.
#1;
guard = 0;
if (scl !== 1'b1) begin
stretch_waits = stretch_waits + 1;
while (scl !== 1'b1 && guard < 20000) begin @(posedge clk); guard = guard + 1; end
if (guard >= 20000) begin
$display(" FAIL the slave never released SCL"); errors = errors + 1;
end
end
hp();
end
endtask
task m_scl_pull;
begin @(negedge clk); m_scl_low = 1'b1; hp(); end
endtask
task m_start;
begin
@(negedge clk); m_scl_low = 1'b0; m_sda_low = 1'b0; hp();
@(negedge clk); m_sda_low = 1'b1; hp(); // SDA falls while SCL is high
m_scl_pull();
end
endtask
task m_restart;
begin
@(negedge clk); m_sda_low = 1'b0; hp(); // release SDA in the low phase
m_scl_release(); // SCL rises
@(negedge clk); m_sda_low = 1'b1; hp(); // SDA falls while SCL is high
m_scl_pull();
end
endtask
task m_stop;
begin
@(negedge clk); m_sda_low = 1'b1; hp(); // SDA low in the low phase
m_scl_release();
@(negedge clk); m_sda_low = 1'b0; hp(); // SDA rises while SCL is high
end
endtask
// One bit clocked by the master. `sda_low` is what the MASTER drives; pass 0 to release
// so the slave can own the line.
task m_bit (input sda_low);
begin
@(negedge clk); m_sda_low = sda_low; hp();
m_scl_release();
m_scl_pull();
end
endtask
task m_byte (input [7:0] d);
begin
for (k = 7; k >= 0; k = k - 1) m_bit(~d[k]); // a zero is a pull-down
end
endtask
// The ninth slot with the master releasing: the slave answers.
task m_ack_slot_listen;
begin m_bit(1'b0); end
endtask
// The ninth slot with the master answering: a pull-down means ACK.
task m_ack_slot_drive (input ack);
begin m_bit(ack); end
endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; m_scl_low = 1'b0; m_sda_low = 1'b0;
drive_while_high = 0; stretch_waits = 0;
framing_in_stretch = 0; stall_load = 1'b0;
ptr_out_of_phase = 0; scl_held_in_reset = 0;
repeat (4) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
hp(); #1;
end
endtask
task ck (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
// ---- tests --------------------------------------------------------------
initial begin
$display("=== i2c_slave_txn: transaction state, a surviving pointer, and a wait state ===");
// ---- T1. Reset, including what the slave does WHILE held in reset.
@(negedge clk); rst_n = 1'b0; m_scl_low = 1'b0; m_sda_low = 1'b0;
scl_held_in_reset = 0;
repeat (20) @(posedge clk);
$display("T1 a target held in reset does not hold the bus down");
ck("T1 SCL untouched throughout reset", scl_held_in_reset, 0);
ck("T1 and the line is idle high", scl, 1);
do_reset();
$display("T1 a reset target is in no phase, stretching nothing, driving nothing");
ck("T1 no phase", in_phase, 0);
ck("T1 not stretching", stretching, 0);
ck("T1 SCL not pulled", txn_scl_low, 0);
ck("T1 rx disabled", rx_enable, 0);
// ---- T2. An addressed write opens a phase.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
m_ack_slot_listen();
$display("T2 our address with the write bit opens a write phase");
ck("T2 in a phase", in_phase, 1);
ck("T2 direction is write", dir_q, 0);
ck("T2 the next byte is the pointer", rx_is_pointer, 1);
ck("T2 one phase counted", n_phases, 1);
// ---- T3. The first data byte is the pointer, the rest are not.
m_byte(8'h03); m_ack_slot_listen();
$display("T3 the first data byte is the pointer and the second is not");
ck("T3 pointer loaded", pointer, 3);
ck("T3 index advanced", data_index, 1);
ck("T3 no longer the pointer byte", rx_is_pointer, 0);
m_byte(8'h9E); m_ack_slot_listen();
ck("T3 the second byte is data", reg_flat[8*3 +: 8], 8'h9E);
ck("T3 index saturated", data_index, 2);
// ---- T4. Somebody else's address opens nothing.
do_reset();
m_start();
m_byte({7'h21, 1'b0});
m_ack_slot_listen();
$display("T4 another device's address opens no phase and enables no shifting");
ck("T4 no phase", in_phase, 0);
ck("T4 rx disabled", rx_enable, 0);
ck("T4 no phase counted", n_phases, 0);
// ---- T5. The acknowledge slot is not a data bit.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
@(negedge clk); m_sda_low = 1'b0; hp();
m_scl_release();
$display("T5 rx_enable drops inside the acknowledge slot: the ninth bit is not data");
ck("T5 the slot is active", ack_active, 1);
ck("T5 so shifting is disabled", rx_enable, 0);
m_scl_pull();
// ---- T6. THE HEADLINE: a repeated START keeps the pointer.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h05); m_ack_slot_listen(); // pointer = 5
m_byte(8'h7C); m_ack_slot_listen(); // reg 5 = 0x7C, pointer = 6
ck("T6 written before the restart", reg_flat[8*5 +: 8], 8'h7C);
ck("T6 pointer before the restart", pointer, 6);
m_restart();
$display("T6 a repeated START clears the phase and KEEPS the pointer");
ck("T6 the phase is gone", in_phase, 0);
ck("T6 the direction is gone", dir_q, 0);
ck("T6 the byte index is gone", data_index, 0);
ck("T6 the pointer SURVIVED", pointer, 6);
ck("T6 and so did the data", reg_flat[8*5 +: 8], 8'h7C);
ck("T6 one restart counted", n_restarts_txn, 1);
// ---- T7. A read is started INSIDE the address acknowledge slot, so the first bit
// lands on the fall that terminates it. Starting at the slot's end leaves the
// transmitter one bit late, which this check is what found.
do_reset();
m_start();
m_byte({ADDR, 1'b1});
ck("T7 no bit has been driven at the eighth bit", n_bits_tx, 0);
m_ack_slot_listen();
$display("T7 a read is started inside the address acknowledge slot, so the first bit is on time");
ck("T7 in a read phase", in_phase, 1);
ck("T7 direction is read", dir_q, 1);
ck("T7 the first bit is already driven", n_bits_tx, 1);
ck("T7 and the transmitter owns SDA", driving, 1);
// ---- T8. The master's answer decides whether another byte follows.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h00); m_ack_slot_listen();
m_byte(8'hC3); m_ack_slot_listen(); // reg 0 = 0xC3
m_restart();
m_byte({ADDR, 1'b1}); m_ack_slot_listen();
for (j = 0; j < 8; j = j + 1) m_bit(1'b0); // the slave sources a byte
m_ack_slot_drive(1'b1); // the master ACKs: send another
$display("T8 an acknowledged read byte is followed by another; a NACK ends it");
ck("T8 the master acknowledged", n_m_ack, 1);
ck("T8 so sourcing continues", keep_sourcing, 1);
for (j = 0; j < 8; j = j + 1) m_bit(1'b0);
m_ack_slot_drive(1'b0); // the master NACKs
ck("T8 the master NACKed", n_m_nack, 1);
ck("T8 so sourcing stopped", keep_sourcing, 0);
ck("T8 two bytes were served", n_bytes_tx, 2);
m_stop();
// ---- T9. Clock stretching, observed by a master that had to wait.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
@(negedge clk); stall_load = 1'b1; // the application needs time
@(posedge clk);
@(negedge clk); stall_load = 1'b0;
m_ack_slot_listen();
$display("T9 the application asks for time, and the master BLOCKS until it is given");
ck("T9 the slave is stretching", stretching, 1);
ck("T9 and is pulling SCL down", txn_scl_low, 1);
ck("T9 SCL is low", scl, 0);
ck("T9 the master has not waited yet", stretch_waits, 0);
// The next byte cannot begin until the slave lets go, so this call BLOCKS.
m_byte(8'h01);
ck("T9 the master had to wait for the line", stretch_waits, 1);
ck("T9 and the stretch is over", stretching, 0);
m_ack_slot_listen();
ck("T9 the transfer continued afterwards", pointer, 1);
ck("T9 one stretch counted", n_stretch, 1);
// ---- T10. A stretch is never a pull-down in the high phase.
$display("T10 across every test so far, SCL was never pulled down while it was high");
ck("T10 no drive in a high phase", drive_while_high, 0);
// ---- T11. A stretch and a framing event cannot coexist, and the design therefore
// carries no code for the combination. This is the check that justifies the removal:
// a stretch holds SCL low, and framing is an SDA edge qualified by SCL HIGH.
do_reset();
m_start();
m_byte({ADDR, 1'b0});
@(negedge clk); stall_load = 1'b1;
@(posedge clk);
@(negedge clk); stall_load = 1'b0;
m_ack_slot_listen();
ck("T11 stretching", stretching, 1);
m_byte(8'h02); // blocks, then completes
m_ack_slot_listen();
m_stop();
$display("T11 a stretch and a framing event never coexist: SCL cannot be low and high");
ck("T11 no framing arrived during a stretch", framing_in_stretch, 0);
ck("T11 the phase closed on the STOP", in_phase, 0);
ck("T11 not stretching", stretching, 0);
ck("T11 SCL released", txn_scl_low, 0);
// ---- T12. The byte index saturates.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h00); m_ack_slot_listen();
for (j = 0; j < 4; j = j + 1) begin m_byte(8'h20); m_ack_slot_listen(); end
$display("T12 the byte index saturates: a later byte never becomes a pointer again");
ck("T12 index pinned at two", data_index, 2);
ck("T12 not a pointer byte", rx_is_pointer, 0);
// ---- T13. A STOP ends the phase.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
ck("T13 in a phase", in_phase, 1);
m_stop();
$display("T13 a STOP ends the phase");
ck("T13 phase closed", in_phase, 0);
ck("T13 index cleared", data_index, 0);
// ---- T14. Reset clears the transaction state and every counter.
do_reset();
$display("T14 reset clears the phase, the index and every counter");
ck("T14 no phase", in_phase, 0);
ck("T14 index zero", data_index, 0);
ck("T14 phases zero", n_phases, 0);
ck("T14 restarts zero", n_restarts_txn, 0);
ck("T14 stretches zero", n_stretch, 0);
// ---- T15. A NACKed read must END, even if the master keeps clocking. This is
// Chapter 18.8 §4 at the transaction level: a slave that sources another byte after a
// NACK holds SDA low for every zero in it and prevents the master's STOP.
do_reset();
m_start();
m_byte({ADDR, 1'b0}); m_ack_slot_listen();
m_byte(8'h00); m_ack_slot_listen();
m_byte(8'hFF); m_ack_slot_listen(); // reg 0 = 0xFF: every bit a pull-down
m_restart();
m_byte({ADDR, 1'b1}); m_ack_slot_listen();
for (j = 0; j < 8; j = j + 1) m_bit(1'b0);
m_ack_slot_drive(1'b0); // NACK: that was the last byte
ck("T15 one byte was served", n_bytes_tx, 1);
// The master now keeps clocking anyway. Nothing may come back.
for (j = 0; j < 8; j = j + 1) begin
m_bit(1'b0);
if (tx_sda_low) begin
$display(" FAIL T15 the slave drove SDA after a NACK"); errors = errors + 1;
end
end
$display("T15 a NACKed read ends even against a master that keeps clocking");
ck("T15 still one byte", n_bytes_tx, 1);
ck("T15 the transmitter is idle", driving, 0);
ck("T15 and SDA was left to the master", tx_sda_low, 0);
m_stop();
ck("T15 so the STOP happened", n_sto > 0, 1);
ck("T15 rx_is_pointer never asserted outside a phase", ptr_out_of_phase, 0);
if (errors == 0) $display("=== i2c_slave_txn: ALL CHECKS PASSED ===");
else $display("=== i2c_slave_txn: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- -----------------------------------------------------------------------------
-- i2c_slave_txn_tb.vhd
-- Independent oracle for i2c_slave_txn -- the integration bench, in VHDL.
--
-- The whole slave stack is instantiated (18.2 through 18.9) and driven from a master model
-- across the wired-AND bus, because every output of this block is an instruction to another
-- block and an instruction with no recipient cannot be checked.
--
-- The master RELEASES SCL and waits for the line to rise. A master that clocked on a fixed
-- schedule could not test a stretch at all.
-- -----------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_slave_txn_tb is
end entity i2c_slave_txn_tb;
architecture sim of i2c_slave_txn_tb is
constant HALF : positive := 8;
constant ADDR : std_logic_vector(6 downto 0) := "1010000"; -- 0x50
constant N_REG : positive := 8;
constant RO_MASK : natural := 4; -- register 2 read-only
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal m_scl_low, m_sda_low : std_logic := '0';
signal scl_dl, sda_dl : 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_holders, sda_holders : unsigned(7 downto 0);
signal scl_q, sda_q, scl_rise, scl_fall, sda_rise, sda_fall : std_logic;
signal start_pulse, restart_pulse, stop_pulse, bus_active, framing_midbyte : std_logic;
signal n_sta, n_rs, n_sto : unsigned(15 downto 0);
signal acquiring, addr_done, match, selected, dir_read : std_logic;
signal a_bit_index : unsigned(3 downto 0);
signal addr_byte : std_logic_vector(7 downto 0);
signal n_match, n_miss : unsigned(15 downto 0);
signal receiving, rx_valid, rx_byte_done : std_logic;
signal r_bit_index : unsigned(3 downto 0);
signal rx_byte : std_logic_vector(7 downto 0);
signal n_bytes_rx, n_partial : unsigned(15 downto 0);
signal in_phase, dir_q, rx_is_pointer, rx_enable, tx_start, tx_continue : std_logic;
signal data_index : unsigned(3 downto 0);
signal txn_scl_low, stretching : std_logic;
signal n_phases, n_restarts_txn, n_stretch : unsigned(15 downto 0);
-- THE APPLICATION'S STALL, AS A COUNTDOWN. A test that raises a level and lowers it by
-- hand can only stretch while nothing is happening, so the master never has to wait and
-- the stretch is invisible to it. A countdown that expires on its own lets the master
-- BLOCK inside its own bit procedure, which is the only way the wait becomes an
-- observable fact about the bus.
constant STALL_N : positive := 40;
signal stall_load : std_logic := '0';
signal stall_hold : integer := 0;
signal stall_req : std_logic;
signal ack_active, ack_armed, ack_sda_low : std_logic;
signal n_acks, n_nacks : unsigned(15 downto 0);
signal tx_req, tx_sda_low, driving, byte_sent : std_logic;
signal t_bit_index : unsigned(3 downto 0);
signal n_bytes_tx, n_bits_tx : unsigned(15 downto 0);
signal rd_data : std_logic_vector(7 downto 0);
signal awaiting, mack_valid, mack_ack, keep_sourcing : std_logic;
signal n_m_ack, n_m_nack : unsigned(15 downto 0);
signal wr_accept : std_logic;
signal reg_flat : std_logic_vector(8*N_REG-1 downto 0);
signal pointer : std_logic_vector(7 downto 0);
signal n_writes, n_refused, n_reads : unsigned(15 downto 0);
signal mid_byte : std_logic;
signal byte_done_any : std_logic;
signal ack_en : std_logic;
signal halt : boolean := false;
-- observers
signal drive_while_high : integer := 0;
signal stretch_waits : integer := 0;
signal framing_in_stretch : integer := 0;
signal ptr_out_of_phase : integer := 0;
signal scl_held_in_reset : integer := 0;
signal clr_obs : boolean := false;
begin
stall_req <= '1' when stall_hold > 0 else '0';
stall_ctr : process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
stall_hold <= 0;
elsif stall_load = '1' then
stall_hold <= STALL_N;
elsif stall_hold > 0 then
stall_hold <= stall_hold - 1;
end if;
end if;
end process;
mid_byte <= acquiring or receiving;
byte_done_any <= rx_byte_done or (addr_done and match);
ack_en <= '1' when (addr_done = '1' and match = '1') else wr_accept;
scl_dl <= txn_scl_low & m_scl_low;
sda_dl <= (ack_sda_low or tx_sda_low) & m_sda_low;
bus_model : entity work.i2c_line_model
generic map (N_DEV => 2)
port map (scl_drive_low => scl_dl, sda_drive_low => sda_dl,
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_holders, sda_holders => sda_holders);
u_sync : entity work.i2c_slave_sync
generic map (SYNC_DEPTH => 2)
port map (clk => clk, rst_n => rst_n, scl_pin => scl, sda_pin => sda,
scl_q => scl_q, sda_q => sda_q, scl_rise => scl_rise, scl_fall => scl_fall,
sda_rise => sda_rise, sda_fall => sda_fall);
u_frm : entity work.i2c_slave_framing
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_q => scl_q,
sda_rise => sda_rise, sda_fall => sda_fall,
start_pulse => start_pulse, restart_pulse => restart_pulse,
stop_pulse => stop_pulse, bus_active => bus_active,
mid_byte => mid_byte, framing_midbyte => framing_midbyte,
n_starts => n_sta, n_restarts => n_rs, n_stops => n_sto);
u_addr : entity work.i2c_slave_addr
generic map (MY_ADDR => ADDR, CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_rise => scl_rise, sda_q => sda_q,
start_pulse => start_pulse, stop_pulse => stop_pulse,
acquiring => acquiring, bit_index => a_bit_index, addr_done => addr_done,
addr_byte => addr_byte, match => match, selected => selected,
dir_read => dir_read, n_match => n_match, n_miss => n_miss);
dut : entity work.i2c_slave_txn
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_q => scl_q, scl_fall => scl_fall,
start_pulse => start_pulse, restart_pulse => restart_pulse,
stop_pulse => stop_pulse, addr_done => addr_done, match => match,
dir_read => dir_read, ack_active => ack_active, rx_valid => rx_valid,
byte_sent => byte_sent, mack_valid => mack_valid, mack_ack => mack_ack,
stall_req => stall_req,
in_phase => in_phase, dir_q => dir_q, rx_is_pointer => rx_is_pointer,
rx_enable => rx_enable, tx_start => tx_start, tx_continue => tx_continue,
data_index => data_index, scl_drive_low => txn_scl_low,
stretching => stretching, n_phases => n_phases,
n_restarts => n_restarts_txn, n_stretch => n_stretch);
u_ack : entity work.i2c_slave_ack
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_fall => scl_fall,
byte_done => byte_done_any, ack_en => ack_en,
start_pulse => start_pulse, stop_pulse => stop_pulse,
sda_drive_low => ack_sda_low, ack_active => ack_active,
ack_armed => ack_armed, n_acks => n_acks, n_nacks => n_nacks);
u_rx : entity work.i2c_slave_rx
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_rise => scl_rise, sda_q => sda_q,
start_pulse => start_pulse, stop_pulse => stop_pulse, rx_enable => rx_enable,
receiving => receiving, bit_index => r_bit_index, rx_byte => rx_byte,
rx_valid => rx_valid, byte_done => rx_byte_done,
n_bytes => n_bytes_rx, n_partial => n_partial);
u_tx : entity work.i2c_slave_tx
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_fall => scl_fall,
start_pulse => start_pulse, stop_pulse => stop_pulse,
tx_start => tx_start, tx_continue => tx_continue,
tx_req => tx_req, tx_byte => rd_data, sda_drive_low => tx_sda_low,
driving => driving, bit_index => t_bit_index, byte_sent => byte_sent,
n_bytes => n_bytes_tx, n_bits => n_bits_tx);
u_mack : entity work.i2c_slave_mack
generic map (CNT_W => 16)
port map (clk => clk, rst_n => rst_n, scl_rise => scl_rise, sda_q => sda_q,
start_pulse => start_pulse, stop_pulse => stop_pulse, byte_sent => byte_sent,
awaiting => awaiting, mack_valid => mack_valid, mack_ack => mack_ack,
keep_sourcing => keep_sourcing, n_ack => n_m_ack, n_nack => n_m_nack);
u_regs : entity work.i2c_slave_regs
generic map (N_REG => N_REG, RO_MASK => RO_MASK, CNT_W => 16)
port map (clk => clk, rst_n => rst_n,
rx_valid => rx_valid, rx_byte => rx_byte, rx_is_pointer => rx_is_pointer,
wr_accept => wr_accept, rd_data => rd_data, rd_taken => tx_req,
reg_flat => reg_flat, pointer => pointer,
n_writes => n_writes, n_refused => n_refused, n_reads => n_reads);
-- NOT guarded by rst_n: a device held in reset must not be holding the bus down. A mutant
-- whose reset value was "stretching" cleared itself on the first clock after release, so
-- every check taken after reset passed and the defect was invisible.
obs_reset : process (clk, clr_obs)
begin
if clr_obs then
scl_held_in_reset <= 0;
elsif rising_edge(clk) then
if rst_n = '0' and txn_scl_low = '1' then
scl_held_in_reset <= scl_held_in_reset + 1;
end if;
end if;
end process;
-- AN OUTPUT MUST BE CORRECT IN ITSELF. rx_is_pointer is only consumed with rx_valid, so a
-- version asserting it outside any phase was harmless HERE -- and a mutant removing the
-- phase gate survived a full pass. Downstream masking is not correctness.
obs_ptr : process (clk, clr_obs)
begin
if clr_obs then
ptr_out_of_phase <= 0;
elsif rising_edge(clk) then
if rst_n = '1' and in_phase = '0' and rx_is_pointer = '1' then
ptr_out_of_phase <= ptr_out_of_phase + 1;
end if;
end if;
end process;
clkgen : process
begin
while not halt loop
clk <= '0'; wait for 5 ns;
clk <= '1'; wait for 5 ns;
end loop;
wait;
end process;
-- The rule of §3.1.9 that cannot be checked from the DUT's ports alone: a stretch is a
-- pull-down that begins in the LOW phase.
obs : process (clk, clr_obs)
variable d : std_logic := '0';
begin
if clr_obs then
drive_while_high <= 0; framing_in_stretch <= 0;
elsif rising_edge(clk) then
if rst_n = '1' then
if txn_scl_low = '1' and d = '0' and scl = '1' then
drive_while_high <= drive_while_high + 1;
end if;
if stretching = '1' and
(start_pulse = '1' or restart_pulse = '1' or stop_pulse = '1') then
framing_in_stretch <= framing_in_stretch + 1;
end if;
end if;
d := txn_scl_low;
end if;
end process;
stim : process
variable err : integer := 0;
procedure hp is
begin
for i in 1 to HALF loop wait until rising_edge(clk); end loop;
end procedure;
procedure ck (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;
function b2i (b : std_logic) return integer is
begin
if b = '1' then return 1; else return 0; end if;
end function;
function slice8 (f : std_logic_vector; idx : integer) return integer is
begin
return to_integer(unsigned(f(8*idx+7 downto 8*idx)));
end function;
-- Release SCL and WAIT FOR IT TO RISE.
procedure m_scl_release is
variable guard : integer := 0;
begin
wait until falling_edge(clk);
m_scl_low <= '0';
wait for 1 ns;
if scl /= '1' then
stretch_waits <= stretch_waits + 1;
while scl /= '1' and guard < 20000 loop
wait until rising_edge(clk);
guard := guard + 1;
end loop;
if guard >= 20000 then
report " FAIL the slave never released SCL" severity note;
err := err + 1;
end if;
end if;
hp;
end procedure;
procedure m_scl_pull is
begin
wait until falling_edge(clk); m_scl_low <= '1'; hp;
end procedure;
procedure m_start is
begin
wait until falling_edge(clk); m_scl_low <= '0'; m_sda_low <= '0'; hp;
wait until falling_edge(clk); m_sda_low <= '1'; hp;
m_scl_pull;
end procedure;
procedure m_restart is
begin
wait until falling_edge(clk); m_sda_low <= '0'; hp;
m_scl_release;
wait until falling_edge(clk); m_sda_low <= '1'; hp;
m_scl_pull;
end procedure;
procedure m_stop is
begin
wait until falling_edge(clk); m_sda_low <= '1'; hp;
m_scl_release;
wait until falling_edge(clk); m_sda_low <= '0'; hp;
end procedure;
procedure m_bit (sda_low : std_logic) is
begin
wait until falling_edge(clk); m_sda_low <= sda_low; hp;
m_scl_release;
m_scl_pull;
end procedure;
procedure m_byte (d : std_logic_vector(7 downto 0)) is
begin
for i in 7 downto 0 loop m_bit(not d(i)); end loop;
end procedure;
procedure m_ack_slot_listen is
begin
m_bit('0');
end procedure;
procedure m_ack_slot_drive (ack : std_logic) is
begin
m_bit(ack);
end procedure;
procedure do_reset is
begin
wait until falling_edge(clk);
rst_n <= '0'; m_scl_low <= '0'; m_sda_low <= '0'; stall_load <= '0';
clr_obs <= true; stretch_waits <= 0; wait for 1 ns; clr_obs <= false;
for i in 1 to 4 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk); rst_n <= '1';
hp; wait for 1 ns;
end procedure;
begin
report "=== i2c_slave_txn: transaction state, a surviving pointer, and a wait state ==="
severity note;
-- T1. Reset, including what the slave does WHILE held in reset.
wait until falling_edge(clk);
rst_n <= '0'; m_scl_low <= '0'; m_sda_low <= '0';
clr_obs <= true; wait for 1 ns; clr_obs <= false;
for i in 1 to 20 loop wait until rising_edge(clk); end loop;
report "T1 a target held in reset does not hold the bus down" severity note;
ck("T1 SCL untouched throughout reset", scl_held_in_reset, 0);
ck("T1 and the line is idle high", b2i(scl), 1);
do_reset;
report "T1 a reset target is in no phase, stretching nothing, driving nothing"
severity note;
ck("T1 no phase", b2i(in_phase), 0);
ck("T1 not stretching", b2i(stretching), 0);
ck("T1 SCL not pulled", b2i(txn_scl_low), 0);
ck("T1 rx disabled", b2i(rx_enable), 0);
-- T2.
do_reset;
m_start;
m_byte(ADDR & '0');
m_ack_slot_listen;
report "T2 our address with the write bit opens a write phase" severity note;
ck("T2 in a phase", b2i(in_phase), 1);
ck("T2 direction is write", b2i(dir_q), 0);
ck("T2 the next byte is the pointer", b2i(rx_is_pointer), 1);
ck("T2 one phase counted", to_integer(n_phases), 1);
-- T3.
m_byte(x"03"); m_ack_slot_listen;
report "T3 the first data byte is the pointer and the second is not" severity note;
ck("T3 pointer loaded", to_integer(unsigned(pointer)), 3);
ck("T3 index advanced", to_integer(data_index), 1);
ck("T3 no longer the pointer byte", b2i(rx_is_pointer), 0);
m_byte(x"9E"); m_ack_slot_listen;
ck("T3 the second byte is data", slice8(reg_flat, 3), 16#9E#);
ck("T3 index saturated", to_integer(data_index), 2);
-- T4.
do_reset;
m_start;
m_byte("0100001" & '0');
m_ack_slot_listen;
report "T4 another device's address opens no phase and enables no shifting"
severity note;
ck("T4 no phase", b2i(in_phase), 0);
ck("T4 rx disabled", b2i(rx_enable), 0);
ck("T4 no phase counted", to_integer(n_phases), 0);
-- T5.
do_reset;
m_start;
m_byte(ADDR & '0');
wait until falling_edge(clk); m_sda_low <= '0'; hp;
m_scl_release;
report "T5 rx_enable drops inside the acknowledge slot: the ninth bit is not data"
severity note;
ck("T5 the slot is active", b2i(ack_active), 1);
ck("T5 so shifting is disabled", b2i(rx_enable), 0);
m_scl_pull;
-- T6. THE HEADLINE.
do_reset;
m_start;
m_byte(ADDR & '0'); m_ack_slot_listen;
m_byte(x"05"); m_ack_slot_listen;
m_byte(x"7C"); m_ack_slot_listen;
ck("T6 written before the restart", slice8(reg_flat, 5), 16#7C#);
ck("T6 pointer before the restart", to_integer(unsigned(pointer)), 6);
m_restart;
report "T6 a repeated START clears the phase and KEEPS the pointer" severity note;
ck("T6 the phase is gone", b2i(in_phase), 0);
ck("T6 the direction is gone", b2i(dir_q), 0);
ck("T6 the byte index is gone", to_integer(data_index), 0);
ck("T6 the pointer SURVIVED", to_integer(unsigned(pointer)), 6);
ck("T6 and so did the data", slice8(reg_flat, 5), 16#7C#);
ck("T6 one restart counted", to_integer(n_restarts_txn), 1);
-- T7.
do_reset;
m_start;
m_byte(ADDR & '1');
ck("T7 no bit has been driven at the eighth bit", to_integer(n_bits_tx), 0);
m_ack_slot_listen;
report "T7 a read is started inside the address acknowledge slot, so the first bit is on time"
severity note;
ck("T7 in a read phase", b2i(in_phase), 1);
ck("T7 direction is read", b2i(dir_q), 1);
ck("T7 the first bit is already driven", to_integer(n_bits_tx), 1);
ck("T7 and the transmitter owns SDA", b2i(driving), 1);
-- T8.
do_reset;
m_start;
m_byte(ADDR & '0'); m_ack_slot_listen;
m_byte(x"00"); m_ack_slot_listen;
m_byte(x"C3"); m_ack_slot_listen;
m_restart;
m_byte(ADDR & '1'); m_ack_slot_listen;
for i in 0 to 7 loop m_bit('0'); end loop;
m_ack_slot_drive('1');
report "T8 an acknowledged read byte is followed by another; a NACK ends it"
severity note;
ck("T8 the master acknowledged", to_integer(n_m_ack), 1);
ck("T8 so sourcing continues", b2i(keep_sourcing), 1);
for i in 0 to 7 loop m_bit('0'); end loop;
m_ack_slot_drive('0');
ck("T8 the master NACKed", to_integer(n_m_nack), 1);
ck("T8 so sourcing stopped", b2i(keep_sourcing), 0);
ck("T8 two bytes were served", to_integer(n_bytes_tx), 2);
m_stop;
-- T9.
do_reset;
m_start;
m_byte(ADDR & '0');
wait until falling_edge(clk); stall_load <= '1';
wait until rising_edge(clk);
wait until falling_edge(clk); stall_load <= '0';
m_ack_slot_listen;
report "T9 the application asks for time, and the master BLOCKS until it is given"
severity note;
ck("T9 the slave is stretching", b2i(stretching), 1);
ck("T9 and is pulling SCL down", b2i(txn_scl_low), 1);
ck("T9 SCL is low", b2i(scl), 0);
ck("T9 the master has not waited yet", stretch_waits, 0);
m_byte(x"01");
ck("T9 the master had to wait for the line", stretch_waits, 1);
ck("T9 and the stretch is over", b2i(stretching), 0);
m_ack_slot_listen;
ck("T9 the transfer continued afterwards", to_integer(unsigned(pointer)), 1);
ck("T9 one stretch counted", to_integer(n_stretch), 1);
-- T10.
report "T10 across every test so far, SCL was never pulled down while it was high"
severity note;
ck("T10 no drive in a high phase", drive_while_high, 0);
-- T11.
do_reset;
m_start;
m_byte(ADDR & '0');
wait until falling_edge(clk); stall_load <= '1';
wait until rising_edge(clk);
wait until falling_edge(clk); stall_load <= '0';
m_ack_slot_listen;
ck("T11 stretching", b2i(stretching), 1);
m_byte(x"02");
m_ack_slot_listen;
m_stop;
report "T11 a stretch and a framing event never coexist: SCL cannot be low and high"
severity note;
ck("T11 no framing arrived during a stretch", framing_in_stretch, 0);
ck("T11 the phase closed on the STOP", b2i(in_phase), 0);
ck("T11 not stretching", b2i(stretching), 0);
ck("T11 SCL released", b2i(txn_scl_low), 0);
-- T12.
do_reset;
m_start;
m_byte(ADDR & '0'); m_ack_slot_listen;
m_byte(x"00"); m_ack_slot_listen;
for i in 0 to 3 loop m_byte(x"20"); m_ack_slot_listen; end loop;
report "T12 the byte index saturates: a later byte never becomes a pointer again"
severity note;
ck("T12 index pinned at two", to_integer(data_index), 2);
ck("T12 not a pointer byte", b2i(rx_is_pointer), 0);
-- T13.
do_reset;
m_start;
m_byte(ADDR & '0'); m_ack_slot_listen;
ck("T13 in a phase", b2i(in_phase), 1);
m_stop;
report "T13 a STOP ends the phase" severity note;
ck("T13 phase closed", b2i(in_phase), 0);
ck("T13 index cleared", to_integer(data_index), 0);
-- T14.
do_reset;
report "T14 reset clears the phase, the index and every counter" severity note;
ck("T14 no phase", b2i(in_phase), 0);
ck("T14 index zero", to_integer(data_index), 0);
ck("T14 phases zero", to_integer(n_phases), 0);
ck("T14 restarts zero", to_integer(n_restarts_txn), 0);
ck("T14 stretches zero", to_integer(n_stretch), 0);
-- T15. A NACKed read must END, even if the master keeps clocking.
do_reset;
m_start;
m_byte(ADDR & '0'); m_ack_slot_listen;
m_byte(x"00"); m_ack_slot_listen;
m_byte(x"FF"); m_ack_slot_listen;
m_restart;
m_byte(ADDR & '1'); m_ack_slot_listen;
for i in 0 to 7 loop m_bit('0'); end loop;
m_ack_slot_drive('0');
ck("T15 one byte was served", to_integer(n_bytes_tx), 1);
for i in 0 to 7 loop
m_bit('0');
if tx_sda_low = '1' then
report " FAIL T15 the slave drove SDA after a NACK" severity note;
err := err + 1;
end if;
end loop;
report "T15 a NACKed read ends even against a master that keeps clocking"
severity note;
ck("T15 still one byte", to_integer(n_bytes_tx), 1);
ck("T15 the transmitter is idle", b2i(driving), 0);
ck("T15 and SDA was left to the master", b2i(tx_sda_low), 0);
m_stop;
if n_sto > 0 then ck("T15 so the STOP happened", 1, 1);
else ck("T15 so the STOP happened", 0, 1); end if;
ck("T15 rx_is_pointer never asserted outside a phase", ptr_out_of_phase, 0);
if err = 0 then
report "=== i2c_slave_txn: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_slave_txn: " & integer'image(err) & " CHECK(S) FAILED ==="
severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;All three languages finish at the same instant:
i2c_slave_txn_tb.sv ALL CHECKS PASSED $finish at 75425000
i2c_slave_txn_tb.v ALL CHECKS PASSED $finish at 75425000
i2c_slave_txn_tb.vhd ALL CHECKS PASSED stopped at 75425 ns8. Mutation Testing
Two passes. The first produced six survivors, and they split three-and-three in a way that is worth more than the final score.
Pass one: six survivors, two different diagnoses
| # | Injected defect | Verdict | Diagnosis |
|---|---|---|---|
| — | stretch engaged at the rising edge | INVALID — did not elaborate | referenced a signal this block has no port for |
| M9 | receive path shifts while stretching | SURVIVED | redundant code — no rising edge exists to shift on |
| M10 | a NACK continues the read anyway | SURVIVED | missing test — nothing clocked after the NACK |
| M11 | direction taken live rather than latched | SURVIVED | equivalent — see below |
| M13 | pointer byte announced outside any phase | SURVIVED | missing check — the output was masked downstream |
| M14 | reset comes up stretching | SURVIVED | missing check — cleared before anyone looked |
| M15 | a read started on every acknowledge slot | SURVIVED | redundant code — the slot occurs once per read |
Three of the six were the testbench's fault and three were the design's — and in the design's case the fix was deletion, not a new test. §5 has the four removals and their proofs.
M11 is a genuine equivalent. dir_read is 18.4's level and dir_q is this block's latch of it, taken at the same instant from the same source. They can only differ if dir_read changes while in_phase is set — and dir_read changes only at addr_done, which requires eight bits after a START, which clears in_phase. So while the substitution matters, it cannot be reached. Discarded, not counted.
Pass two: sixteen valid mutants, sixteen killed
| # | Injected defect | Expected detection | Result |
|---|---|---|---|
| M1 | a read started at the end of the acknowledge slot | T7 | KILLED (6) |
| M2 | stretch engaged while SCL is still high | T10 | KILLED (2) |
| M3 | stretch engaged on any cycle of the slot | T9, T10 | KILLED (6) |
| M4 | the stretch is never released | T9 | KILLED (26) |
| M5 | framing does not close the phase | T6, T13 | KILLED (6) |
| M6 | a repeated START leaves the byte index behind | T6 | KILLED (2) |
| M7 | the byte index wraps instead of saturating | T12 | KILLED (2) |
| M8 | the receive path shifts during the acknowledge slot | T5 | KILLED (9) |
| M9 | a NACK continues the read anyway | T15 new | KILLED (9) |
| M10 | the pointer byte is announced outside any phase | observer new | KILLED (2) |
| M11 | reset comes up stretching | T1 new | KILLED (3) |
| M12 | a phase opens on any address byte, matched or not | T4 | KILLED (4) |
| M13 | the direction is not latched at all | T2, T5 | KILLED (11) |
| M14 | the byte index never advances | T3 | KILLED (11) |
| M15 | a stretch is announced but SCL is never pulled | T9 | KILLED (3) |
| M16 | the transmitter is never continued | T8 | KILLED (4) |
baseline: PASS (verified before injecting anything)
valid mutants: 16 killed: 16 survived: 0 equivalent: 1 invalid: 1
design lines deleted as unreachable: 4 (one register among them)
restored: PASSM4's twenty-six failures are the loudest number in this module, and they are not a compliment to the bench — a stretch that never releases hangs the master, so every check after it fails. M2's two failures are the informative ones: exactly the two an observer raised, at exactly the instant the rule forbids.
9. Verification Connection — Properties of a Wait State
// Not synthesisable. Icarus rejects SVA, so these document the intent the bench
// checks procedurally, and each one is a rule from §3.1.9 or §3.1.10.
// A stretch never begins while SCL is high.
property stretch_begins_low;
@(posedge clk) disable iff (!rst_n)
$rose(scl_drive_low) |-> !scl_q;
endproperty
// A stretch and a framing event never coexist -- the property that replaced
// four lines of unreachable RTL.
property no_framing_during_stretch;
@(posedge clk) disable iff (!rst_n)
stretching |-> !(start_pulse || restart_pulse || stop_pulse);
endproperty
// A device held in reset holds nothing.
property reset_releases_the_bus;
@(posedge clk) !rst_n |-> !scl_drive_low;
endproperty
// The pointer byte is only ever announced inside a phase.
property pointer_only_in_phase;
@(posedge clk) disable iff (!rst_n)
rx_is_pointer |-> in_phase;
endpropertyThe last two exist because mutants survived. That is the honest way to build an assertion set: each property is a defect somebody actually injected and the bench could not see.
10. FPGA and ASIC Implications
The wait state is the only part of this block with a bus-level consequence, and it is a single flop driving a pad's pull-down enable. It must reach the pad without combinational gating that could glitch, because a glitch on SCL is a clock pulse to every device on the bus. Register it at the boundary and drive the pad from that register — Module 19 owns the pad itself.
Everything else is bookkeeping, and there is no arithmetic wider than a four-bit saturating index. The design's cost is the three diagnostic counters, which CNT_W exists to remove.
stall_req is a level from the application, and it crosses no clock domain here — this block and the application share clk. If the application genuinely runs on another clock, the crossing needs a synchroniser on stall_req, and getting it wrong makes the slave stretch for one clock or not at all. That is Module 19's territory, and it is the one place in a slave where a missing synchroniser produces a bus-level failure rather than a local one.
11. Debugging — The Read Whose First Bit Is Always Wrong
Symptom. Writes work. Reads return a byte that is the expected value shifted left by one, with a one in the least significant bit — or, on a different master, the expected value with its top bit replaced by a one. Single-byte and multi-byte reads are equally affected. A logic analyser shows nine data pulses where there should be eight.
What it is not. Not the register file, whose contents are provably right from the write side. Not the shift order, which would produce a reversal rather than a shift. Not the address decode.
What it is. The transmitter was started at the end of the acknowledge slot instead of its beginning, so it missed the terminating fall and placed bit 7 one bit-time late. The master's first sample therefore reads the released line — a one — and every subsequent sample reads the bit before the one it wanted.
Why the two symptoms. It depends on whether the master counts nine pulses or eight. A master that clocks exactly eight and then reads the ninth as its own acknowledge slot sees the value shifted; one that resynchronises sees the top bit replaced.
12. Common Misconceptions
"A repeated START resets the slave, so the register pointer is lost." It resets the bus logic — this block — and nothing else. The pointer is application state and survives, which is the only reason a combined transfer works.
"Clock stretching means the slave drives SCL." It means the slave pulls SCL low. There is no drive-high; the master owns every rising edge.
"A slave can start stretching whenever it needs to." Only in a low phase. Pulling SCL down while it is high truncates the sampling window for every device on the bus.
"The slave should begin its read data after the address acknowledge finishes." It must be loaded before that, so the acknowledge slot's own terminating fall can carry the first bit. §3.
"A NACK ends a read because the slave sets a flag." It ends because no continue is issued. This design has no read-ended flag at all — one was written, found to be unreachable, and deleted.
"A surviving mutant always means a missing test." Three of six survivors here meant the opposite: a line of RTL that no test could distinguish from its absence, because the case it defended against cannot occur. §5.
13. Reason It Through
Of each register this block holds, ask: would a combined transfer still work if a START cleared it? What does the answer tell you?
All three answer yes, which is why all three belong here. A register that answered no would be application state in the wrong module. §1.
Why must the transmitter be started inside the acknowledge slot rather than at its end?
Because 18.7 loads on the first fall after being started, and the fall that must carry bit 7 is the one terminating the ninth pulse. Starting at the end leaves it one bit-time late. §3.
A stretch engages one cycle after the fall that ends the acknowledge slot. Why is that safe, and what would make it unsafe?
SCL is low for the whole low phase, so the pull-down lands inside it. It would be unsafe if the condition were the slot's level rather than its closing fall, which could assert during the high phase — mutation M3. §4.
Four lines were deleted rather than tested. What is the obligation that justifies a deletion?
A proof that the condition under which the line changes an outcome is unsatisfiable. Each of the four has a one-sentence proof resting on a property established elsewhere. §5.
Two mutants were wrong and invisible because a consumer masked them. What does that say about how to judge an output?
That an output must be correct in itself, not merely harmless in the current integration. Both were caught by per-cycle observers on the output, not by new scenarios. §8.
Why can a master model with a fixed clock schedule not test clock stretching?
Because it would drive SCL high against the slave's pull-down, which is electrically impossible and makes the stretch unobservable. The master must release and wait. §7.
14. Understanding Check
15. Summary
Protocol state is cleared by a START; application state is not — and the boundary between them is a module boundary, which is what makes the rule enforceable rather than remembered.
Everything this block holds is deliberately disposable. The test of whether a register belongs here is whether a combined transfer would still work if a START cleared it.
A repeated START clears the phase and keeps the pointer, and a bench must assert both halves: one without the other passes a design that is broken in the opposite direction.
A read is started inside the address acknowledge slot, not at its end, because 18.7 loads on the next falling edge and the only one available is the slot's own. That defect lives between two correct modules, which is why the bench had to be an integration bench.
Clock stretching is a pull-down that begins in a low phase. One assignment can set it, and scl_fall is in that assignment's condition — so the rule is structural, not remembered.
The master model must be able to lose. A master that clocks on a fixed schedule cannot observe a stretch at all, so the bench's master releases SCL and counts the times it had to wait.
Three mutation survivors meant the testbench was incomplete; three meant the design contained code for cases that cannot occur. The second diagnosis demands deletion, and discharging it is a proof obligation rather than a judgement.
Four lines went, one of them a whole register — and the arguments that justified their removal became assertions, which is cheaper to maintain than the code and says what the code only implied.
Two mutants were wrong and invisible because a consumer masked them. An output that is only right because something downstream ignores it is not right, and 18.11 is the integration that will stop ignoring it.
Sixteen valid mutants, sixteen killed, with one equivalent and one non-elaborating injection removed from the denominator — and the informative failure counts are the small ones.
16. What Comes Next
Every block exists and every block is verified in three languages. What does not exist is one module you can instantiate.
Chapter 18.11 builds it: the wiring made explicit as RTL rather than as a testbench, the arbitration between the two blocks that can drive SDA, what reset must mean for a device attached to a bus it does not own, and what a target does when the bus does something the protocol does not allow.
It is also where the integration tests stop proving connectivity and start proving behaviour — because a wiring diagram that compiles is not a slave.
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 I²C Stretching Mechanism — Holding SCL Low
Stretching needed no new mechanism: the specification already described it for multi-master synchronization. One sentence decides whether a master survives it — and getting it wrong collapses the high phase on the bit a stretch ended.
- Related topic
The SCL Timing Generator — Phases, Strobes and the Readback Rule
Where Table 10's microseconds become counts of system-clock cycles. Derives the period budget that must include rise and fall time, shows why rounding down is always illegal and rounding up always legal, and builds a generator that leaves its low phase only when the line actually reads back high — which implements clock stretching and clock synchronization with no extra logic.
- Related topic
START and STOP Generation — The Framing Sequencer
The edges the bit engine cannot produce, and why that is structural rather than stylistic. Builds the four framing sequences from their Table 10 intervals, shows why every SCL release inside them must wait for the readback or the START is not a START at all, and finds a defect that no bus-level check could have caught.
