I²C · Module 20
The Target Responder — Modeling a Real Device
A responder judged by the awkward legal behaviour it can produce on demand — refusal, mid-transfer NACK, clock stretching — with a policy deliberately too thin to share the target's bugs. Includes why 'not acknowledging' and 'the transfer is over' are different states, and a mutation that survived on a parameter value.
A responder model exists so that the driver has something to talk to. That framing produces a bad responder, because it suggests the goal is for transfers to succeed.
The goal is the opposite. A responder is valuable in proportion to the range of legal but inconvenient behaviour it can produce on demand — refusing an address it is perfectly capable of answering, stretching when nothing requires it, acknowledging four bytes and then refusing the fifth. Those are the behaviours a real part exhibits and that a controller has to survive, and a responder that always cooperates verifies a controller only against the easiest device it will ever meet.
1. Thin Policy, Not a Second Design
The temptation is to make the responder a good model of Module 18's target: a register file, a pointer, a read-only mask, the six documented decisions. It would be more realistic and it would be a serious mistake.
So the policy is deliberately thin: four parameters, none of which is a register map.
| parameter | what it decides | why a controller cares |
|---|---|---|
ACK_ADDR | answer our address, or refuse it | a controller must handle an absent or busy device |
NACK_AFTER | acknowledge this many data bytes, then stop | mid-transfer refusal is legal and common |
STRETCH_CLKS | hold SCL after an acknowledge | the controller must wait rather than count |
READ_BASE | the first byte of a read, incrementing | read data has to come from somewhere |
Nothing in that list is a claim about Module 18. The responder answers at an address and produces bytes; whether those bytes are the right ones is a question about a device contract, and it is answered by Chapter 20.8's reference model, which is a separate component with a separate job.
This is the division that keeps the two honest. The responder produces traffic; the reference model produces expectations. A single component doing both would be predicting its own output.
2. Two Different Reasons Not to Drive the Ninth Slot
The responder's state machine has six states, and two of them look redundant until you try to remove one.
R_IDLE waiting for a START
R_ADDR shifting in the address byte
R_ACK the ninth slot, and WE own it
R_WDATA shifting in a data byte
R_RDATA shifting OUT a data byte
R_CACK the ninth slot, and the CONTROLLER owns itR_ACK and R_CACK are both "the ninth bit slot". They are not the same state, because the question "who drives SDA here" has different answers, and getting that wrong produced the most instructive bug in this chapter.
The same distinction has a consequence for bit indexing that is worth showing, because it is the kind of detail that produces a single wrong bit and a long debugging session:
// ONE BIT INDEX, TWO ENTRY PATHS. `bitcnt` names the NEXT bit to
// drive, as `tx[7 - bitcnt]`, and the byte is finished when it reaches
// 8. That single convention makes both ways into this state work:
//
// from R_ACK the exit IS the fall that closes our acknowledge slot,
// so bit 7 is driven there and bitcnt arrives as 1;
// from R_CACK the decision is made at a RISE, so nothing is driven
// yet and bitcnt arrives as 0 -- the fall that closes
// the controller's slot drives bit 7 here.
//
// The first version of this file drove bit 7 only on the R_ACK path, so
// every byte after the first was missing its top bit and shifted. The
// controller read 0x83 where 0xC1 was expected -- a plausible-looking
// wrong value, which is the worst kind.The first version shifted a register instead, with a separate initialisation per path. The second byte of every read came back missing its top bit — 0x83 where 0xC1 was expected — because the two entry paths needed different starting counts and only one of them had been considered.
3. A Concatenation Cannot Be Part-Selected
A small language note, recorded because it cost real time and the error message points elsewhere.
// The byte being assembled, including the bit arriving this cycle. A named wire
// because a CONCATENATION CANNOT BE PART-SELECTED: `{shreg[6:0], sda_in}[7:1]` is
// not legal, exactly as Chapter 18.4's own comment records. The first version of
// this file wrote it that way and would not compile.
wire [7:0] shreg_next = {shreg[6:0], sda_in};The intent was to test the next shift-register value while also assigning it. Written inline as a part-select of a concatenation, Icarus rejects it with a syntax error whose position is the concatenation rather than the selection. Naming the intermediate value fixes it and reads better.
4. The Responder
// -----------------------------------------------------------------------------
// i2c_resp_bfm.sv
// A target responder: behave like a real device, and misbehave on request.
//
// WHY A RESPONDER IS NOT A SECOND DUT. Module 18's target is a synthesisable design
// whose job is to be correct. This is a verification component whose job is to be
// CONTROLLABLE -- it must be able to NACK an address it would normally answer, stretch
// when asked, and return data the environment chose, because those are the stimuli a
// controller has to be tested against.
//
// The distinction shows up in what each one is allowed to do. The DUT's acknowledge
// policy is fixed by its specification. This BFM's is a PARAMETER, and Chapter 20.6
// argues that the moment a responder acquires its own register semantics, pointer
// rules and error handling, it has become a second implementation with a second set of
// bugs -- and nobody knows which one is wrong when they disagree.
//
// SO THE POLICY IS DELIBERATELY THIN:
// ack_addr answer this address, or refuse it
// nack_after NACK the Nth data byte of a write (0 = never)
// stretch_clks hold SCL for this many clocks after an acknowledge (0 = never)
// read_base read data is read_base + byte index, so a controller can tell
// which byte it got without the BFM needing a register model
//
// `read_base + index` is the whole of its "memory". A real register file would make it
// a device model, which is Chapter 20.8's predictor -- a different component with a
// different job.
//
// OPEN-DRAIN DISCIPLINE: two drive-low outputs, no way to drive HIGH.
//
// BOUNDED: the responder is driven entirely by observed bus edges. It waits for
// nothing, so it cannot hang; a silent bus simply leaves it idle.
// -----------------------------------------------------------------------------
module i2c_resp_bfm #(
parameter [6:0] MY_ADDR = 7'h50,
// POLICY -- what makes this a verification component rather than a design.
parameter bit ACK_ADDR = 1'b1, // 0 = refuse our own address, on purpose
parameter int NACK_AFTER = 0, // NACK the Nth write data byte (0 = never)
parameter int STRETCH_CLKS = 0, // hold SCL this long after an ACK
parameter [7:0] READ_BASE = 8'hC0 // read data = READ_BASE + byte index
) (
input logic clk,
input logic rst_n,
input logic scl_in, // RESOLVED
input logic sda_in, // RESOLVED
output logic scl_drive_low,
output logic sda_drive_low,
// ---- observation, for a bench to assert against -------------------------
output logic selected,
output logic [7:0] last_write,
output logic [15:0] n_addr_seen,
output logic [15:0] n_acked,
output logic [15:0] n_nacked,
output logic [15:0] n_stretches
);
// Its own view of the lines -- one delay stage, like the monitor's. A responder
// reads the RESOLVED bus for the same reason: what it must react to is what the
// wire became, not what anybody intended.
logic scl_d, sda_d;
wire scl_rise = scl_in & ~scl_d;
wire scl_fall = ~scl_in & scl_d;
wire sda_fall = ~sda_in & sda_d;
wire sda_rise = sda_in & ~sda_d;
wire start_now = sda_fall & scl_in & scl_d;
wire stop_now = sda_rise & scl_in & scl_d;
typedef enum logic [2:0] {
R_IDLE, // no transfer open
R_ADDR, // shifting in the address byte
R_ACK, // driving OUR acknowledge, in a slot we own
R_WDATA, // shifting in a write data byte
R_RDATA, // shifting out a read data byte
R_CACK // waiting out the CONTROLLER's acknowledge, in a slot it owns
} state_t;
// R_ACK AND R_CACK ARE DIFFERENT STATES ON PURPOSE, and collapsing them is the
// mistake the first version of this file made. Both are ninth slots, but the
// OWNERSHIP differs: in R_ACK this device drives the bit, and in R_CACK the
// controller does while this device must release. Treating "I am not driving an
// acknowledge" as "the transfer is over" ended every read after one byte -- the
// responder went idle and the controller read 0xFF from a released bus.
// Chapter 18.8 makes the same distinction from the target's side.
state_t st;
logic [3:0] bitcnt;
logic [7:0] shreg;
logic dir_read;
logic [2:0] dbyte; // which data byte of this phase
logic will_ack;
logic [7:0] tx;
logic [15:0] stretch_cnt;
// The byte being assembled, including the bit arriving this cycle. A named wire
// because a CONCATENATION CANNOT BE PART-SELECTED: `{shreg[6:0], sda_in}[7:1]` is
// not legal, exactly as Chapter 18.4's own comment records. The first version of
// this file wrote it that way and would not compile.
wire [7:0] shreg_next = {shreg[6:0], sda_in};
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
scl_d <= 1'b1; sda_d <= 1'b1;
scl_drive_low <= 1'b0; sda_drive_low <= 1'b0;
st <= R_IDLE; bitcnt <= 4'd0; shreg <= 8'h00;
dir_read <= 1'b0; dbyte <= 3'd0; will_ack <= 1'b0; tx <= 8'h00;
selected <= 1'b0; last_write <= 8'h00;
n_addr_seen <= 16'd0; n_acked <= 16'd0; n_nacked <= 16'd0;
n_stretches <= 16'd0; stretch_cnt <= 16'd0;
end else begin
scl_d <= scl_in; sda_d <= sda_in;
// ---- the stretch, if policy asked for one --------------------------
// Held for a fixed count, then released. A responder that stretched forever
// would be a fault injector, which is Chapter 20.9's component.
if (stretch_cnt != 16'd0) begin
stretch_cnt <= stretch_cnt - 16'd1;
scl_drive_low <= 1'b1;
if (stretch_cnt == 16'd1) scl_drive_low <= 1'b0;
end
// ---- framing resets everything ------------------------------------
if (start_now) begin
st <= R_ADDR; bitcnt <= 4'd0; shreg <= 8'h00;
dbyte <= 3'd0; selected <= 1'b0;
sda_drive_low <= 1'b0;
end else if (stop_now) begin
st <= R_IDLE; selected <= 1'b0; bitcnt <= 4'd0;
sda_drive_low <= 1'b0;
end else begin
case (st)
R_ADDR: if (scl_rise) begin
shreg <= shreg_next;
if (bitcnt == 4'd7) begin
// The address is complete. Policy decides the answer.
n_addr_seen <= n_addr_seen + 16'd1;
dir_read <= sda_in;
will_ack <= ACK_ADDR && (shreg_next[7:1] == MY_ADDR);
selected <= (shreg_next[7:1] == MY_ADDR);
tx <= READ_BASE;
bitcnt <= 4'd0;
st <= R_ACK;
end else bitcnt <= bitcnt + 4'd1;
end
// The acknowledge is driven from the FALLING edge that opens the ninth
// slot and released at the next fall -- Chapter 18.5's window, obeyed
// by the BFM because a responder that acknowledged at the wrong instant
// would fail a correct controller.
R_ACK: begin
if (scl_fall) begin
if (sda_drive_low || !will_ack) begin
// the slot is over: release and move on
sda_drive_low <= 1'b0;
if (will_ack) begin
n_acked <= n_acked + 16'd1;
if (STRETCH_CLKS != 0) begin
stretch_cnt <= STRETCH_CLKS[15:0];
n_stretches <= n_stretches + 16'd1;
end
end else n_nacked <= n_nacked + 16'd1;
bitcnt <= 4'd0;
if (!will_ack) st <= R_IDLE;
else if (dir_read) st <= R_RDATA;
else st <= R_WDATA;
// A read's first bit is driven as the acknowledge slot closes,
// so it is stable before the controller's next rising edge.
if (will_ack && dir_read) begin
// This fall closes our acknowledge slot, so bit 7 goes out
// now and bitcnt advances to 1.
sda_drive_low <= ~tx[7];
bitcnt <= 4'd1;
end
end else begin
sda_drive_low <= 1'b1; // pull LOW for the ACK
end
end
end
R_WDATA: if (scl_rise) begin
shreg <= shreg_next;
if (bitcnt == 4'd7) begin
last_write <= shreg_next;
dbyte <= dbyte + 3'd1;
// POLICY: NACK the Nth data byte, on purpose.
will_ack <= !(NACK_AFTER != 0 && (dbyte + 3'd1) == NACK_AFTER[2:0]);
bitcnt <= 4'd0;
st <= R_ACK;
end else bitcnt <= bitcnt + 4'd1;
end
// ONE BIT INDEX, TWO ENTRY PATHS. `bitcnt` names the NEXT bit to
// drive, as `tx[7 - bitcnt]`, and the byte is finished when it reaches
// 8. That single convention makes both ways into this state work:
//
// from R_ACK the exit IS the fall that closes our acknowledge slot,
// so bit 7 is driven there and bitcnt arrives as 1;
// from R_CACK the decision is made at a RISE, so nothing is driven
// yet and bitcnt arrives as 0 -- the fall that closes
// the controller's slot drives bit 7 here.
//
// The first version of this file drove bit 7 only on the R_ACK path, so
// every byte after the first was missing its top bit and shifted. The
// controller read 0x83 where 0xC1 was expected -- a plausible-looking
// wrong value, which is the worst kind.
R_RDATA: if (scl_fall) begin
if (bitcnt == 4'd8) begin
sda_drive_low <= 1'b0; // RELEASE: the ninth slot is theirs
bitcnt <= 4'd0;
st <= R_CACK;
end else begin
sda_drive_low <= ~tx[7 - bitcnt[2:0]];
bitcnt <= bitcnt + 4'd1;
end
end
// The controller's acknowledge slot. Sample it at the RISE -- the only
// instant it is guaranteed stable -- and let it decide whether to
// source another byte. Chapter 18.8's contract, from the other side.
R_CACK: if (scl_rise) begin
dbyte <= dbyte + 3'd1;
if (!sda_in) begin
// ACK: the controller wants another byte. Nothing is driven at a
// RISE -- that would change SDA while SCL is high, which is
// framing, not data. bitcnt = 0 means R_RDATA drives bit 7 at the
// next fall.
tx <= READ_BASE + {5'd0, dbyte} + 8'd1;
bitcnt <= 4'd0;
st <= R_RDATA;
end else begin
// NACK: the controller is done. Release and wait for framing.
bitcnt <= 4'd0;
st <= R_IDLE;
end
end
default: st <= R_IDLE;
endcase
end
end
end
endmoduleNote that the responder, like every other participant, only ever pulls low or releases. scl_drive_low and sda_drive_low are its entire output onto the bus. Stretching is implemented by holding scl_drive_low for a counted number of clocks and then releasing — there is no other way to do it, which is the point of 19.1.
5. The Bench: Two Actives and an Independent Witness
The responder is exercised against the 20.5 driver with no design present at all. That arrangement is worth defending, because "a testbench testing a testbench" sounds like a waste of effort.
It is not, for two reasons. First, both components are about to be used to make claims about a real target, and a defect in either produces a claim that is wrong in a way nobody will attribute correctly. Second, the arrangement admits a third participant that has no stake in either: the monitor of 20.7, reading the resolved bus, reconstructing the traffic independently.
// -----------------------------------------------------------------------------
// i2c_bfm_pair_tb.sv
// The two BFMs against each other, with the monitor watching. No DUT.
//
// WHY NO DUT. Both BFMs are verification components that will later be trusted to
// stimulate and respond to real designs. If they are wrong, every result obtained with
// them is wrong -- and a failure would be attributed to the DUT. So they are tested
// against each other first, with the monitor of Chapter 20.7 as an independent witness
// to what actually reached the wire.
//
// THREE INDEPENDENT VIEWS OF EVERY TRANSFER, and they must agree:
// the controller's observation (obs_addr_acked, obs_r0 ...)
// the responder's observation (n_acked, last_write ...)
// the monitor's reconstruction (byte values, acknowledges, transaction count)
//
// Two agreeing views could share an assumption. Three, produced by components with no
// shared logic, is materially stronger -- and where they disagree, the disagreement
// names the component that is wrong.
//
// Every wait is bounded; a watchdog ends the run if a BFM deadlocks.
// -----------------------------------------------------------------------------
`timescale 1ns/1ps
module i2c_bfm_pair_tb;
localparam int HALF = 16;
localparam [6:0] ADDR = 7'h50;
localparam [6:0] OTHER = 7'h21;
localparam [6:0] STRETCH_ADDR = 7'h33;
logic clk = 1'b0, rst_n = 1'b0;
// ---- the bus: controller BFM, responder BFM, and a STRETCHING responder ----
//
// The third device is the same responder module with one parameter changed. Clock
// stretching cannot be tested by a responder that never stretches, and it cannot be
// switched on at run time because the hold length is an elaboration-time parameter --
// so the stretching case is a second instance at its own address. It answers to
// STRETCH_ADDR and is silent for every transfer aimed elsewhere, which means T1 to T4
// below are unaffected by its presence and T5 is the only test that involves it.
logic c_scl_low, c_sda_low, r_scl_low, r_sda_low, s_scl_low, s_sda_low;
wire scl, sda;
wire [2:0] scl_in, sda_in, scl_rbl, sda_rbl;
wire [7:0] scl_holders, sda_holders;
i2c_line_model #(.N_DEV(3)) bus (
.scl_drive_low({s_scl_low, r_scl_low, c_scl_low}),
.sda_drive_low({s_sda_low, r_sda_low, c_sda_low}),
.scl(scl), .sda(sda), .scl_in(scl_in), .sda_in(sda_in),
.scl_released_but_low(scl_rbl), .sda_released_but_low(sda_rbl),
.scl_holders(scl_holders), .sda_holders(sda_holders));
// ---- the controller BFM (Chapter 20.5) -----------------------------------
logic req = 1'b0, req_read = 1'b0, req_restart = 1'b0;
logic [6:0] req_addr = ADDR;
logic [2:0] req_len = 3'd0;
logic [7:0] req_d0 = 8'h00, req_d1 = 8'h00, req_d2 = 8'h00;
wire [7:0] c_donecnt;
wire c_aacked, c_stretched, c_timeout, c_busy;
integer dc_before;
integer nb_before, nack_before;
wire [2:0] c_nacked;
wire [7:0] c_r0, c_r1, c_r2;
i2c_ctrl_bfm #(.HALF(HALF), .STRETCH_TIMEOUT(20000)) ctrl (
.clk(clk), .rst_n(rst_n),
.scl_drive_low(c_scl_low), .sda_drive_low(c_sda_low),
.scl_in(scl), .sda_in(sda),
.req(req), .req_addr(req_addr), .req_read(req_read), .req_len(req_len),
.req_d0(req_d0), .req_d1(req_d1), .req_d2(req_d2), .req_restart(req_restart), .req_hold(1'b0),
.done_count(c_donecnt), .obs_addr_acked(c_aacked), .obs_n_acked(c_nacked),
.obs_r0(c_r0), .obs_r1(c_r1), .obs_r2(c_r2),
.obs_stretched(c_stretched), .obs_timeout(c_timeout), .busy(c_busy));
// ---- the responder BFM (Chapter 20.6) ------------------------------------
wire r_selected;
wire [7:0] r_lastwr;
wire [15:0] r_naddr, r_nack, r_nnack, r_nstr;
// READ_BASE = 0x00 is chosen, not arbitrary: it makes the byte after the last one
// read have bit 7 = 0. A responder that mistook the controller's NACK for an ACK
// would then PULL SDA LOW while the controller frames a STOP, and the STOP could
// not happen. At READ_BASE = 0xC0 every byte's top bit was 1, so a broken responder
// released the line and nothing was observable -- mutation R06 survived on a
// parameter value rather than on a correct design.
i2c_resp_bfm #(.MY_ADDR(ADDR), .ACK_ADDR(1'b1), .NACK_AFTER(0),
.STRETCH_CLKS(0), .READ_BASE(8'h00)) resp (
.clk(clk), .rst_n(rst_n),
.scl_in(scl), .sda_in(sda),
.scl_drive_low(r_scl_low), .sda_drive_low(r_sda_low),
.selected(r_selected), .last_write(r_lastwr),
.n_addr_seen(r_naddr), .n_acked(r_nack), .n_nacked(r_nnack),
.n_stretches(r_nstr));
// ---- the same responder, instructed to be BUSY (Chapter 20.6) -------------
// STRETCH_CLKS = 40 is longer than one half-period at HALF = 16, so the hold is
// unmistakably a stretch rather than an artefact of bit timing, and it is far shorter
// than the controller's STRETCH_TIMEOUT so a correct driver waits it out rather than
// giving up. Both inequalities matter: with the first one broken the test proves
// nothing, and with the second one broken it proves the opposite of what it claims.
wire s_selected;
wire [7:0] s_lastwr;
wire [15:0] s_naddr, s_nack, s_nnack, s_nstr;
i2c_resp_bfm #(.MY_ADDR(STRETCH_ADDR), .ACK_ADDR(1'b1), .NACK_AFTER(0),
.STRETCH_CLKS(40), .READ_BASE(8'h00)) sresp (
.clk(clk), .rst_n(rst_n),
.scl_in(scl), .sda_in(sda),
.scl_drive_low(s_scl_low), .sda_drive_low(s_sda_low),
.selected(s_selected), .last_write(s_lastwr),
.n_addr_seen(s_naddr), .n_acked(s_nack), .n_nacked(s_nnack),
.n_stretches(s_nstr));
// ---- the monitor (Chapter 20.7): the independent witness ------------------
wire m_start, m_restart, m_stop, m_bvalid, m_backed, m_bisaddr;
wire [7:0] m_bdata;
wire m_active, m_read, m_aacked, m_done, m_rs;
wire [6:0] m_addr;
wire [3:0] m_ndata;
wire [15:0] m_ntxn, m_nbytes, m_nnacks;
i2c_mon #(.MAX_BYTES(8)) mon (
.clk(clk), .rst_n(rst_n), .scl(scl), .sda(sda),
.saw_start(m_start), .saw_restart(m_restart), .saw_stop(m_stop),
.byte_valid(m_bvalid), .byte_data(m_bdata), .byte_acked(m_backed),
.byte_is_addr(m_bisaddr),
.txn_active(m_active), .txn_addr(m_addr), .txn_read(m_read),
.txn_addr_acked(m_aacked), .txn_n_data(m_ndata),
.txn_done(m_done), .txn_ended_by_restart(m_rs),
.n_txns(m_ntxn), .n_bytes(m_nbytes), .n_nacks(m_nnacks));
integer errors = 0, n;
integer nb = 0;
logic [7:0] cap [0:15];
logic cap_ack [0:15];
always @(posedge clk) if (rst_n && m_bvalid && nb < 16) begin
cap[nb] <= m_bdata; cap_ack[nb] <= m_backed; nb <= nb + 1;
end
// MUST STAY 0: cycles in which BOTH participants pull SDA. Two LOWs are legal on
// this bus, so this is not an error detector -- it is here because a BFM that
// drove during another's acknowledge slot would show up as a sustained overlap.
integer both_pull = 0;
always @(posedge clk) if (rst_n && c_sda_low && r_sda_low) both_pull <= both_pull + 1;
always #5 clk = ~clk;
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk); rst_n = 1'b0; req = 1'b0;
step; step; step;
@(negedge clk); rst_n = 1'b1;
for (n = 0; n < 40; n = n + 1) step;
nb = 0; both_pull = 0;
end
endtask
// Fire one transaction and wait for the driver to report done -- BOUNDED, so a
// deadlocked BFM fails rather than hanging.
task run_txn;
begin
dc_before = c_donecnt;
@(negedge clk); req = 1'b1;
@(negedge clk); req = 1'b0;
n = 0;
// Wait for the COUNT to change, not for a pulse: a one-cycle `done` polled on
// a clock edge can be missed, and the run then looks like a deadlock.
while (c_donecnt == dc_before && n < 400000) begin @(posedge clk); n = n + 1; end
if (c_donecnt == dc_before) begin
$display(" FAIL the controller BFM never completed a transaction");
errors = errors + 1;
end
for (n = 0; n < 8; n = n + 1) step;
end
endtask
task ck (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
#6000000;
$display(" FAIL watchdog: the BFM pair did not finish");
$display("=== i2c_bfm_pair: 1 CHECK(S) FAILED ===");
$finish;
end
initial begin
$display("=== i2c_bfm_pair: two BFMs, one monitor, three independent views ===");
// ----------------------------------------------------------------
// T1. A WRITE, AGREED BY ALL THREE VIEWS. The controller says the address was
// acknowledged; the responder says it acknowledged an address; the monitor
// says an acknowledged address byte appeared on the wire. Three components
// with no shared logic, one conclusion.
// ----------------------------------------------------------------
do_reset;
req_addr = ADDR; req_read = 1'b0; req_len = 3'd2;
req_d0 = 8'h11; req_d1 = 8'h22; req_restart = 1'b0;
run_txn;
$display("T1 a write: controller, responder and monitor all agree");
ck("T1 controller saw the address acked", c_aacked, 1);
ck("T1 controller saw two data acks", c_nacked, 2);
ck("T1 responder saw one address", r_naddr, 1);
ck("T1 responder acknowledged three bytes", r_nack, 3);
ck("T1 responder's last write byte", r_lastwr, 8'h22);
ck("T1 monitor reconstructed three bytes", nb, 3);
ck("T1 monitor: the address byte", cap[0], {ADDR, 1'b0});
ck("T1 monitor: first data byte", cap[1], 8'h11);
ck("T1 monitor: second data byte", cap[2], 8'h22);
ck("T1 monitor saw no NACK", m_nnacks, 0);
ck("T1 one transaction on the wire", m_ntxn, 1);
ck("T1 the controller did not report a stretch", c_stretched, 0);
ck("T1 and did not time out", c_timeout, 0);
// ----------------------------------------------------------------
// T2. A READ, AND THE DATA CAME FROM THE RESPONDER. The controller's observed
// bytes must match the responder's policy (READ_BASE + index). The monitor
// independently confirms the same bytes were on the wire -- which is what
// distinguishes "the controller decoded correctly" from "the controller
// invented plausible data".
// ----------------------------------------------------------------
do_reset;
req_addr = ADDR; req_read = 1'b1; req_len = 3'd2;
run_txn;
$display("T2 a read: the bytes the controller got are the bytes on the wire");
ck("T2 the address was acked", c_aacked, 1);
ck("T2 first byte is READ_BASE", c_r0, 8'h00);
ck("T2 second byte is READ_BASE+1", c_r1, 8'h01);
ck("T2 the monitor saw the same first byte", cap[1], 8'h00);
ck("T2 and the same second byte", cap[2], 8'h01);
ck("T2 the monitor knows it was a read", m_read, 1);
// The byte COUNT and the NACK matter as much as the values. A controller that
// never NACKed the last byte, or a responder that read a NACK as an ACK, would
// leave the pair sourcing bytes nobody asked for -- and a responder still
// driving SDA when the controller tries to frame a STOP prevents the line from
// rising at all. Mutations C06, R06 and R07 all survived until these three
// checks existed.
ck("T2 exactly three bytes on the wire (address + two data)", nb, 3);
ck("T2 the controller NACKed the last byte", m_nnacks, 1);
ck("T2 the monitor saw the transaction close", m_ntxn, 1);
ck("T2 and the bus returned to idle", scl & sda, 1);
// ----------------------------------------------------------------
// T2b. A ONE-BIT NEAR-MISS ADDRESS IS REFUSED BY THE RESPONDER.
//
// T3 below uses 0x21, which differs from 0x50 in four bits -- so a
// comparator that had dropped any ONE of them would still reject it, and
// mutation R08 survived. Modules 18.4 and 19.9 established the rule: only
// an address exactly one bit away shows that a specific bit participates.
// ----------------------------------------------------------------
do_reset;
req_addr = ADDR ^ 7'h40; req_read = 1'b0; req_len = 3'd1; req_d0 = 8'h5A;
run_txn;
$display("T2b an address one bit from ours is refused by the responder");
ck("T2b the controller saw no ack", c_aacked, 0);
ck("T2b the responder was not selected", r_selected, 0);
ck("T2b the responder counted a refusal", r_nnack > 0, 1);
ck("T2b the monitor's address byte", cap[0], {(ADDR ^ 7'h40), 1'b0});
// ----------------------------------------------------------------
// T3. THE RESPONDER REFUSES A FOREIGN ADDRESS, AND ALL THREE AGREE. The
// controller learns its transfer failed; the responder counts a NACK; the
// monitor sees the ninth bit high.
// ----------------------------------------------------------------
do_reset;
req_addr = OTHER; req_read = 1'b0; req_len = 3'd1; req_d0 = 8'h55;
run_txn;
$display("T3 a foreign address: refused, and the refusal is visible to all three");
ck("T3 the controller saw no ack", c_aacked, 0);
ck("T3 the responder was not selected", r_selected, 0);
ck("T3 the monitor saw a NACK", m_nnacks > 0, 1);
ck("T3 the monitor's address byte", cap[0], {OTHER, 1'b0});
// ----------------------------------------------------------------
// T4. NEITHER BFM EVER DRIVES HIGH, AND SIMULTANEOUS LOWS STAY RARE.
// Neither module has any way to drive a line high -- both outputs are
// drive-low intents -- so this is structural. What IS worth counting is
// sustained overlap: a BFM that drove during the other's acknowledge slot
// would show up here even though both were only ever pulling LOW.
// ----------------------------------------------------------------
$display("T4 both BFMs pull LOW or release; neither ever drives HIGH");
ck("T4 no sustained SDA overlap", both_pull < 4, 1);
ck("T4 the bus returned to idle", scl & sda, 1);
// ----------------------------------------------------------------
// T5. CLOCK STRETCHING: THE TARGET STOPS TIME AND THE DRIVER WAITS.
//
// Aimed at the stretching responder. It acknowledges, then holds SCL low for
// 40 clocks before letting the transfer continue. There is nothing the
// controller can do about that except wait, and waiting correctly is the whole
// of stretch support on the controller side.
//
// FOUR THINGS ARE CHECKED, and the order is the argument:
//
// 1. the responder actually stretched -- `n_stretches`, from the responder
// itself. Without this the test could pass because nothing happened.
// 2. the controller NOTICED -- `obs_stretched`, derived from the resolved SCL
// line failing to rise when released. It is an observation, not a message:
// the responder never tells the controller anything.
// 3. the controller did NOT time out, so the wait was bounded and completed.
// 4. the data is unaffected. This is the real payload of the test. A stretch
// changes WHEN bits move and changes nothing about WHICH bits move, so a
// correct stretch is invisible above the signal level -- the monitor
// reconstructs exactly the same bytes, and the byte count is unchanged.
//
// That last point is why this test lives here and not in the monitor chapter.
// The monitor is edge-driven and has no notion of a clock period, so it handles
// stretching by construction. It is the DRIVER that has to be written for it,
// because a driver that counts cycles instead of waiting for the line will
// clock data out into a target that has stopped listening.
// ----------------------------------------------------------------
nb_before = m_nbytes; nack_before = r_nack;
req_addr = STRETCH_ADDR; req_read = 1'b0; req_len = 3'd2;
req_d0 = 8'h6C; req_d1 = 8'h5B;
run_txn;
$display("T5 a stretching target: the driver waits, and no data moves in the gap");
ck("T5 the responder really did stretch", s_nstr > 0, 1);
ck("T5 the controller observed the stretch", c_stretched, 1);
ck("T5 and did not give up waiting", c_timeout, 0);
ck("T5 the address was still acknowledged", c_aacked, 1);
ck("T5 both data bytes were still acknowledged", c_nacked, 2);
ck("T5 the responder received the last byte intact", s_lastwr, 8'h5B);
ck("T5 the monitor reconstructed three bytes anyway",
m_nbytes - nb_before, 3);
// Every responder SEES every address byte -- that is what a shared bus means, and
// `n_addr_seen` rises on both. What distinguishes them is that only one of them
// ANSWERED, so the count that must not move is the acknowledge count.
ck("T5 the non-stretching responder acknowledged nothing", r_nack, nack_before);
ck("T5 and was not selected", r_selected, 0);
ck("T5 the bus returned to idle", scl & sda, 1);
if (errors == 0) $display("=== i2c_bfm_pair: ALL CHECKS PASSED ===");
else $display("=== i2c_bfm_pair: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmoduleTwo parameter choices in that bench are load-bearing, and both were found by mutations that survived for the wrong reason.
READ_BASE = 0x00, not 0xC0. A responder that mistook the controller's terminating NACK for an acknowledge would keep sourcing bytes — so it would pull SDA low while the controller was trying to frame a STOP, and the STOP could not happen. That is a loud, obvious failure. At READ_BASE = 0xC0 every byte's top bit was 1, so the broken responder released the line instead, nothing was observable, and mutation R06 survived on a parameter value rather than on a correct design. Setting the base to 0x00 makes the byte after the last read have bit 7 clear, which makes the fault visible.
STRETCH_CLKS = 40 on a second instance. Clock stretching cannot be tested by a responder that never stretches and cannot be switched on mid-run, because the hold length is an elaboration-time parameter. So the stretching case is a second instance of the same module at its own address, silent for every transfer aimed elsewhere. Two inequalities matter and both are checked in the comment there: 40 is longer than a half-period, so the hold is unmistakably a stretch; and 40 is far shorter than the driver's timeout, so a correct driver waits it out. Break the first and the test proves nothing; break the second and it proves the opposite of what it claims.
Every multi-byte read returns 0xFF after the first byte
Pitfall — one acknowledge state for two different owners
// A responder model with a single acknowledge state. Writes work perfectly.
// Single-byte reads work. Multi-byte reads return the first byte correctly and
// 0xFF for every byte after it.
//
// R_RDATA: if (bit_done) begin
// state <= R_ACK; // the ninth slot
// end
//
// R_ACK: begin
// sda_drive_low <= ack_this; // WE drive the acknowledge
// if (slot_done) state <= (ack_this) ? R_WDATA : R_IDLE;
// end // not acking => finished
//
// During a READ the controller owns the ninth slot, not the responder. So the
// responder drives a slot it does not own, and then -- because "we are not
// acknowledging" is encoded as "the transfer is over" -- returns to R_IDLE.
//
// The controller keeps clocking. Nobody is driving SDA. It reads the pull-up:
// 0xFF, for every byte after the first.
//
// The symptom points squarely at the read datapath, which is correct.Pitfall — a mutation that survived because of a parameter value
// Mutation R06 makes the responder misread the controller's terminating NACK
// as an acknowledge, so it sources one byte too many. It SURVIVED: every check
// in the bench passed.
//
// The bench was reading with READ_BASE = 0xC0, so the bytes were:
//
// 0xC0, 0xC1, 0xC2, ... every one with bit 7 = 1
//
// A broken responder sources one extra byte. Its first action is to drive bit 7
// of that byte -- which is 1, meaning RELEASE. So the broken responder releases
// SDA at exactly the moment the correct one would have, the controller frames
// its STOP unobstructed, and the extra byte is invisible.
//
// The mutation is real. The design is wrong. The bench cannot see it, for a
// reason that has nothing to do with the bench's structure.6. What 20.6 Settled
A responder is judged by the awkward behaviour it can produce, not by whether transfers succeed. Refusal, mid-transfer NACK and stretching are the behaviours that matter, because they are what a controller has to survive.
Its policy must be thin. Four parameters and no register map. Modelling the target's decisions would make it agree with the target about them, which is the common-mode failure of 20.4 arriving through the stimulus.
Producing traffic and producing expectations are different jobs. The responder does the first; 20.8's reference model does the second. One component doing both would be predicting its own output.
"I am not driving this slot" and "the transfer is over" are different states. Collapsing them terminated every multi-byte read after one byte, and the symptom pointed at the read datapath, which was correct.
A survivor whose observability depends on a constant is a stimulus problem. READ_BASE = 0xC0 hid a real defect because every read byte's top bit was 1; 0x00 exposes it. Classify before adding checks — adding checks there would have found nothing.
Next is the passive component, and the hardest one to get right: reconstructing a transaction from raw edges without assuming the design is correct, and without borrowing a single line of its framing logic. Chapter 20.7 — Monitor Architecture.
Continue learning
Related tutorials
- Related topic
The Master Agent — Stimulus That Owns the Bus
The driver that turns an intent into edges, owns SCL, and must obey every rule it exists to test others against. Covers the difference between releasing a line and the line being high, why every wait needs a bound and two tests, and why a completion handshake must be a count rather than a pulse.
- 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
Bus Feedback — Clock Stretching and Arbitration From One Comparison
Clock stretching and arbitration loss are not two features. They are one comparison — a line this master released that reads back low — applied to two wires, differing only in a timing qualifier and an intent gate. Builds both from a single comparator and shows why a master can only ever lose by trying to send a one.
