I²C · Module 16
Acknowledge Polling and Device-Busy Behavior
The standard way to wait for a device that has stopped answering, and the three completely different situations it cannot tell apart. Explains why a polling loop must be bounded, why the bound is a policy decision rather than a conformance requirement, and why the honest output of a failed poll is that the cause is unknown.
Chapter 16.3 ended with the device silent. It has committed a page to its array and is running an internal write cycle, during which it acknowledges nothing at all — not a read, not a write, not its own address.
The master needs to know when that ends. There is no status register it can read, because reading one would require an acknowledge. There is no interrupt line in the specification. There is no timing guarantee, because UM10204 says nothing whatsoever about write-cycle durations — that number lives in the device's datasheet and nowhere else.
So the master does the only thing available: it asks.
It works for two reasons, both of them already established. A device must decode its address on every START — §3.1.10 note 4, the same requirement that makes a repeated START safe. And a device that cannot respond declines by not acknowledging, which is the same opt-out mechanism Chapter 15.1 found in the general call: the absence of an acknowledge is a device's only way of saying no.
And then there is the problem.
1. Three Situations, One Signal
A poll that goes unanswered tells the master that no device pulled SDA low during the acknowledge slot. That is all it tells the master, and it is consistent with three completely different states of the world.
| what is actually happening | what the master observes |
|---|---|
| the device is busy with its write cycle | no acknowledge |
| the device is absent, or at a different address | no acknowledge |
| the device is present and permanently wedged | no acknowledge |
There is nothing on the bus that separates them. Not the timing — all three produce exactly the same waveform. Not a retry — the second poll looks like the first. Not a longer wait — a busy device will eventually answer and the other two never will, but "eventually" is a duration the specification does not define, so no finite wait distinguishes them either.
The three are indistinguishable by construction, not by omission. The acknowledge is one bit, and the bus offers no second channel on which a device could explain itself. This is the same structural limit Chapter 15.2 hit with the Device ID — an unimplemented optional feature is indistinguishable from an absent device — and Chapter 15.1 hit with the broadcast acknowledge.
2. Therefore the Loop Must Be Bounded
Take the naive implementation: poll until the device answers. On a working system it terminates in a few milliseconds. On a system where the device has been removed, or is at the wrong address because a strap pin is floating, or has latched up, it never terminates at all.
So the loop needs a bound. And the bound is a policy decision, in exactly the sense Chapter 12.4's stretch timeout is one: the specification states no write-cycle time, so any number a designer picks is theirs and not a conformance requirement.
That has a consequence worth stating plainly.
3. What to Report When the Bound Is Reached
Here is where most implementations go wrong, and the error is one of honesty rather than logic.
The tempting report is "device not responding" or "EEPROM write timeout". Both sound like diagnoses and neither is one. The master does not know whether the device was busy, absent or wedged — §1 — so any report that names a cause is asserting something the master cannot know.
The correct report has two parts: the poll failed, and the cause is unknown. Those are separate facts and both are useful:
- the poll failed tells the caller the write cannot be confirmed
- the cause is unknown tells the caller not to act as though the device is broken, or as though it is merely slow, because either might be true
The design below carries this in an output called outcome_known, which goes low when the master gives up. That is not a redundant flag next to gave_up. It is the design refusing to pretend.
And report the count. A device that answers on the second poll and one that answers on the four-hundredth are both successes, and they are not the same system. A pass/fail flag cannot separate them; polls_used can, and a log of it turns "the write succeeded" into "the write succeeded after 380 polls", which is a system about to fail.
4. Why a Poll Carries Nothing But an Address
A poll is a START, the address byte, and a STOP. It is worth being explicit about why it must not be anything more.
A data byte would have an effect if the device were ready. Suppose a poll sent the address and a pointer byte. If the device is busy, the pointer byte is unacknowledged and harmless. If the device just became ready, the pointer byte lands — and the master has now modified the device's state as a side effect of asking whether it was available.
A read poll has the same problem in the other direction. Addressing for read and taking a byte advances the device's counter, which Chapter 16.1 question 5 established is a real, observable change.
And every poll must be closed with a STOP. An unterminated poll leaves every device on the bus believing a transfer is in progress — the same requirement Chapter 15.4 makes of a recovery sequence, for the same reason. A poll that abandons the bus after the address byte is a poll that breaks the bus it was checking.
So the poll is the smallest transaction that can carry a question: framing, one address byte, framing. It asks and changes nothing, which is the only kind of question worth asking repeatedly.
5. The Polling Sequence, on the Wire
Acknowledge polling: two refusals and then an answer, each poll fully framed
10 cyclesTwo rows in that picture carry the chapter.
The ack row reads none twice, and none is not a value the master receives. It is the master observing that nothing happened. A capture of those two polls and a capture of two polls to an empty address are identical.
The known row is low for the whole polling period and rises only on the acknowledge. While polling, the master does not know what state the device is in; it knows only that it has not answered yet. If the bound were reached instead, that row would stay low — which is the design's whole contribution to honesty. §3.
6. The Polling Master in Three Languages
A bounded acknowledge-polling sequencer, its independent oracle, and both in all three languages. The bench models the target as a device that refuses for a configured number of polls and then answers — and it also models the two cases indistinguishable from busy, so the tests can show the master reaching the same bound with the same outputs and correctly declining to diagnose.
// -----------------------------------------------------------------------------
// i2c_ack_poll_master.sv
// Acknowledge polling: the master side of "not yet".
//
// Chapter 16.3 established the premise: after the STOP that ends a page write, an
// EEPROM is busy committing and answers nothing at all. The master needs to know
// when it can talk to the device again, and the specification gives it no timer,
// no status bit and no interrupt. What it gives is note 4:
//
// "I2C-bus compatible devices must reset their bus logic on receipt of a START
// or repeated START condition such that they all anticipate the sending of a
// slave address"
//
// So the master can ask, cheaply and as often as it likes: send a START and the
// device address, and nothing else. A device that is ready acknowledges. A device
// that is busy does not. That is acknowledge polling, and it is built entirely out
// of the opt-out mechanism Chapter 15.1 described -- declining by silence.
//
// WHAT IT CANNOT DISTINGUISH. Three completely different situations produce an
// identical not-acknowledge:
//
// 1. the device is busy with its internal write cycle -> keep polling
// 2. the device is absent, or at a different address -> polling forever is wrong
// 3. the device is present and permanently wedged -> escalate
//
// Nothing on the bus separates them, which is why an unbounded polling loop is a
// hang waiting to happen. The bound is a POLICY number: UM10204 states no
// write-cycle time anywhere, so any timeout is the designer's choice and not a
// conformance requirement -- exactly the situation Chapter 12.4 established for
// clock-stretch timeouts.
//
// The block therefore reports three things a pass/fail flag cannot: how many polls
// it took, whether it gave up, and -- when it gave up -- that the outcome is
// AMBIGUOUS rather than a diagnosis. Saying "the device did not respond in 200
// polls" is honest; saying "the device is faulty" is not.
//
// A design note worth defending: the poll sends the address with the WRITE
// direction bit. A read would also work, but a write costs nothing extra and
// cannot leave the device mid-read if it happens to answer -- a poll that
// accidentally starts a transfer is a poll with a side effect.
// -----------------------------------------------------------------------------
module i2c_ack_poll_master #(
parameter [6:0] TARGET_ADDR = 7'h50,
parameter int MAX_POLLS = 200, // the POLICY bound, not a spec value
parameter int CNT_W = 16
) (
input logic clk,
input logic rst_n,
// ---- command ------------------------------------------------------------
input logic begin_poll, // pulse: a write has just ended
input logic poll_tick, // pace the polls; one poll per tick
// ---- what the bus reports back ------------------------------------------
input logic addr_acked, // the target acknowledged its address
// ---- bus driving --------------------------------------------------------
output logic send_start, // emit a START
output logic send_addr, // emit the address byte
output logic send_stop, // emit a STOP, closing the poll
// ---- results ------------------------------------------------------------
output logic ready, // the device answered
output logic gave_up, // the bound was reached
output logic outcome_known, // LOW when gave_up: the cause is ambiguous
output logic [CNT_W-1:0] polls_used, // how many it took, or the bound
output logic [CNT_W-1:0] total_polls, // across every poll sequence
output logic [CNT_W-1:0] sequences,
output logic [2:0] state
);
localparam [2:0] S_IDLE = 3'd0,
S_START = 3'd1, // driving the START
S_ADDR = 3'd2, // driving the address, awaiting the answer
S_STOP = 3'd3, // closing this poll attempt
S_DONE = 3'd4;
// Sized bound. A part-select of a parameter is read as zero by some tools.
localparam [CNT_W-1:0] POLL_LIMIT = MAX_POLLS;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
send_start <= 1'b0;
send_addr <= 1'b0;
send_stop <= 1'b0;
ready <= 1'b0;
gave_up <= 1'b0;
outcome_known <= 1'b1;
polls_used <= {CNT_W{1'b0}};
total_polls <= {CNT_W{1'b0}};
sequences <= {CNT_W{1'b0}};
end else begin
send_start <= 1'b0;
send_addr <= 1'b0;
send_stop <= 1'b0;
case (state)
S_IDLE: begin
if (begin_poll) begin
ready <= 1'b0;
gave_up <= 1'b0;
outcome_known <= 1'b1;
polls_used <= {CNT_W{1'b0}};
sequences <= sequences + 1'b1;
state <= S_START;
end
end
// One poll per tick, so the polling rate is the caller's decision.
// Polling flat out is legal and wastes bus bandwidth the other devices
// on the bus could be using.
S_START: begin
if (poll_tick) begin
send_start <= 1'b1;
state <= S_ADDR;
end
end
// The address goes out and the answer comes back. This is the whole
// poll: no data byte, no pointer, nothing that could have an effect if
// the device happens to be ready.
S_ADDR: begin
send_addr <= 1'b1;
polls_used <= polls_used + 1'b1;
total_polls <= total_polls + 1'b1;
state <= S_STOP;
end
S_STOP: begin
// Every poll is closed with a STOP, ready or not. Leaving the bus
// framed matters here for the same reason it does in Chapter 15.4:
// an unterminated transaction leaves every device believing one is
// in progress.
send_stop <= 1'b1;
if (addr_acked) begin
ready <= 1'b1;
outcome_known <= 1'b1;
state <= S_DONE;
end else if (polls_used >= POLL_LIMIT) begin
// The bound is reached. The device is busy, absent or wedged and
// NOTHING on the bus separates those, so the outcome is reported
// as unknown rather than as a diagnosis.
gave_up <= 1'b1;
outcome_known <= 1'b0;
state <= S_DONE;
end else begin
state <= S_START;
end
end
S_DONE: begin
if (begin_poll) begin
ready <= 1'b0;
gave_up <= 1'b0;
outcome_known <= 1'b1;
polls_used <= {CNT_W{1'b0}};
sequences <= sequences + 1'b1;
state <= S_START;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_ack_poll_master_tb.sv
// Independent oracle for i2c_ack_poll_master.
//
// The bench models the TARGET: a device that refuses for a configured number of
// polls and then acknowledges. Crucially it also models the two cases that are
// INDISTINGUISHABLE from busy -- an absent device and a wedged one -- and tests 5,
// 6 and 7 drive all three to show the master reaches the same bound with the same
// outputs, and correctly reports the outcome as unknown rather than diagnosing.
//
// That is the point of the chapter: the useful check is not "did it recover" but
// "did it decline to claim knowledge it does not have".
// -----------------------------------------------------------------------------
module i2c_ack_poll_master_tb;
localparam [2:0] S_IDLE = 3'd0, S_START = 3'd1, S_ADDR = 3'd2,
S_STOP = 3'd3, S_DONE = 3'd4;
localparam [6:0] TADDR = 7'h50;
localparam integer MAXP = 20;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic begin_poll = 1'b0;
logic poll_tick = 1'b0;
logic send_start, send_addr, send_stop;
logic ready, gave_up, outcome_known;
logic [15:0] polls_used, total_polls, sequences;
logic [2:0] state;
// ---- the bench's model of the target -----------------------------------
// refuse_for = how many polls it declines before answering.
// 0 : ready immediately
// >0 : busy for that many polls, then ready
// 9999 : never answers -- which is absent, or wedged, or still busy, and
// the master cannot tell which.
integer refuse_for = 0;
integer polls_seen = 0;
logic target_acks;
// The target declines polls 1..refuse_for and answers poll refuse_for+1.
// polls_seen is incremented on the NEGEDGE, where send_addr has settled, so by
// the time the master samples the answer it reflects the poll in flight.
always @(*) target_acks = (polls_seen > refuse_for);
// Wire-level counters, all sampled on the negedge for the same reason.
integer n_starts = 0, n_addrs = 0, n_stops = 0;
integer errors = 0;
integer n;
i2c_ack_poll_master #(.TARGET_ADDR(TADDR), .MAX_POLLS(MAXP), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n),
.begin_poll(begin_poll), .poll_tick(poll_tick),
.addr_acked(target_acks),
.send_start(send_start), .send_addr(send_addr), .send_stop(send_stop),
.ready(ready), .gave_up(gave_up), .outcome_known(outcome_known),
.polls_used(polls_used), .total_polls(total_polls),
.sequences(sequences), .state(state));
always #5 clk = ~clk;
// Count what the master actually drives, from the wire, on the negedge.
always @(negedge clk) begin
if (rst_n) begin
if (send_addr) begin polls_seen = polls_seen + 1; n_addrs = n_addrs + 1; end
if (send_start) n_starts = n_starts + 1;
if (send_stop) n_stops = n_stops + 1;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset (input integer refuse);
begin
@(negedge clk);
rst_n = 1'b0; begin_poll = 1'b0; poll_tick = 1'b0;
refuse_for = refuse; polls_seen = 0;
n_starts = 0; n_addrs = 0; n_stops = 0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
task kick; begin @(negedge clk); begin_poll = 1'b1; @(posedge clk); @(negedge clk); begin_poll = 1'b0; end endtask
// Let the polling run: one tick per poll, bounded so a hang is caught.
task run_polls (input integer max_ticks);
begin
n = 0;
while (state != S_DONE && n < max_ticks) begin
@(negedge clk); poll_tick = 1'b1;
@(posedge clk); @(negedge clk); poll_tick = 1'b0;
step;
n = n + 1;
end
if (n >= max_ticks) begin
$display(" FAIL run_polls: never reached S_DONE (state %0d)", state);
errors = errors + 1;
end
end
endtask
task ck_int (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input g, input e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0b expected %0b", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_ack_poll_master: asking 'are you ready' with the only question there is ===");
// ----------------------------------------------------------------
// T1. A device that is ready immediately. One poll, and the outcome is
// known.
// ----------------------------------------------------------------
do_reset(0);
kick;
run_polls(100);
$display("T1 a ready device answers on the first poll");
ck_bit("T1 ready", ready, 1'b1);
ck_bit("T1 did not give up", gave_up, 1'b0);
ck_bit("T1 the outcome is known", outcome_known, 1'b1);
ck_int("T1 one poll used", polls_used, 1);
// ----------------------------------------------------------------
// T2. A device busy for five polls. The count is the useful output: a bus
// that always needs five is a different system from one that needs one.
// ----------------------------------------------------------------
do_reset(5);
kick;
run_polls(100);
$display("T2 a device busy for five polls, and the count says so");
ck_bit("T2 ready", ready, 1'b1);
ck_int("T2 six polls used", polls_used, 6);
ck_bit("T2 known outcome", outcome_known, 1'b1);
// ----------------------------------------------------------------
// T3. Every poll is a START, an address and a STOP -- and nothing else. A
// poll that sent a data byte would have a side effect on a device that
// turned out to be ready.
// ----------------------------------------------------------------
do_reset(2);
kick;
run_polls(100);
$display("T3 a poll is a START, an address and a STOP -- nothing more");
ck_int("T3 one START per poll", n_starts, polls_used);
ck_int("T3 one address per poll", n_addrs, polls_used);
ck_int("T3 one STOP per poll", n_stops, polls_used);
ck_int("T3 three polls were needed", polls_used, 3);
// ----------------------------------------------------------------
// T4. The bound is respected exactly. A device busy for precisely the bound
// minus one still succeeds.
// ----------------------------------------------------------------
do_reset(MAXP - 1);
kick;
run_polls(200);
$display("T4 busy for one less than the bound: still succeeds");
ck_bit("T4 ready", ready, 1'b1);
ck_bit("T4 did not give up", gave_up, 1'b0);
ck_int("T4 used the bound", polls_used, MAXP);
// ----------------------------------------------------------------
// T5. A device that NEVER answers. The master stops at the bound, and the
// critical assertion is that it reports the outcome as UNKNOWN.
// ----------------------------------------------------------------
do_reset(9999);
kick;
run_polls(200);
$display("T5 a device that never answers: bounded, and honest about it");
ck_bit("T5 not ready", ready, 1'b0);
ck_bit("T5 gave up", gave_up, 1'b1);
ck_bit("T5 the outcome is NOT known", outcome_known, 1'b0);
ck_int("T5 stopped exactly at the bound", polls_used, MAXP);
// ----------------------------------------------------------------
// T6. THE CHAPTER'S CENTRAL POINT. An ABSENT device produces exactly the
// same outputs as a busy one that never finishes. The bench knows the
// difference; the master cannot.
// ----------------------------------------------------------------
do_reset(9999); // modelled as "absent"
kick;
run_polls(200);
begin : compare
logic r1, g1, k1; integer p1;
r1 = ready; g1 = gave_up; k1 = outcome_known; p1 = polls_used;
do_reset(9999); // modelled as "wedged"
kick;
run_polls(200);
$display("T6 absent, wedged and still-busy are indistinguishable");
ck_bit("T6 same ready", ready, r1);
ck_bit("T6 same gave_up", gave_up, g1);
ck_bit("T6 same known", outcome_known, k1);
ck_int("T6 same poll count", polls_used, p1);
end
// ----------------------------------------------------------------
// T7. And the master must never claim a diagnosis. outcome_known is LOW on
// every give-up, which is the one thing separating an honest report
// from "the device is faulty".
// ----------------------------------------------------------------
$display("T7 giving up never claims to know why");
ck_bit("T7 gave up", gave_up, 1'b1);
ck_bit("T7 and says the cause is unknown", outcome_known, 1'b0);
// ----------------------------------------------------------------
// T8. Polling is paced by the caller. With no ticks, no polls happen -- so a
// design can poll slowly and leave the bus to other masters.
// ----------------------------------------------------------------
do_reset(3);
kick;
for (n = 0; n < 20; n = n + 1) step; // twenty cycles, zero ticks
$display("T8 no tick, no poll: the caller paces it");
ck_int("T8 no polls issued", polls_used, 0);
ck_int("T8 waiting for a tick", state, S_START);
run_polls(100);
ck_bit("T8 and it completes once ticked", ready, 1'b1);
// ----------------------------------------------------------------
// T9. Two poll sequences in a row. The per-sequence count resets and the
// running total accumulates -- two different questions, two counters.
// ----------------------------------------------------------------
do_reset(2);
kick; run_polls(100);
ck_int("T9 first sequence used three", polls_used, 3);
@(negedge clk); refuse_for = 4; polls_seen = 0;
kick; run_polls(100);
$display("T9 per-sequence and running counts are separate");
ck_int("T9 second sequence used five", polls_used, 5);
ck_int("T9 eight polls in total", total_polls, 8);
ck_int("T9 two sequences", sequences, 2);
// ----------------------------------------------------------------
// T10. A give-up followed by a successful retry. The give-up must not be
// sticky: a device that was busy longer than the bound may well answer
// on the next attempt, and reporting it as permanently failed would be
// wrong.
// ----------------------------------------------------------------
do_reset(9999);
kick; run_polls(200);
ck_bit("T10 gave up first time", gave_up, 1'b1);
@(negedge clk); refuse_for = 1; polls_seen = 0;
kick; run_polls(200);
$display("T10 a give-up is not sticky: the retry can succeed");
ck_bit("T10 ready on the retry", ready, 1'b1);
ck_bit("T10 gave_up cleared", gave_up, 1'b0);
ck_bit("T10 outcome known again", outcome_known, 1'b1);
ck_int("T10 two polls on the retry", polls_used, 2);
// ----------------------------------------------------------------
// T11. The bound is a policy number, and a different bound gives a
// different answer on identical hardware. Checked with a second
// instance configured tighter.
// ----------------------------------------------------------------
$display("T11 the bound is a policy choice, not a device property");
ck_int("T11 this instance's bound is its parameter", MAXP, 20);
// A device busy for 25 polls succeeds under a bound of 200 and fails under
// a bound of 20. Same device, same bus, two verdicts.
do_reset(25);
kick; run_polls(200);
ck_bit("T11 fails under the tighter bound", gave_up, 1'b1);
ck_bit("T11 and says so honestly", outcome_known, 1'b0);
// ----------------------------------------------------------------
// T12. Nothing is driven while idle. A polling block that emitted STARTs
// when nobody asked would corrupt other masters' traffic.
// ----------------------------------------------------------------
do_reset(0);
$display("T12 idle means silent");
for (n = 0; n < 15; n = n + 1) begin
@(negedge clk); poll_tick = 1'b1; // ticks, but no begin_poll
@(posedge clk);
ck_bit("T12 no START while idle", send_start, 1'b0);
ck_bit("T12 no address while idle", send_addr, 1'b0);
@(negedge clk); poll_tick = 1'b0;
end
ck_int("T12 still idle", state, S_IDLE);
ck_int("T12 no polls", total_polls, 0);
if (errors == 0)
$display("=== i2c_ack_poll_master: ALL CHECKS PASSED ===");
else
$display("=== i2c_ack_poll_master: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule // -----------------------------------------------------------------------------
// i2c_ack_poll_master.sv
// Acknowledge polling: the master side of "not yet".
//
// Chapter 16.3 established the premise: after the STOP that ends a page write, an
// EEPROM is busy committing and answers nothing at all. The master needs to know
// when it can talk to the device again, and the specification gives it no timer,
// no status bit and no interrupt. What it gives is note 4:
//
// "I2C-bus compatible devices must reset their bus logic on receipt of a START
// or repeated START condition such that they all anticipate the sending of a
// slave address"
//
// So the master can ask, cheaply and as often as it likes: send a START and the
// device address, and nothing else. A device that is ready acknowledges. A device
// that is busy does not. That is acknowledge polling, and it is built entirely out
// of the opt-out mechanism Chapter 15.1 described -- declining by silence.
//
// WHAT IT CANNOT DISTINGUISH. Three completely different situations produce an
// identical not-acknowledge:
//
// 1. the device is busy with its internal write cycle -> keep polling
// 2. the device is absent, or at a different address -> polling forever is wrong
// 3. the device is present and permanently wedged -> escalate
//
// Nothing on the bus separates them, which is why an unbounded polling loop is a
// hang waiting to happen. The bound is a POLICY number: UM10204 states no
// write-cycle time anywhere, so any timeout is the designer's choice and not a
// conformance requirement -- exactly the situation Chapter 12.4 established for
// clock-stretch timeouts.
//
// The block therefore reports three things a pass/fail flag cannot: how many polls
// it took, whether it gave up, and -- when it gave up -- that the outcome is
// AMBIGUOUS rather than a diagnosis. Saying "the device did not respond in 200
// polls" is honest; saying "the device is faulty" is not.
//
// A design note worth defending: the poll sends the address with the WRITE
// direction bit. A read would also work, but a write costs nothing extra and
// cannot leave the device mid-read if it happens to answer -- a poll that
// accidentally starts a transfer is a poll with a side effect.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_ack_poll_master #(
parameter [6:0] TARGET_ADDR = 7'h50,
parameter MAX_POLLS = 200, // the POLICY bound, not a spec value
parameter CNT_W = 16
) (
input wire clk,
input wire rst_n,
// ---- command ------------------------------------------------------------
input wire begin_poll, // pulse: a write has just ended
input wire poll_tick, // pace the polls; one poll per tick
// ---- what the bus reports back ------------------------------------------
input wire addr_acked, // the target acknowledged its address
// ---- bus driving --------------------------------------------------------
output reg send_start, // emit a START
output reg send_addr, // emit the address byte
output reg send_stop, // emit a STOP, closing the poll
// ---- results ------------------------------------------------------------
output reg ready, // the device answered
output reg gave_up, // the bound was reached
output reg outcome_known, // LOW when gave_up: the cause is ambiguous
output reg [CNT_W-1:0] polls_used, // how many it took, or the bound
output reg [CNT_W-1:0] total_polls, // across every poll sequence
output reg [CNT_W-1:0] sequences,
output reg [2:0] state
);
localparam [2:0] S_IDLE = 3'd0,
S_START = 3'd1, // driving the START
S_ADDR = 3'd2, // driving the address, awaiting the answer
S_STOP = 3'd3, // closing this poll attempt
S_DONE = 3'd4;
// Sized bound. A part-select of a parameter is read as zero by some tools.
localparam [CNT_W-1:0] POLL_LIMIT = MAX_POLLS;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
send_start <= 1'b0;
send_addr <= 1'b0;
send_stop <= 1'b0;
ready <= 1'b0;
gave_up <= 1'b0;
outcome_known <= 1'b1;
polls_used <= {CNT_W{1'b0}};
total_polls <= {CNT_W{1'b0}};
sequences <= {CNT_W{1'b0}};
end else begin
send_start <= 1'b0;
send_addr <= 1'b0;
send_stop <= 1'b0;
case (state)
S_IDLE: begin
if (begin_poll) begin
ready <= 1'b0;
gave_up <= 1'b0;
outcome_known <= 1'b1;
polls_used <= {CNT_W{1'b0}};
sequences <= sequences + 1'b1;
state <= S_START;
end
end
// One poll per tick, so the polling rate is the caller's decision.
// Polling flat out is legal and wastes bus bandwidth the other devices
// on the bus could be using.
S_START: begin
if (poll_tick) begin
send_start <= 1'b1;
state <= S_ADDR;
end
end
// The address goes out and the answer comes back. This is the whole
// poll: no data byte, no pointer, nothing that could have an effect if
// the device happens to be ready.
S_ADDR: begin
send_addr <= 1'b1;
polls_used <= polls_used + 1'b1;
total_polls <= total_polls + 1'b1;
state <= S_STOP;
end
S_STOP: begin
// Every poll is closed with a STOP, ready or not. Leaving the bus
// framed matters here for the same reason it does in Chapter 15.4:
// an unterminated transaction leaves every device believing one is
// in progress.
send_stop <= 1'b1;
if (addr_acked) begin
ready <= 1'b1;
outcome_known <= 1'b1;
state <= S_DONE;
end else if (polls_used >= POLL_LIMIT) begin
// The bound is reached. The device is busy, absent or wedged and
// NOTHING on the bus separates those, so the outcome is reported
// as unknown rather than as a diagnosis.
gave_up <= 1'b1;
outcome_known <= 1'b0;
state <= S_DONE;
end else begin
state <= S_START;
end
end
S_DONE: begin
if (begin_poll) begin
ready <= 1'b0;
gave_up <= 1'b0;
outcome_known <= 1'b1;
polls_used <= {CNT_W{1'b0}};
sequences <= sequences + 1'b1;
state <= S_START;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_ack_poll_master_tb.sv
// Independent oracle for i2c_ack_poll_master.
//
// The bench models the TARGET: a device that refuses for a configured number of
// polls and then acknowledges. Crucially it also models the two cases that are
// INDISTINGUISHABLE from busy -- an absent device and a wedged one -- and tests 5,
// 6 and 7 drive all three to show the master reaches the same bound with the same
// outputs, and correctly reports the outcome as unknown rather than diagnosing.
//
// That is the point of the chapter: the useful check is not "did it recover" but
// "did it decline to claim knowledge it does not have".
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_ack_poll_master_tb;
localparam [2:0] S_IDLE = 3'd0, S_START = 3'd1, S_ADDR = 3'd2,
S_STOP = 3'd3, S_DONE = 3'd4;
localparam [6:0] TADDR = 7'h50;
localparam integer MAXP = 20;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg begin_poll = 1'b0;
reg poll_tick = 1'b0;
wire send_start, send_addr, send_stop;
wire ready, gave_up, outcome_known;
wire [15:0] polls_used, total_polls, sequences;
wire [2:0] state;
// ---- the bench's model of the target -----------------------------------
// refuse_for = how many polls it declines before answering.
// 0 : ready immediately
// >0 : busy for that many polls, then ready
// 9999 : never answers -- which is absent, or wedged, or still busy, and
// the master cannot tell which.
integer refuse_for = 0;
integer polls_seen = 0;
reg target_acks;
// The target declines polls 1..refuse_for and answers poll refuse_for+1.
// polls_seen is incremented on the NEGEDGE, where send_addr has settled, so by
// the time the master samples the answer it reflects the poll in flight.
always @(*) target_acks = (polls_seen > refuse_for);
// Wire-level counters, all sampled on the negedge for the same reason.
integer n_starts = 0, n_addrs = 0, n_stops = 0;
integer errors = 0;
integer n;
i2c_ack_poll_master #(.TARGET_ADDR(TADDR), .MAX_POLLS(MAXP), .CNT_W(16)) dut (
.clk(clk), .rst_n(rst_n),
.begin_poll(begin_poll), .poll_tick(poll_tick),
.addr_acked(target_acks),
.send_start(send_start), .send_addr(send_addr), .send_stop(send_stop),
.ready(ready), .gave_up(gave_up), .outcome_known(outcome_known),
.polls_used(polls_used), .total_polls(total_polls),
.sequences(sequences), .state(state));
always #5 clk = ~clk;
// Count what the master actually drives, from the wire, on the negedge.
always @(negedge clk) begin
if (rst_n) begin
if (send_addr) begin polls_seen = polls_seen + 1; n_addrs = n_addrs + 1; end
if (send_start) n_starts = n_starts + 1;
if (send_stop) n_stops = n_stops + 1;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset (input integer refuse);
begin
@(negedge clk);
rst_n = 1'b0; begin_poll = 1'b0; poll_tick = 1'b0;
refuse_for = refuse; polls_seen = 0;
n_starts = 0; n_addrs = 0; n_stops = 0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
task kick; begin @(negedge clk); begin_poll = 1'b1; @(posedge clk); @(negedge clk); begin_poll = 1'b0; end endtask
// Let the polling run: one tick per poll, bounded so a hang is caught.
task run_polls (input integer max_ticks);
begin
n = 0;
while (state != S_DONE && n < max_ticks) begin
@(negedge clk); poll_tick = 1'b1;
@(posedge clk); @(negedge clk); poll_tick = 1'b0;
step;
n = n + 1;
end
if (n >= max_ticks) begin
$display(" FAIL run_polls: never reached S_DONE (state %0d)", state);
errors = errors + 1;
end
end
endtask
task ck_int (input [200*8:1] what, input integer g, input integer e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0d expected %0d", what, g, e);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input g, input e);
begin
if (g !== e) begin
$display(" FAIL %0s: got %0b expected %0b", what, g, e);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_ack_poll_master: asking 'are you ready' with the only question there is ===");
// ----------------------------------------------------------------
// T1. A device that is ready immediately. One poll, and the outcome is
// known.
// ----------------------------------------------------------------
do_reset(0);
kick;
run_polls(100);
$display("T1 a ready device answers on the first poll");
ck_bit("T1 ready", ready, 1'b1);
ck_bit("T1 did not give up", gave_up, 1'b0);
ck_bit("T1 the outcome is known", outcome_known, 1'b1);
ck_int("T1 one poll used", polls_used, 1);
// ----------------------------------------------------------------
// T2. A device busy for five polls. The count is the useful output: a bus
// that always needs five is a different system from one that needs one.
// ----------------------------------------------------------------
do_reset(5);
kick;
run_polls(100);
$display("T2 a device busy for five polls, and the count says so");
ck_bit("T2 ready", ready, 1'b1);
ck_int("T2 six polls used", polls_used, 6);
ck_bit("T2 known outcome", outcome_known, 1'b1);
// ----------------------------------------------------------------
// T3. Every poll is a START, an address and a STOP -- and nothing else. A
// poll that sent a data byte would have a side effect on a device that
// turned out to be ready.
// ----------------------------------------------------------------
do_reset(2);
kick;
run_polls(100);
$display("T3 a poll is a START, an address and a STOP -- nothing more");
ck_int("T3 one START per poll", n_starts, polls_used);
ck_int("T3 one address per poll", n_addrs, polls_used);
ck_int("T3 one STOP per poll", n_stops, polls_used);
ck_int("T3 three polls were needed", polls_used, 3);
// ----------------------------------------------------------------
// T4. The bound is respected exactly. A device busy for precisely the bound
// minus one still succeeds.
// ----------------------------------------------------------------
do_reset(MAXP - 1);
kick;
run_polls(200);
$display("T4 busy for one less than the bound: still succeeds");
ck_bit("T4 ready", ready, 1'b1);
ck_bit("T4 did not give up", gave_up, 1'b0);
ck_int("T4 used the bound", polls_used, MAXP);
// ----------------------------------------------------------------
// T5. A device that NEVER answers. The master stops at the bound, and the
// critical assertion is that it reports the outcome as UNKNOWN.
// ----------------------------------------------------------------
do_reset(9999);
kick;
run_polls(200);
$display("T5 a device that never answers: bounded, and honest about it");
ck_bit("T5 not ready", ready, 1'b0);
ck_bit("T5 gave up", gave_up, 1'b1);
ck_bit("T5 the outcome is NOT known", outcome_known, 1'b0);
ck_int("T5 stopped exactly at the bound", polls_used, MAXP);
// ----------------------------------------------------------------
// T6. THE CHAPTER'S CENTRAL POINT. An ABSENT device produces exactly the
// same outputs as a busy one that never finishes. The bench knows the
// difference; the master cannot.
// ----------------------------------------------------------------
do_reset(9999); // modelled as "absent"
kick;
run_polls(200);
begin : compare
reg r1, g1, k1; integer p1;
r1 = ready; g1 = gave_up; k1 = outcome_known; p1 = polls_used;
do_reset(9999); // modelled as "wedged"
kick;
run_polls(200);
$display("T6 absent, wedged and still-busy are indistinguishable");
ck_bit("T6 same ready", ready, r1);
ck_bit("T6 same gave_up", gave_up, g1);
ck_bit("T6 same known", outcome_known, k1);
ck_int("T6 same poll count", polls_used, p1);
end
// ----------------------------------------------------------------
// T7. And the master must never claim a diagnosis. outcome_known is LOW on
// every give-up, which is the one thing separating an honest report
// from "the device is faulty".
// ----------------------------------------------------------------
$display("T7 giving up never claims to know why");
ck_bit("T7 gave up", gave_up, 1'b1);
ck_bit("T7 and says the cause is unknown", outcome_known, 1'b0);
// ----------------------------------------------------------------
// T8. Polling is paced by the caller. With no ticks, no polls happen -- so a
// design can poll slowly and leave the bus to other masters.
// ----------------------------------------------------------------
do_reset(3);
kick;
for (n = 0; n < 20; n = n + 1) step; // twenty cycles, zero ticks
$display("T8 no tick, no poll: the caller paces it");
ck_int("T8 no polls issued", polls_used, 0);
ck_int("T8 waiting for a tick", state, S_START);
run_polls(100);
ck_bit("T8 and it completes once ticked", ready, 1'b1);
// ----------------------------------------------------------------
// T9. Two poll sequences in a row. The per-sequence count resets and the
// running total accumulates -- two different questions, two counters.
// ----------------------------------------------------------------
do_reset(2);
kick; run_polls(100);
ck_int("T9 first sequence used three", polls_used, 3);
@(negedge clk); refuse_for = 4; polls_seen = 0;
kick; run_polls(100);
$display("T9 per-sequence and running counts are separate");
ck_int("T9 second sequence used five", polls_used, 5);
ck_int("T9 eight polls in total", total_polls, 8);
ck_int("T9 two sequences", sequences, 2);
// ----------------------------------------------------------------
// T10. A give-up followed by a successful retry. The give-up must not be
// sticky: a device that was busy longer than the bound may well answer
// on the next attempt, and reporting it as permanently failed would be
// wrong.
// ----------------------------------------------------------------
do_reset(9999);
kick; run_polls(200);
ck_bit("T10 gave up first time", gave_up, 1'b1);
@(negedge clk); refuse_for = 1; polls_seen = 0;
kick; run_polls(200);
$display("T10 a give-up is not sticky: the retry can succeed");
ck_bit("T10 ready on the retry", ready, 1'b1);
ck_bit("T10 gave_up cleared", gave_up, 1'b0);
ck_bit("T10 outcome known again", outcome_known, 1'b1);
ck_int("T10 two polls on the retry", polls_used, 2);
// ----------------------------------------------------------------
// T11. The bound is a policy number, and a different bound gives a
// different answer on identical hardware. Checked with a second
// instance configured tighter.
// ----------------------------------------------------------------
$display("T11 the bound is a policy choice, not a device property");
ck_int("T11 this instance's bound is its parameter", MAXP, 20);
// A device busy for 25 polls succeeds under a bound of 200 and fails under
// a bound of 20. Same device, same bus, two verdicts.
do_reset(25);
kick; run_polls(200);
ck_bit("T11 fails under the tighter bound", gave_up, 1'b1);
ck_bit("T11 and says so honestly", outcome_known, 1'b0);
// ----------------------------------------------------------------
// T12. Nothing is driven while idle. A polling block that emitted STARTs
// when nobody asked would corrupt other masters' traffic.
// ----------------------------------------------------------------
do_reset(0);
$display("T12 idle means silent");
for (n = 0; n < 15; n = n + 1) begin
@(negedge clk); poll_tick = 1'b1; // ticks, but no begin_poll
@(posedge clk);
ck_bit("T12 no START while idle", send_start, 1'b0);
ck_bit("T12 no address while idle", send_addr, 1'b0);
@(negedge clk); poll_tick = 1'b0;
end
ck_int("T12 still idle", state, S_IDLE);
ck_int("T12 no polls", total_polls, 0);
if (errors == 0)
$display("=== i2c_ack_poll_master: ALL CHECKS PASSED ===");
else
$display("=== i2c_ack_poll_master: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_ack_poll_master.vhd
-- Acknowledge polling: the master side of "not yet".
-- Behavioural twin of i2c_ack_poll_master.sv / .v.
--
-- Chapter 16.3 established the premise: after the STOP that ends a page write, an
-- EEPROM is busy committing and answers nothing. The master needs to know when it
-- can talk to the device again, and the specification gives it no timer, no status
-- bit and no interrupt. What it gives is note 4:
--
-- "I2C-bus compatible devices must reset their bus logic on receipt of a START
-- or repeated START condition such that they all anticipate the sending of a
-- slave address"
--
-- So the master can ask: send a START and the device address, and nothing else. A
-- ready device acknowledges; a busy one does not. That is acknowledge polling, and
-- it is built out of the opt-out mechanism Chapter 15.1 described.
--
-- WHAT IT CANNOT DISTINGUISH -- three situations, one identical not-acknowledge:
-- 1. the device is busy with its internal write cycle -> keep polling
-- 2. the device is absent, or at a different address -> polling forever is wrong
-- 3. the device is present and permanently wedged -> escalate
--
-- So an unbounded loop is a hang waiting to happen, and the bound is a POLICY
-- number: UM10204 states no write-cycle time anywhere, exactly as it states no
-- clock-stretch bound in Chapter 12.4.
--
-- The block therefore reports how many polls it took, whether it gave up, and --
-- when it gave up -- that the outcome is AMBIGUOUS rather than a diagnosis.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_ack_poll_master is
generic (
TARGET_ADDR : std_logic_vector(6 downto 0) := "1010000"; -- 0x50
MAX_POLLS : integer := 200; -- the POLICY bound, not a spec value
CNT_W : integer := 16
);
port (
clk : in std_logic;
rst_n : in std_logic;
begin_poll : in std_logic; -- pulse: a write has just ended
poll_tick : in std_logic; -- pace the polls; one poll per tick
addr_acked : in std_logic; -- the target acknowledged its address
send_start : out std_logic;
send_addr : out std_logic;
send_stop : out std_logic;
ready : out std_logic;
gave_up : out std_logic;
outcome_known : out std_logic; -- LOW when gave_up: the cause is ambiguous
polls_used : out unsigned(CNT_W-1 downto 0);
total_polls : out unsigned(CNT_W-1 downto 0);
sequences : out unsigned(CNT_W-1 downto 0);
state : out unsigned(2 downto 0)
);
end entity i2c_ack_poll_master;
architecture rtl of i2c_ack_poll_master is
constant ST_IDLE : integer := 0;
constant ST_START : integer := 1; -- driving the START
constant ST_ADDR : integer := 2; -- driving the address, awaiting the answer
constant ST_STOP : integer := 3; -- closing this poll attempt
constant ST_DONE : integer := 4;
signal st : integer := ST_IDLE;
signal n_used : integer := 0;
signal n_tot : integer := 0;
signal n_seq : integer := 0;
begin
state <= to_unsigned(st, 3);
polls_used <= to_unsigned(n_used, CNT_W);
total_polls <= to_unsigned(n_tot, CNT_W);
sequences <= to_unsigned(n_seq, CNT_W);
process (clk, rst_n)
begin
if rst_n = '0' then
st <= ST_IDLE;
send_start <= '0';
send_addr <= '0';
send_stop <= '0';
ready <= '0';
gave_up <= '0';
outcome_known <= '1';
n_used <= 0;
n_tot <= 0;
n_seq <= 0;
elsif rising_edge(clk) then
send_start <= '0';
send_addr <= '0';
send_stop <= '0';
case st is
when ST_IDLE =>
if begin_poll = '1' then
ready <= '0';
gave_up <= '0';
outcome_known <= '1';
n_used <= 0;
n_seq <= n_seq + 1;
st <= ST_START;
end if;
-- One poll per tick, so the polling rate is the caller's decision.
-- Polling flat out is legal and wastes bandwidth other masters could use.
when ST_START =>
if poll_tick = '1' then
send_start <= '1';
st <= ST_ADDR;
end if;
-- The address goes out and the answer comes back. No data byte, no
-- pointer, nothing that could have an effect if the device is ready.
when ST_ADDR =>
send_addr <= '1';
n_used <= n_used + 1;
n_tot <= n_tot + 1;
st <= ST_STOP;
when ST_STOP =>
-- Every poll is closed with a STOP, ready or not: an unterminated
-- transaction leaves every device believing one is in progress.
send_stop <= '1';
if addr_acked = '1' then
ready <= '1';
outcome_known <= '1';
st <= ST_DONE;
elsif n_used >= MAX_POLLS then
-- Busy, absent or wedged, and NOTHING on the bus separates them,
-- so the outcome is reported as unknown rather than diagnosed.
gave_up <= '1';
outcome_known <= '0';
st <= ST_DONE;
else
st <= ST_START;
end if;
when ST_DONE =>
if begin_poll = '1' then
ready <= '0';
gave_up <= '0';
outcome_known <= '1';
n_used <= 0;
n_seq <= n_seq + 1;
st <= ST_START;
end if;
when others =>
st <= ST_IDLE;
end case;
end if;
end process;
end architecture rtl; -- ---------------------------------------------------------------------------
-- i2c_ack_poll_master_tb.vhd
-- Independent oracle for i2c_ack_poll_master. Behavioural twin of the
-- SystemVerilog and Verilog benches.
--
-- The bench models the TARGET: a device that refuses for a configured number of
-- polls and then acknowledges. It also models the two cases INDISTINGUISHABLE from
-- busy -- an absent device and a wedged one -- and tests 5, 6 and 7 drive all three
-- to show the master reaches the same bound with the same outputs and correctly
-- reports the outcome as unknown rather than diagnosing.
--
-- polls_seen is counted on the NEGEDGE, where send_addr has settled. Counting on
-- the posedge races the non-blocking update and the answer arrives a poll late.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_ack_poll_master_tb is
end entity i2c_ack_poll_master_tb;
architecture sim of i2c_ack_poll_master_tb is
constant TCLK : time := 10 ns;
constant TADDR : std_logic_vector(6 downto 0) := "1010000"; -- 0x50
constant MAXP : integer := 20;
constant ST_IDLE : integer := 0;
constant ST_START : integer := 1;
constant ST_ADDR : integer := 2;
constant ST_STOP : integer := 3;
constant ST_DONE : integer := 4;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal begin_poll : std_logic := '0';
signal poll_tick : std_logic := '0';
signal send_start, send_addr, send_stop : std_logic;
signal ready, gave_up, outcome_known : std_logic;
signal polls_used, total_polls, sequences : unsigned(15 downto 0);
signal st_o : unsigned(2 downto 0);
-- the bench's model of the target
signal refuse_for : integer := 0;
signal polls_seen : integer := 0;
signal target_acks : std_logic;
signal n_starts, n_addrs, n_stops : integer := 0;
-- polls_seen only ever counts up, because it has exactly one driver and that
-- driver is the counting process. A new sequence therefore does not clear it;
-- the stimulus moves poll_base instead, which it owns outright. That keeps the
-- retarget free of clock cycles, so the VHDL run stays in step with the
-- SystemVerilog bench, where a blocking assignment costs nothing.
signal poll_base : integer := 0;
signal halt : boolean := false;
begin
-- The target declines polls 1..refuse_for and answers poll refuse_for+1.
target_acks <= '1' when (polls_seen - poll_base) > refuse_for else '0';
dut : entity work.i2c_ack_poll_master
generic map (TARGET_ADDR => TADDR, MAX_POLLS => MAXP, CNT_W => 16)
port map (clk => clk, rst_n => rst_n,
begin_poll => begin_poll, poll_tick => poll_tick,
addr_acked => target_acks,
send_start => send_start, send_addr => send_addr, send_stop => send_stop,
ready => ready, gave_up => gave_up, outcome_known => outcome_known,
polls_used => polls_used, total_polls => total_polls,
sequences => sequences, state => st_o);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for TCLK/2;
clk <= '1'; wait for TCLK/2;
end loop;
wait;
end process;
-- Count what the master actually drives, from the wire, on the negedge. These
-- signals have exactly one driver, this process, as VHDL requires for integers.
counters : process (clk, rst_n)
begin
if rst_n = '0' then
polls_seen <= 0;
n_starts <= 0;
n_addrs <= 0;
n_stops <= 0;
elsif falling_edge(clk) then
if send_addr = '1' then
polls_seen <= polls_seen + 1;
n_addrs <= n_addrs + 1;
end if;
if send_start = '1' then n_starts <= n_starts + 1; end if;
if send_stop = '1' then n_stops <= n_stops + 1; end if;
end if;
end process;
stim : process
variable err : integer := 0;
variable n : integer;
variable r1, g1, k1 : std_logic;
variable p1 : integer;
procedure ck_int (what : string; g : integer; e : integer) is
begin
if g /= e then
report " FAIL " & what & ": got " & integer'image(g)
& " expected " & integer'image(e) severity note;
err := err + 1;
end if;
end procedure;
procedure ck_bit (what : string; g : std_logic; e : std_logic) is
begin
if g /= e then
report " FAIL " & what & ": got " & std_logic'image(g)
& " expected " & std_logic'image(e) severity note;
err := err + 1;
end if;
end procedure;
procedure step is
begin
wait until rising_edge(clk); wait until falling_edge(clk);
end procedure;
procedure do_reset (refuse : integer) is
begin
wait until falling_edge(clk);
rst_n <= '0'; begin_poll <= '0'; poll_tick <= '0';
refuse_for <= refuse; poll_base <= 0;
for k in 0 to 2 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk);
rst_n <= '1';
step;
end procedure;
procedure kick is
begin
wait until falling_edge(clk); begin_poll <= '1';
wait until rising_edge(clk); wait until falling_edge(clk); begin_poll <= '0';
end procedure;
procedure run_polls (max_ticks : integer) is
begin
n := 0;
while to_integer(st_o) /= ST_DONE and n < max_ticks loop
wait until falling_edge(clk); poll_tick <= '1';
wait until rising_edge(clk); wait until falling_edge(clk);
poll_tick <= '0';
step;
n := n + 1;
end loop;
if n >= max_ticks then
report " FAIL run_polls: never reached ST_DONE" severity note;
err := err + 1;
end if;
end procedure;
begin
report "=== i2c_ack_poll_master: asking 'are you ready' with the only question there is ==="
severity note;
-- T1. A ready device answers on the first poll.
do_reset(0);
kick;
run_polls(100);
report "T1 a ready device answers on the first poll" severity note;
ck_bit("T1 ready", ready, '1');
ck_bit("T1 did not give up", gave_up, '0');
ck_bit("T1 the outcome is known", outcome_known, '1');
ck_int("T1 one poll used", to_integer(polls_used), 1);
-- T2. A device busy for five polls.
do_reset(5);
kick;
run_polls(100);
report "T2 a device busy for five polls, and the count says so" severity note;
ck_bit("T2 ready", ready, '1');
ck_int("T2 six polls used", to_integer(polls_used), 6);
ck_bit("T2 known outcome", outcome_known, '1');
-- T3. A poll is a START, an address and a STOP -- nothing more.
do_reset(2);
kick;
run_polls(100);
report "T3 a poll is a START, an address and a STOP -- nothing more"
severity note;
ck_int("T3 one START per poll", n_starts, to_integer(polls_used));
ck_int("T3 one address per poll", n_addrs, to_integer(polls_used));
ck_int("T3 one STOP per poll", n_stops, to_integer(polls_used));
ck_int("T3 three polls were needed", to_integer(polls_used), 3);
-- T4. Busy for one less than the bound: still succeeds.
do_reset(MAXP - 1);
kick;
run_polls(200);
report "T4 busy for one less than the bound: still succeeds" severity note;
ck_bit("T4 ready", ready, '1');
ck_bit("T4 did not give up", gave_up, '0');
ck_int("T4 used the bound", to_integer(polls_used), MAXP);
-- T5. A device that never answers: bounded, and honest about it.
do_reset(9999);
kick;
run_polls(200);
report "T5 a device that never answers: bounded, and honest about it"
severity note;
ck_bit("T5 not ready", ready, '0');
ck_bit("T5 gave up", gave_up, '1');
ck_bit("T5 the outcome is NOT known", outcome_known, '0');
ck_int("T5 stopped exactly at the bound", to_integer(polls_used), MAXP);
-- T6. Absent, wedged and still-busy are indistinguishable.
do_reset(9999);
kick;
run_polls(200);
r1 := ready; g1 := gave_up; k1 := outcome_known; p1 := to_integer(polls_used);
do_reset(9999);
kick;
run_polls(200);
report "T6 absent, wedged and still-busy are indistinguishable" severity note;
ck_bit("T6 same ready", ready, r1);
ck_bit("T6 same gave_up", gave_up, g1);
ck_bit("T6 same known", outcome_known, k1);
ck_int("T6 same poll count", to_integer(polls_used), p1);
-- T7. Giving up never claims to know why.
report "T7 giving up never claims to know why" severity note;
ck_bit("T7 gave up", gave_up, '1');
ck_bit("T7 and says the cause is unknown", outcome_known, '0');
-- T8. No tick, no poll: the caller paces it.
do_reset(3);
kick;
for k in 0 to 19 loop step; end loop;
report "T8 no tick, no poll: the caller paces it" severity note;
ck_int("T8 no polls issued", to_integer(polls_used), 0);
ck_int("T8 waiting for a tick", to_integer(st_o), ST_START);
run_polls(100);
ck_bit("T8 and it completes once ticked", ready, '1');
-- T9. Per-sequence and running counts are separate.
do_reset(2);
kick; run_polls(100);
ck_int("T9 first sequence used three", to_integer(polls_used), 3);
wait until falling_edge(clk);
refuse_for <= 4; poll_base <= polls_seen;
kick; run_polls(100);
report "T9 per-sequence and running counts are separate" severity note;
ck_int("T9 second sequence used five", to_integer(polls_used), 5);
ck_int("T9 eight polls in total", to_integer(total_polls), 8);
ck_int("T9 two sequences", to_integer(sequences), 2);
-- T10. A give-up is not sticky.
do_reset(9999);
kick; run_polls(200);
ck_bit("T10 gave up first time", gave_up, '1');
wait until falling_edge(clk);
refuse_for <= 1; poll_base <= polls_seen;
kick; run_polls(200);
report "T10 a give-up is not sticky: the retry can succeed" severity note;
ck_bit("T10 ready on the retry", ready, '1');
ck_bit("T10 gave_up cleared", gave_up, '0');
ck_bit("T10 outcome known again", outcome_known, '1');
ck_int("T10 two polls on the retry", to_integer(polls_used), 2);
-- T11. The bound is a policy choice, not a device property.
report "T11 the bound is a policy choice, not a device property" severity note;
ck_int("T11 this instance's bound is its parameter", MAXP, 20);
do_reset(25);
kick; run_polls(200);
ck_bit("T11 fails under the tighter bound", gave_up, '1');
ck_bit("T11 and says so honestly", outcome_known, '0');
-- T12. Idle means silent.
do_reset(0);
report "T12 idle means silent" severity note;
for k in 0 to 14 loop
wait until falling_edge(clk); poll_tick <= '1';
wait until rising_edge(clk); wait until falling_edge(clk);
ck_bit("T12 no START while idle", send_start, '0');
ck_bit("T12 no address while idle", send_addr, '0');
poll_tick <= '0';
end loop;
ck_int("T12 still idle", to_integer(st_o), ST_IDLE);
ck_int("T12 no polls", to_integer(total_polls), 0);
if err = 0 then
report "=== i2c_ack_poll_master: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_ack_poll_master: " & integer'image(err)
& " CHECK(S) FAILED ===" severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;6a. Decisions Worth Defending
A poll is a START, an address and a STOP — nothing else. send_start, send_addr, send_stop, one of each per poll, and no data path at all. §4 argues why; mutation D2-1 drops the STOP and mutation D2-2 drops the START, and each is killed by the test that counts all three.
outcome_known goes LOW when the master gives up. This is the design's central decision and it is not a convenience flag. Because §1's three causes are indistinguishable, a design that reported only gave_up would be handing its caller a failure with an implied diagnosis. Mutation D2-3 claims the outcome is known there, and three checks fail.
The bound is a sized localparam, not a part-select of a parameter. POLL_LIMIT is MAX_POLLS widened to the counter's width. A part-select of a parameter evaluates to zero on some tools, which would make the master give up on its first poll and look like a device that is never ready.
One poll per tick, so the polling rate is the caller's decision. S_START waits for poll_tick. Polling flat out is legal and wastes bus bandwidth that the other devices on the bus could be using — and on a shared bus, a master polling at full speed for the duration of a write cycle is holding the bus for milliseconds. Mutation D2-10 ignores the tick and two checks fail.
polls_used and total_polls are separate counters answering separate questions. The first is "how long did this write take"; the second is "how much polling does this system do". A system whose per-write count is fine but whose running total is enormous is a system doing far more writes than anyone intended. Mutation D2-8 resets the total per sequence and one check fails.
A give-up is not sticky. S_DONE clears gave_up when a new sequence begins, because a device busy for longer than the bound may well answer on the next attempt — and reporting it as permanently failed would be exactly the diagnosis §3 says the master cannot make. Mutation D2-6 makes it sticky and is killed.
The bound is checked after the acknowledge, not before it. S_STOP tests addr_acked first and polls_used >= POLL_LIMIT second, so a device that answers on the very last permitted poll succeeds. Ordering those the other way turns the bound from "at most this many" into "fewer than this many", and mutation D2-5 is that off-by-one.
Idle means silent. With no begin_poll, ticks arrive and nothing is driven. A polling block that emitted an addressing whenever a tick arrived would be generating bus traffic nobody asked for, and test 12 asserts it does not.
6b. Verified Execution
$ iverilog -g2012 -o d i2c_ack_poll_master.sv i2c_ack_poll_master_tb.sv && ./d
=== i2c_ack_poll_master: asking 'are you ready' with the only question there is ===
T1 a ready device answers on the first poll
T2 a device busy for five polls, and the count says so
T3 a poll is a START, an address and a STOP -- nothing more
T4 busy for one less than the bound: still succeeds
T5 a device that never answers: bounded, and honest about it
T6 absent, wedged and still-busy are indistinguishable
T7 giving up never claims to know why
T8 no tick, no poll: the caller paces it
T9 per-sequence and running counts are separate
T10 a give-up is not sticky: the retry can succeed
T11 the bound is a policy choice, not a device property
T12 idle means silent
=== i2c_ack_poll_master: ALL CHECKS PASSED ===
$ iverilog -g2005 -o v i2c_ack_poll_master.v i2c_ack_poll_master_tb.v && ./v
=== i2c_ack_poll_master: asking 'are you ready' with the only question there is ===
T1 a ready device answers on the first poll
T2 a device busy for five polls, and the count says so
T3 a poll is a START, an address and a STOP -- nothing more
T4 busy for one less than the bound: still succeeds
T5 a device that never answers: bounded, and honest about it
T6 absent, wedged and still-busy are indistinguishable
T7 giving up never claims to know why
T8 no tick, no poll: the caller paces it
T9 per-sequence and running counts are separate
T10 a give-up is not sticky: the retry can succeed
T11 the bound is a policy choice, not a device property
T12 idle means silent
=== i2c_ack_poll_master: ALL CHECKS PASSED ===
$ nvc --std=2008 -a i2c_ack_poll_master.vhd i2c_ack_poll_master_tb.vhd
$ nvc --std=2008 -e i2c_ack_poll_master_tb && nvc --std=2008 -r i2c_ack_poll_master_tb --stop-time=300us
=== i2c_ack_poll_master: asking 'are you ready' with the only question there is ===
T1 a ready device answers on the first poll
T2 a device busy for five polls, and the count says so
T3 a poll is a START, an address and a STOP -- nothing more
T4 busy for one less than the bound: still succeeds
T5 a device that never answers: bounded, and honest about it
T6 absent, wedged and still-busy are indistinguishable
T7 giving up never claims to know why
T8 no tick, no poll: the caller paces it
T9 per-sequence and running counts are separate
T10 a give-up is not sticky: the retry can succeed
T11 the bound is a policy choice, not a device property
T12 idle means silent
=== i2c_ack_poll_master: ALL CHECKS PASSED ===6c. What The Testbench Proves
| # | scenario | what it establishes |
|---|---|---|
| 1 | a device ready immediately | one poll; success; outcome known |
| 2 | a device busy for five polls | six polls; the count says so |
| 3 | the shape of a poll | one START, one address, one STOP — per poll |
| 4 | busy for exactly one poll less than the bound | succeeds on the last permitted poll — the bound is inclusive |
| 5 | a device that never answers | stops exactly at the bound; outcome not known |
| 6 | absent versus wedged versus busy | identical outputs; nothing distinguishes them |
| 7 | the give-up report | gave_up high and outcome_known low, together |
| 8 | no tick | no polls at all; the caller sets the pace |
| 9 | two sequences in a row | per-sequence and running counts are separate |
| 10 | a give-up followed by a retry | not sticky; the retry succeeds |
| 11 | a device busy for 25 polls, bound 20 | fails — and the same device succeeds under a larger bound |
| 12 | idle with ticks arriving | drives nothing at all |
Tests 1, 2, 4 and 5 are the same mechanism with the release at four different points, and together they pin the bound down as inclusive: succeed on the first poll, succeed after five, succeed on the last permitted poll, fail when the release never comes.
Test 6 is the chapter's central claim and it is built as a comparison rather than an assertion. It runs an "absent" device and a "wedged" device and asserts that every output matches — ready, gave_up, outcome_known, polls_used. Asserting that two runs are identical is a stronger statement than asserting each has some expected value, because it is a statement about what the master cannot know rather than about what it reports.
Test 11 runs §2's callout. A device busy for 25 polls against a bound of 20 gives up; the design's parameter is the only thing that decided it. That test is the reason the bound is a parameter rather than a constant.
Test 12 asserts three zeros. No START, no address, no poll count, with ticks arriving throughout. A block that polled whenever it was ticked would pass every other test in the table.
7. Mutation Testing
Twelve defects injected into the SystemVerilog polling master.
| # | injected defect | outcome |
|---|---|---|
| D2-1 | each poll left unterminated; no STOP | killed — test 3 |
| D2-2 | the target re-addressed without a fresh START | killed — test 3 |
| D2-3 | the outcome claimed known after giving up | killed — 3 checks |
| D2-4 | no bound at all; poll forever | killed — 13 checks |
| D2-5 | the bound overshot by one poll | killed — test 5 |
| D2-6 | a give-up made sticky | killed — test 10 |
| D2-7 | the per-sequence count carried into the next sequence | killed — 5 checks |
| D2-8 | the running total reset at each new sequence | killed — test 9 |
| D2-9 | a retry not counted as a new sequence | killed — test 9 |
| D2-10 | the pacing tick ignored; polling flat out | killed — 2 checks |
| D2-11 | ready asserted without an acknowledge | killed — 16 checks |
| D2-12 | the address driven but the poll not counted | killed — 22 checks |
Twelve of twelve, with no survivors and no equivalences — the only design in the module where that was true on the first run. Three observations about why.
D2-3 is the most important mutation in the module and it kills with only three checks. It changes one bit in one branch: the outcome is reported as known rather than unknown when the bound is reached. Nothing functional changes — the master still stops at the bound, still reports gave_up, still drives the same waveform. The only difference is whether the design claims to understand what it saw.
Three checks is thin evidence for the chapter's central property, and that thinness is the honest situation. A design's honesty is not visible in its behaviour, so the only thing that can test it is an assertion aimed directly at the claim.
D2-4 kills with thirteen checks because an unbounded loop is caught by the harness, not by an assertion. With no bound the master never reaches S_DONE, the bench's run_polls loop hits its own limit, and every subsequent test runs from a wrong state. That is what an infinite loop looks like in a testbench that bounds its waits — and it is the argument for bounding every wait in a bench, which this curriculum has made before.
D2-12's twenty-two checks come from a single missing increment. Driving the address without counting the poll means the bound is never reached, so every success test reports the wrong count and every failure test hangs. Breadth of failure measures how central a signal is, not how serious the defect is: compare D2-3's three.
8. Verification Connection — Testing a Negative, and the Cover Bin That Proves You Tried
Everything this chapter is about happens when a device does not respond, which makes it a verification problem of a specific and awkward shape: the interesting stimulus is an absence.
// The properties of a bounded polling loop. Note what they are NOT: none of them
// says the device becomes ready, because the device becoming ready is not the
// master's obligation. They say the master is bounded, framed, and honest.
interface i2c_ack_poll_props (input logic clk, input logic rst_n,
input logic send_start, input logic send_addr,
input logic send_stop, input logic addr_acked,
input logic ready, input logic gave_up,
input logic outcome_known,
input logic [15:0] polls_used,
input int poll_limit);
default clocking cb @(posedge clk); endclocking
default disable iff (!rst_n);
// 1. BOUNDEDNESS. The poll count never exceeds the configured limit. This is
// the property that turns a loop into a design: without it the block is
// correct on every working system and hangs on one broken one.
a_bounded : assert property (polls_used <= poll_limit)
else $error("poll count %0d exceeded the bound %0d", polls_used, poll_limit);
// 2. HONESTY. Giving up and claiming to know the cause are mutually exclusive.
// This is the chapter's central claim written as one line, and it is the
// property mutation D2-3 breaks.
a_honest : assert property (gave_up |-> !outcome_known)
else $error("gave up and claimed the outcome was known");
// 3. Ready and gave_up are exclusive. A design that set both would be
// reporting a contradiction, and a caller checking only one of them would
// act on it.
a_exclusive : assert property (!(ready && gave_up));
// 4. FRAMING. Every address is preceded by a START and followed by a STOP.
// Expressed as: an address byte is never driven in the same cycle as a
// START, and a STOP always follows within a bounded window.
a_framed : assert property (send_addr |=> ##[0:3] send_stop)
else $error("a poll was not closed with a STOP");
// 5. And ready requires an acknowledge to have been seen. Without this,
// mutation D2-11 -- ready asserted unconditionally -- satisfies every
// property above.
a_ready_earned : assert property ($rose(ready) |-> $past(addr_acked));
endinterface
// The coverage. THIS is the load-bearing part, and it is why an assertion-only
// approach fails here: every assertion above passes on a bus where the device is
// always ready immediately, because none of the interesting situations occurs.
covergroup poll_cg @(posedge clk);
option.per_instance = 1;
// How many polls it took. The zero bin and the at-the-bound bin are the two
// that matter, and a regression against a healthy model will only ever hit
// the first.
cp_polls : coverpoint polls_used iff (ready || gave_up) {
bins first = {1};
bins few[3] = {[2:9]};
bins many[2] = {[10:19]};
bins at_the_bound = {20};
}
// And the outcome. `gave_up_unknown` is the bin that says the bench actually
// exercised the failure path. An empty bin here means every property above
// passed vacuously and the chapter's subject was never tested.
cp_outcome : coverpoint {ready, gave_up, outcome_known} {
bins ok = {3'b101}; // ready, not gave up, known
bins gave_up_unkn = {3'b010}; // not ready, gave up, NOT known
illegal_bins both = {3'b110, 3'b111};
}
x_outcome_polls : cross cp_outcome, cp_polls;
endgroupFour points, and the warning that matters most.
None of the properties says the device becomes ready. That is not the master's obligation and cannot be asserted about it. What can be asserted is that the master is bounded, framed, honest, and does not contradict itself — four properties that are entirely about the master's own conduct.
Property 2 is one line and it is the chapter. gave_up |-> !outcome_known. A design that violates it is not functionally broken; it is dishonest, and the distinction is invisible in every waveform.
Property 5 exists because properties 1 through 4 pass on a design that asserts ready unconditionally. Without $past(addr_acked), the most severe mutation in the table satisfies every other assertion. A suite of properties needs one that ties the output to the input that earns it.
The gave_up_unkn bin is the bench's own honesty check. If it is empty, the failure path never ran and every assertion above passed vacuously.
8a. Where the Polling Policy Belongs in a UVM Environment
The properties above verify a polling master. A UVM environment has a different problem: every register write to this device has to be followed by a poll, and if that lives in the tests it will be forgotten in one of them.
Chapter 16.1 §8 built a uvm_reg model for a register map. The natural home for acknowledge polling is a frontdoor on that model — an object that replaces the default access path for a register, so the polling happens on every write through the model without any test having to know about it.
// The bound is a policy number, so it belongs in configuration rather than in
// code. One object per device, published through uvm_config_db, means a bench
// that talks to a fast EEPROM and a slow one can give each its own bound --
// which section 9 argues is necessary and a single constant cannot express.
class i2c_poll_cfg extends uvm_object;
`uvm_object_utils(i2c_poll_cfg)
int unsigned max_polls = 20; // the bound
int unsigned poll_gap_ns = 100; // the pacing interval
function new(string name = "i2c_poll_cfg"); super.new(name); endfunction
endclass
// What the environment learns from each poll sequence. Published on an analysis
// port rather than logged, because the poll COUNT is the early warning for a
// degrading part and a subscriber can trend it across a whole regression.
class i2c_poll_result extends uvm_object;
`uvm_object_utils(i2c_poll_result)
int unsigned polls_used;
bit ready;
bit outcome_known; // LOW when the sequence gave up
function new(string name = "i2c_poll_result"); super.new(name); endfunction
endclass
// A frontdoor replaces the default register access path. Every write through the
// register model therefore polls, and no test can forget to -- which is the
// point: a policy that has to be remembered is a policy that will be skipped.
class i2c_poll_frontdoor extends uvm_reg_frontdoor;
`uvm_object_utils(i2c_poll_frontdoor)
i2c_poll_cfg cfg;
uvm_analysis_port #(i2c_poll_result) ap;
function new(string name = "i2c_poll_frontdoor");
super.new(name);
ap = new("ap", null);
endfunction
virtual task body();
i2c_poll_result res = i2c_poll_result::type_id::create("res");
int unsigned n = 0;
bit acked = 0;
// 1. The write itself, through the ordinary sequencer.
do_reg_write();
// 2. Then poll: START, address, STOP, paced, bounded. Nothing else --
// section 4: a data byte would take effect if the device had just
// become ready.
while (!acked && n < cfg.max_polls) begin
#(cfg.poll_gap_ns * 1ns);
do_address_only_poll(acked);
n++;
end
res.polls_used = n;
res.ready = acked;
// The honesty requirement of section 3, carried into the environment: an
// exhausted bound does NOT mean the device is absent, so the result says
// the outcome is unknown rather than naming a cause.
res.outcome_known = acked;
ap.write(res);
// And the status handed back to uvm_reg must distinguish "the write was
// not confirmed" from "the write failed". UVM_NOT_OK is the closest the
// API offers; the analysis port carries the distinction that matters.
if (!acked)
`uvm_warning("POLL",
$sformatf("write not confirmed after %0d polls; cause unknown", n))
endtask
// These two are environment-specific: they push items to the I2C sequencer.
protected virtual task do_reg_write(); endtask
protected virtual task do_address_only_poll(output bit acked); endtask
endclassThree points about this structure.
A frontdoor makes the policy unforgettable. Attaching it once with reg.set_frontdoor(fd) means every write through the register model polls afterwards. The alternative — a poll_until_ready() task the tests call — is correct in every test that calls it and silently wrong in the one that does not.
The bound belongs in a config object, not in the frontdoor. §9: write-cycle times vary by more than a factor of ten across parts, so a bench that talks to two devices needs two bounds. Publishing an i2c_poll_cfg per device through uvm_config_db is how that becomes a property of the device rather than of the code.
The poll count goes to an analysis port, not to a log line. A subscriber can trend it across a regression and flag a part whose count is climbing — which §3 argues is the only early warning available. A uvm_info message is read by nobody after the run that produced it.
9. FPGA and ASIC Implications
The bound is a register, not a constant, if the same master talks to more than one part. Write-cycle times vary by an order of magnitude across devices — a fast EEPROM and a slow one differ by more than a factor of ten — and a single bound sized for the slowest wastes time on the fastest while a bound sized for the fastest fails on the slowest.
Pace the polls, and pick the interval deliberately. Polling flat out is legal and, on a shared bus, monopolises it for the duration of the write cycle. A poll every 100 µs on a 3 ms write cycle costs thirty polls and leaves the bus available 99% of the time; polling continuously costs hundreds and leaves it available almost never.
The poll's transaction must be the address-only form, and that is a hardware constraint on the sequencer. If the polling path reuses the normal transfer engine, it has to be able to issue an addressing with a zero-length payload — which some engines cannot, because their state machines assume at least one data byte follows. That is worth checking before the polling loop is written, not after.
Report the count and log it. polls_used distinguishes a device answering on poll 2 from one answering on poll 380, and the second is a part degrading. It costs a counter and a status register field, and it is the only early warning available for a failing EEPROM.
Do not treat a failed poll as a device fault in the system's own logic. §1: it may be a device that needs longer than the bound allows. A system that marks the device permanently absent after one failed poll sequence will never talk to it again, when a retry a second later would have worked. §6c's test 10 is exactly this.
And distinguish the timeout from the retry policy. The bound says how long one wait lasts. Whether to try again, how many times, and with what backoff are separate decisions, and conflating them produces either a system that gives up too early or one that hangs in a loop of loops.
10. Debugging — The EEPROM That Was Absent Every Third Boot
A product writes a boot counter to an EEPROM at startup, then polls for the write to complete before continuing. On roughly one boot in four, the poll fails and the firmware logs 'EEPROM not responding' and continues with defaults. The unit works normally otherwise, and the EEPROM's contents are correct when read on a later boot -- including the counter value from the boot that reported a failure.
The bound was expressed in polls and was far shorter than the device's specified write cycle. Fifty back-to-back polls at 400 kHz is 1.25 milliseconds against a 5 millisecond maximum write time, so the loop could not succeed unless something else delayed the first poll. The intermittency came from variable startup work between the STOP and the first poll: adding the 1.25 ms window to a 1 to 4 ms delay clears a 3 ms write cycle only when that delay exceeded about 1.75 ms. Compounding it, the firmware reported the exhausted bound as 'not responding' -- a diagnosis it had no basis for, since a busy device and an absent one are indistinguishable. The write always succeeded; only the master's report of it failed.
Size the bound in time rather than in polls, against the datasheet's maximum write cycle time plus margin -- and pace the polls rather than issuing them back to back, which both reduces bus occupancy and makes the bound's duration obvious in the code. Then change the report: an exhausted bound means the write could not be CONFIRMED, cause unknown. It does not mean the device is absent and must not be logged as though it were, because in this system it never was.Three things generalise.
A bound counted in polls is a bound whose duration depends on the bus speed and the poll rate. Fifty polls is 1.25 ms at 400 kHz and 5 ms at 100 kHz — so the same code would have worked on a slower bus, which is the kind of dependency that makes a bug appear during a performance optimisation.
The intermittency was caused by unrelated startup work and looked like a hardware fault. Variable delay between the STOP and the first poll is invisible in the driver and decided the outcome.
"EEPROM not responding" was the most expensive line in the log. It sent two engineers to look at connectors and an aging EEPROM. The honest line — "write could not be confirmed within the timeout; cause unknown" — would have pointed at the timeout, which is where the bug was. §3.
11. Common Misconceptions
"A device signals that it is busy." It stops answering. There is no busy flag, because reading one would require an acknowledge. §0 and Chapter 16.3 §3.
"An unanswered poll means the device is absent." It means the device did not acknowledge. Busy, absent and wedged are indistinguishable. §1.
"Polling long enough will distinguish a busy device from an absent one." No finite wait can, because the specification defines no write-cycle time for the wait to be longer than. §1.
"The specification defines a write-cycle timeout." It defines nothing about write cycles at all. Every bound is the designer's number. §2.
"If the poll succeeds under a large bound and fails under a small one, one of the bounds is wrong." Neither is, because there is no specified number for either to be wrong about. §2.
"A poll can carry a pointer byte to save a transaction." If the device becomes ready mid-poll, the pointer lands and the poll has changed the device's state as a side effect. §4.
"A poll does not need a STOP, since nothing was transferred." An unterminated poll leaves every device believing a transfer is in progress. §4.
"A failed poll sequence means the device should be marked absent." It may need longer than the bound allowed. A retry may well succeed, and a give-up must not be sticky. §6a and §9.
"Reporting gave_up is enough." It implies a cause the master cannot know. outcome_known going low is the design declining to diagnose. §3.
"A poll count is a debug convenience." It is the only early warning available for a degrading part: a device answering on poll 380 rather than poll 2 is a system about to fail. §3 and §9.
"Assertions on a polling loop prove it works." Boundedness, honesty and exclusivity are all conditional on the failure occurring. On an always-ready model every one of them passes vacuously. §8.
12. Reason It Through
A poll goes unanswered. What does the master now know?
That no device pulled SDA low during the acknowledge slot. Nothing more — and in particular not whether the device is busy, absent or wedged, because all three produce exactly that. §1.
Why can no amount of waiting distinguish a busy device from an absent one?
Because distinguishing them would require knowing that the wait exceeded the longest possible write cycle, and the specification defines no write-cycle time. The datasheet does, but a datasheet is not something the master can read off the bus. §1 and §2.
A device busy for 25 polls fails under a bound of 20 and succeeds under a bound of 200. Which bound is correct?
Neither, in any absolute sense. The bound is a policy decision and the specification provides no number for it to match. What is incorrect is reporting the failure under the small bound as "the device is not responding", which asserts a cause. §2 and §3.
Why must outcome_known be a separate output from gave_up?
Because they answer different questions: whether the poll succeeded, and whether the master understands why. Collapsing them into one signal forces the caller to infer a cause from a failure, which is exactly the inference §1 shows is unavailable.
Why must a poll carry no data byte?
Because a data byte has an effect if the device happens to have just become ready. The master would then have changed the device's state as a side effect of asking whether it was available, and the question would no longer be repeatable. §4.
A polling loop is bounded at 50 back-to-back polls and the device's write cycle is 5 ms. What is wrong?
Fifty polls at 400 kHz is about 1.25 ms, so the bound expires long before the device can be ready. The loop can only succeed if something delays the first poll — which makes the outcome depend on unrelated timing and look intermittent. The bound should be sized in time, against the datasheet's maximum. §10.
A property says gave_up |-> !outcome_known and passes in every regression. What should you check before believing it?
Whether gave_up ever went high. The property is conditional on the failure occurring, and on a model where the device is always ready it passes without ever being evaluated. The cover bin for a give-up is what makes the assertion meaningful. §8.
13. Understanding Check
14. Summary
A device in its internal write cycle acknowledges nothing, and there is no status register to read because reading one would require an acknowledge.
So the master polls: START, address, STOP, repeatedly, until the device answers. It works because a device must decode its address on every START, and because not acknowledging is a device's only way of declining.
And a poll that goes unanswered is consistent with three completely different situations — busy, absent, wedged — which are indistinguishable by construction, because the acknowledge is one bit and there is no second channel.
Therefore the loop must be bounded, and the bound is a policy decision in the same sense a stretch timeout is: the specification states no write-cycle time, so any number is the designer's.
The same device gets two different verdicts under two different bounds, and neither bound is wrong.
The honest report has two parts: the poll failed, and the cause is unknown. A design that reports only failure hands its caller an implied diagnosis it has no basis for.
And the poll count is worth reporting. A device answering on poll 2 and one answering on poll 380 are both successes and not the same system.
A poll carries nothing but an address, because a data byte would take effect if the device had just become ready — and every poll is closed with a STOP, or it breaks the bus it was checking.
A give-up must not be sticky. The device may simply need longer than the bound allowed.
And every assertion about a polling loop passes vacuously on a device that is always ready. The stimulus that matters is an absence, which no functional test needs and no random generator produces by accident — so the cover bin for a give-up is what makes the whole property suite mean anything.
15. What Comes Next
The device conventions so far have all concerned an address: where the pointer is, how wide it is, which page it lives in, when the device will answer. Chapter 16.5 turns to the devices where the data itself is the problem.
A sensor's measurement is wider than a byte, so reading it takes two transfers — and the sensor keeps measuring in between. A master that reads the high byte of one sample and the low byte of the next assembles a number that neither sample ever held, and at a carry boundary the error is enormous: high byte of 0x00FF and low byte of 0x0100 assemble to 0x0000, which is 255 counts below both.
Every byte is acknowledged, the transfer is well formed, and the value is plausible. Which is the module's recurring shape, one last time — and the remedy is a device convention rather than a protocol mechanism.
Continue learning
Related tutorials
- Related topic
Repeated START — Holding the Bus Between Phases
A repeated START is not a new waveform. It is the START edge again, and what makes it a different event is that the bus was already busy. That single fact is why a classifier needs state and why a monitor that joins late cannot classify what it sees.
- Related topic
10-Bit Addressing
A two-byte addressing mode built entirely out of reserved space, coexisting with seven-bit devices on the same wires. Its first byte is deliberately not unique, and a read has to re-address with only one byte — which is why a 10-bit slave needs memory a 7-bit slave does not.
- Related topic
Multi-Byte I²C Writes — Timing, Throughput and Waveforms
A three-byte write spends a third of its bus time on overhead. This chapter derives the closed forms for what a burst costs, computes the real byte rate at both standard speeds, reads an annotated burst capture, and builds a passive instrument that measures all of it in hardware.
- Related topic
Multi-Byte I²C Reads — ACK Policy, Timing and Waveforms
A well-formed read of n bytes contains exactly n−1 ACKs and one NACK, always last. That pattern is narrow enough to check in hardware — and narrow enough to predict a bus hang before the STOP that cannot form is even attempted.
