I²C · Module 6
Address Decoding Inside a Slave
The first piece of slave hardware in the curriculum, and it is assembled rather than written: byte capture, reserved-map classification, one equality test, and a decision whose ordering matters — because a prohibition has to beat an address match.
Four chapters have described addressing from outside a device: the byte on the wire, the two-byte extension, the reserved map, and how a board assigns addresses. Every one of them assumed a device that watches the bus and recognises its own address.
This chapter builds it. It is the first slave-side hardware in the curriculum, and the most interesting thing about it is how little of it is new — three of its four parts already exist, and the part that does not is a decision with an ordering that the specification never states.
1. Four Stages, Three of Them Already Built
The decoder's job decomposes cleanly, and the decomposition is the design:
| stage | question | where it came from |
|---|---|---|
| framing | has a transfer begun, and is this byte an address? | Module 5 — S, Sr and P detection |
| capture | what are the eight bits? | Chapter 6.1 — i2c_address_byte_capture |
| classify | what kind of address is it? | Chapter 6.3 — i2c_address_classifier |
| decide | should this device respond, and how? | this chapter |
So three stages are instantiations and one is new logic. That is worth dwelling on, because the alternative — one module that shifts, decodes the reserved map, and decides, all in one always block — is how this block is usually written and is considerably worse: the reserved-map decode becomes unreviewable against the table, the capture's edge detection gets tangled with the decision, and none of the three can be verified in isolation.
2. The Ordering Rule
The decision stage evaluates three conditions, and the order is not arbitrary:
if (must_not_ack) -> do NOT respond <-- FIRST, always
else if (own_match) -> respond, latch direction
else if (gen_call_match) -> respond as a broadcast receiver
else -> stay silent
Why the prohibition is first:
[Chapter 6.3] established that two Table 3 footnotes forbid responding --
the START byte and the CBUS address -- and that neither makes an exception
for a device that happens to hold that address. A device strapped to the
CBUS address by a board error, or assigned it deliberately under the
specification's escape hatch, still must not answer it.
So `own_match` being true is NOT sufficient to respond. Checking the match
first and the prohibition afterwards produces a device that is compliant in
every ordinary case and non-compliant in exactly the case the footnote was
written for.
Why the general call is last:
It is the broadest condition -- it matches an address no device owns -- so it
must not shadow a specific match. It cannot in practice, because the general
call address and a device's own address are different addresses, but keeping
the specific case ahead of the broad one is the habit that survives a future
edit adding a third broad case.That first rule is the single most important line in the chapter, and it is invisible in the specification: Table 3's footnotes and the address-matching requirement live in different sections and nothing says which wins. The priority has to be derived from what the footnotes are for.
3. What "Respond" Means Here — and What It Does Not
The decoder's response output is ack_request: a one-cycle pulse meaning this device wants to acknowledge the byte just seen. It is deliberately not an SDA driver.
The distinction matters because the acknowledge is a bus cycle, not a signal level. It occupies the ninth clock pulse of the byte, the receiver drives SDA low during it, and it has its own timing obligations and its own failure modes. All of that is Module 7, and putting it in the decoder would mean the decoder could not be verified without also modelling the ninth clock.
So the boundary is:
| this chapter owns | Module 7 owns |
|---|---|
| deciding whether to acknowledge | driving the acknowledge in the ninth clock slot |
addressed and direction as state | what a transmitter does with direction |
| releasing on P or a foreign Sr | acknowledging data bytes |
| the general call as a build option | what the general call's second byte means |
direction is worth one note. It is latched from the eighth bit and it says what the master asked for, not what this device will do about it. A device that is addressed for a read has to become a transmitter, and that is the byte-transfer layer's problem — but it cannot start until this stage has told it which way the transfer goes, which is why direction is an output of addressing rather than of data handling.
4. Releasing the Device
Two release conditions, both inherited directly from Module 5:
A STOP releases unconditionally. Chapter 5.3 established that a STOP ends the transfer for every device on the segment.
A framing START — including a repeated START — also releases. This is the one that looks optional and is not. Chapter 5.4 established that a repeated START does not release the bus, so nothing else is going to tell this device to let go. If a device stayed addressed across an Sr, it would remain addressed while another device's address was being transmitted, and anything downstream acting on addressed would be acting on a stale claim.
The subtlety is when. The device must release at the framing event itself, not merely once the new address resolves eight clock pulses later. Between those two moments there is a window in which the device is not the target and has not yet been told so, and §8 injects a fault that leaves it addressed for exactly that window — a mutation that survived the first version of the testbench because the test only looked afterwards.
Note the contrast with Chapter 6.2's matcher, which had to keep something across an Sr. There, was_selected survives because the 10-bit read re-address is interpreted against it. Here, addressed must not survive, because it is a claim about right now. Two pieces of state, two opposite rules, and the difference is what each one means.
5. The Decoder in Three Languages
Three files per language: the decoder plus the two blocks it instantiates. All three must be compiled together.
// The slave-side address decoder: shift, classify, compare, decide, respond.
// It is ASSEMBLED from the two blocks built earlier in this module rather than
// reimplementing them -- i2c_address_byte_capture and i2c_address_classifier.
module i2c_address_decoder #(
parameter logic [6:0] OWN_ADDR = 7'h48,
// General call support is OPTIONAL in the specification (Table 2), so it is
// a build-time decision and not something every device must implement.
parameter bit ANSWER_GEN_CALL = 1'b1
)(
input logic clk,
input logic rst_n,
input logic scl_in, // observed bus level
input logic sda_in, // observed bus level
input logic frame_start, // pulse: S or Sr observed (Module 5)
input logic frame_stop, // pulse: P observed (Module 5)
output logic addressed, // this transfer is for this device
output logic direction, // 1 = master READS from us
output logic gen_call_active, // selected by the general call, not by address
output logic ack_request, // pulse: acknowledge the address byte
output logic [6:0] last_addr // the address seen, for software and debug
);
localparam logic [3:0] CLS_GENERAL_CALL = 4'd1;
// ---- stage 1: get the byte off the wire ----
logic [6:0] cap_addr;
logic cap_rw, cap_valid, capturing;
i2c_address_byte_capture u_capture (
.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addr(cap_addr), .rw(cap_rw), .addr_valid(cap_valid), .capturing(capturing)
);
// ---- stage 2: decide what kind of address it is ----
logic cls_reserved, cls_must_not_ack;
logic [3:0] cls_class;
i2c_address_classifier u_classify (
.addr(cap_addr), .rw(cap_rw),
.is_reserved(cls_reserved), .addr_class(cls_class), .must_not_ack(cls_must_not_ack)
);
// ---- stage 3: compare and decide ----
logic own_match, gen_call_match;
assign own_match = (cap_addr == OWN_ADDR);
assign gen_call_match = (cls_class == CLS_GENERAL_CALL) && ANSWER_GEN_CALL;
always_ff @(posedge clk) begin
if (!rst_n) begin
addressed <= 1'b0;
direction <= 1'b0;
gen_call_active <= 1'b0;
ack_request <= 1'b0;
last_addr <= 7'h00;
end else begin
ack_request <= 1'b0;
if (frame_stop) begin
// A STOP releases this device unconditionally.
addressed <= 1'b0;
gen_call_active <= 1'b0;
end else if (frame_start) begin
// S or Sr: stop being addressed until the new address says otherwise.
// A device that stayed selected across an Sr would answer a transfer
// aimed at somebody else.
addressed <= 1'b0;
gen_call_active <= 1'b0;
end else if (cap_valid) begin
last_addr <= cap_addr;
// The prohibition WINS over a match. Two Table 3 footnotes forbid
// responding at all, and they do not make an exception for a device
// that happens to have been given that address.
if (cls_must_not_ack) begin
addressed <= 1'b0;
gen_call_active <= 1'b0;
end else if (own_match) begin
addressed <= 1'b1;
direction <= cap_rw;
gen_call_active <= 1'b0;
ack_request <= 1'b1;
end else if (gen_call_match) begin
// The general call is a broadcast WRITE: there is no readable
// direction, because every device would be transmitting at once.
addressed <= 1'b1;
direction <= 1'b0;
gen_call_active <= 1'b1;
ack_request <= 1'b1;
end else begin
addressed <= 1'b0;
gen_call_active <= 1'b0;
end
end
end
end
endmodule module i2c_address_decoder_tb;
logic clk = 1'b0, rst_n, scl_in, sda_in, frame_start, frame_stop;
int errors = 0;
// THREE devices, to separate three different decisions:
// G -- an ordinary part at 0x48 that answers the general call
// N -- the same part with general call support NOT built
// C -- a part mis-assigned the CBUS address, to prove the prohibition wins
logic addrG, dirG, gcG, ackG; logic [6:0] laG;
logic addrN, dirN, gcN, ackN; logic [6:0] laN;
logic addrC, dirC, gcC, ackC; logic [6:0] laC;
i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b1)) dutG
(.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addressed(addrG), .direction(dirG), .gen_call_active(gcG),
.ack_request(ackG), .last_addr(laG));
i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b0)) dutN
(.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addressed(addrN), .direction(dirN), .gen_call_active(gcN),
.ack_request(ackN), .last_addr(laN));
i2c_address_decoder #(.OWN_ADDR(7'b0000001), .ANSWER_GEN_CALL(1'b0)) dutC
(.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addressed(addrC), .direction(dirC), .gen_call_active(gcC),
.ack_request(ackC), .last_addr(laC));
always #5 clk = ~clk;
initial begin #80000; $display("FAIL: watchdog expired"); $finish; end
int nG = 0, nN = 0, nC = 0;
always @(posedge clk) if (rst_n) begin
if (ackG) nG++;
if (ackN) nN++;
if (ackC) nC++;
end
task automatic send_bit(input logic b);
scl_in = 1'b0; repeat (2) @(negedge clk);
sda_in = b; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
endtask
task automatic pulse_start(); frame_start = 1'b1; @(negedge clk); frame_start = 1'b0; endtask
task automatic pulse_stop(); frame_stop = 1'b1; @(negedge clk); frame_stop = 1'b0; endtask
// A full addressing phase: framing START then the eight-bit address byte.
task automatic address_phase(input logic [6:0] a, input logic dir);
pulse_start();
for (int i = 6; i >= 0; i--) send_bit(a[i]);
send_bit(dir);
repeat (2) @(negedge clk);
endtask
task automatic chk(input logic got, input logic want, input int step);
if (got !== want) begin
$display("FAIL: step %0d -- signal was %0b, expected %0b", step, got, want);
errors++;
end
endtask
initial begin
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; frame_start = 1'b0; frame_stop = 1'b0;
repeat (3) @(negedge clk);
chk(addrG, 1'b0, 0); chk(gcG, 1'b0, 0);
rst_n = 1'b1; @(negedge clk);
// 1 -- our own address, WRITE.
nG = 0;
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 1); chk(dirG, 1'b0, 1); chk(gcG, 1'b0, 1);
if (nG != 1) begin $display("FAIL: expected exactly one ACK for our address, saw %0d", nG); errors++; end
if (laG !== 7'h48) begin $display("FAIL: last_addr = 0x%02h", laG); errors++; end
// 2 -- our own address, READ. Same match, opposite direction.
pulse_stop();
nG = 0;
address_phase(7'h48, 1'b1);
chk(addrG, 1'b1, 2); chk(dirG, 1'b1, 2);
if (nG != 1) begin $display("FAIL: read address not acknowledged once"); errors++; end
// 3 -- somebody else's address. No ACK, and not addressed.
pulse_stop();
nG = 0;
address_phase(7'h49, 1'b0);
chk(addrG, 1'b0, 3);
if (nG != 0) begin $display("FAIL: acknowledged another device's address"); errors++; end
// 4 -- THE GENERAL CALL. The device that supports it answers; the device
// built without support stays silent. Both are compliant, because the
// specification makes general call OPTIONAL.
pulse_stop();
nG = 0; nN = 0;
address_phase(7'b0000000, 1'b0);
chk(addrG, 1'b1, 4); chk(gcG, 1'b1, 4); chk(dirG, 1'b0, 4);
chk(addrN, 1'b0, 4); chk(gcN, 1'b0, 4);
if (nG != 1) begin $display("FAIL: general call not acknowledged by the supporting device"); errors++; end
if (nN != 0) begin $display("FAIL: a device without general call support acknowledged it"); errors++; end
// 5 -- THE START BYTE. Same seven address bits as the general call, R/W = 1.
// "No device is allowed to acknowledge at the reception of the START
// byte" -- so the direction bit alone flips a broadcast into a
// must-not-answer.
pulse_stop();
nG = 0; nN = 0;
address_phase(7'b0000000, 1'b1);
chk(addrG, 1'b0, 5); chk(gcG, 1'b0, 5);
if (nG != 0 || nN != 0) begin
$display("FAIL: the START byte was acknowledged (G=%0d N=%0d)", nG, nN); errors++; end
// 6 -- THE PROHIBITION WINS OVER A MATCH. Device C was given the CBUS
// address. It matches, and it must still not respond.
pulse_stop();
nC = 0;
address_phase(7'b0000001, 1'b0);
chk(addrC, 1'b0, 6);
if (nC != 0) begin
$display("FAIL: a device assigned the CBUS address responded to it"); errors++; end
// 7 -- a STOP releases the device.
pulse_stop();
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 7);
pulse_stop();
repeat (2) @(negedge clk);
chk(addrG, 1'b0, 7);
// 8 -- Sr TO A DIFFERENT ADDRESS must release us. A device that stayed
// selected across a repeated START would answer somebody else's
// transfer -- and Module 5 established that Sr does not release the bus,
// so nothing else would tell it to let go.
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 8);
address_phase(7'h49, 1'b0); // Sr, then another device's address
chk(addrG, 1'b0, 8);
// 8b -- THE DEVICE MUST LET GO AT THE Sr ITSELF, not merely once the new
// address resolves eight clocks later. Between the Sr and the end of
// the new address byte, a device that is still reporting `addressed`
// is claiming a transfer that may belong to somebody else -- and
// anything downstream acting on that flag acts on a stale claim.
pulse_stop();
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 82);
pulse_start(); // the Sr alone -- no address byte yet
repeat (2) @(negedge clk);
chk(addrG, 1'b0, 82); // released already, mid-address-phase
// 9 -- Sr back to US re-selects, with the new direction.
address_phase(7'h48, 1'b1);
chk(addrG, 1'b1, 9); chk(dirG, 1'b1, 9);
if (errors == 0)
$display("PASS: own address matched both directions, general call optional, prohibitions win, Sr releases");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule // The slave-side address decoder: shift, classify, compare, decide, respond.
// ASSEMBLED from the two blocks built earlier in this module rather than
// reimplementing them -- i2c_address_byte_capture and i2c_address_classifier.
module i2c_address_decoder #(
parameter [6:0] OWN_ADDR = 7'h48,
// General call support is OPTIONAL in the specification (Table 2).
parameter ANSWER_GEN_CALL = 1'b1
)(
input wire clk,
input wire rst_n,
input wire scl_in, // observed bus level
input wire sda_in, // observed bus level
input wire frame_start, // pulse: S or Sr observed (Module 5)
input wire frame_stop, // pulse: P observed (Module 5)
output reg addressed, // this transfer is for this device
output reg direction, // 1 = master READS from us
output reg gen_call_active, // selected by the general call
output reg ack_request, // pulse: acknowledge the address byte
output reg [6:0] last_addr // the address seen, for software and debug
);
localparam [3:0] CLS_GENERAL_CALL = 4'd1;
// ---- stage 1: get the byte off the wire ----
wire [6:0] cap_addr;
wire cap_rw, cap_valid, capturing;
i2c_address_byte_capture u_capture (
.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addr(cap_addr), .rw(cap_rw), .addr_valid(cap_valid), .capturing(capturing)
);
// ---- stage 2: decide what kind of address it is ----
wire cls_reserved, cls_must_not_ack;
wire [3:0] cls_class;
i2c_address_classifier u_classify (
.addr(cap_addr), .rw(cap_rw),
.is_reserved(cls_reserved), .addr_class(cls_class), .must_not_ack(cls_must_not_ack)
);
// ---- stage 3: compare and decide ----
wire own_match = (cap_addr == OWN_ADDR);
wire gen_call_match = (cls_class == CLS_GENERAL_CALL) && ANSWER_GEN_CALL;
always @(posedge clk) begin
if (!rst_n) begin
addressed <= 1'b0;
direction <= 1'b0;
gen_call_active <= 1'b0;
ack_request <= 1'b0;
last_addr <= 7'h00;
end else begin
ack_request <= 1'b0;
if (frame_stop) begin
addressed <= 1'b0;
gen_call_active <= 1'b0;
end else if (frame_start) begin
// S or Sr: stop being addressed until the new address says otherwise.
addressed <= 1'b0;
gen_call_active <= 1'b0;
end else if (cap_valid) begin
last_addr <= cap_addr;
// The prohibition WINS over a match: the footnotes forbidding a
// response make no exception for a device given that address.
if (cls_must_not_ack) begin
addressed <= 1'b0;
gen_call_active <= 1'b0;
end else if (own_match) begin
addressed <= 1'b1;
direction <= cap_rw;
gen_call_active <= 1'b0;
ack_request <= 1'b1;
end else if (gen_call_match) begin
// The general call is a broadcast WRITE: there is no readable
// direction, because every device would be transmitting at once.
addressed <= 1'b1;
direction <= 1'b0;
gen_call_active <= 1'b1;
ack_request <= 1'b1;
end else begin
addressed <= 1'b0;
gen_call_active <= 1'b0;
end
end
end
end
endmodule module i2c_address_decoder_tb;
reg clk, rst_n, scl_in, sda_in, frame_start, frame_stop;
integer errors;
// THREE devices, to separate three different decisions:
// G -- an ordinary part at 0x48 that answers the general call
// N -- the same part with general call support NOT built
// C -- a part mis-assigned the CBUS address, to prove the prohibition wins
wire addrG, dirG, gcG, ackG; wire [6:0] laG;
wire addrN, dirN, gcN, ackN; wire [6:0] laN;
wire addrC, dirC, gcC, ackC; wire [6:0] laC;
i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b1)) dutG
(.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addressed(addrG), .direction(dirG), .gen_call_active(gcG),
.ack_request(ackG), .last_addr(laG));
i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b0)) dutN
(.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addressed(addrN), .direction(dirN), .gen_call_active(gcN),
.ack_request(ackN), .last_addr(laN));
i2c_address_decoder #(.OWN_ADDR(7'b0000001), .ANSWER_GEN_CALL(1'b0)) dutC
(.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
.frame_start(frame_start), .frame_stop(frame_stop),
.addressed(addrC), .direction(dirC), .gen_call_active(gcC),
.ack_request(ackC), .last_addr(laC));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin #80000; $display("FAIL: watchdog expired"); $finish; end
integer nG, nN, nC, i;
always @(posedge clk) if (rst_n) begin
if (ackG) nG = nG + 1;
if (ackN) nN = nN + 1;
if (ackC) nC = nC + 1;
end
task send_bit; input b; begin
scl_in = 1'b0; repeat (2) @(negedge clk);
sda_in = b; repeat (2) @(negedge clk);
scl_in = 1'b1; repeat (2) @(negedge clk);
scl_in = 1'b0; repeat (1) @(negedge clk);
end endtask
task pulse_start; begin frame_start = 1'b1; @(negedge clk); frame_start = 1'b0; end endtask
task pulse_stop; begin frame_stop = 1'b1; @(negedge clk); frame_stop = 1'b0; end endtask
// A full addressing phase: framing START then the eight-bit address byte.
task address_phase; input [6:0] a; input dir; integer k; begin
pulse_start();
for (k = 6; k >= 0; k = k - 1) send_bit(a[k]);
send_bit(dir);
repeat (2) @(negedge clk);
end endtask
task chk; input got; input want; input integer step; begin
if (got !== want) begin
$display("FAIL: step %0d -- signal was %0b, expected %0b", step, got, want);
errors = errors + 1;
end
end endtask
initial begin
errors = 0; nG = 0; nN = 0; nC = 0;
rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; frame_start = 1'b0; frame_stop = 1'b0;
repeat (3) @(negedge clk);
chk(addrG, 1'b0, 0); chk(gcG, 1'b0, 0);
rst_n = 1'b1; @(negedge clk);
// 1 -- our own address, WRITE.
nG = 0;
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 1); chk(dirG, 1'b0, 1); chk(gcG, 1'b0, 1);
if (nG != 1) begin $display("FAIL: expected exactly one ACK for our address, saw %0d", nG); errors = errors + 1; end
if (laG !== 7'h48) begin $display("FAIL: last_addr = 0x%02h", laG); errors = errors + 1; end
// 2 -- our own address, READ. Same match, opposite direction.
pulse_stop();
nG = 0;
address_phase(7'h48, 1'b1);
chk(addrG, 1'b1, 2); chk(dirG, 1'b1, 2);
if (nG != 1) begin $display("FAIL: read address not acknowledged once"); errors = errors + 1; end
// 3 -- somebody else's address. No ACK, and not addressed.
pulse_stop();
nG = 0;
address_phase(7'h49, 1'b0);
chk(addrG, 1'b0, 3);
if (nG != 0) begin $display("FAIL: acknowledged another device's address"); errors = errors + 1; end
// 4 -- THE GENERAL CALL. The device that supports it answers; the device
// built without support stays silent. Both are compliant, because the
// specification makes general call OPTIONAL.
pulse_stop();
nG = 0; nN = 0;
address_phase(7'b0000000, 1'b0);
chk(addrG, 1'b1, 4); chk(gcG, 1'b1, 4); chk(dirG, 1'b0, 4);
chk(addrN, 1'b0, 4); chk(gcN, 1'b0, 4);
if (nG != 1) begin $display("FAIL: general call not acknowledged by the supporting device"); errors = errors + 1; end
if (nN != 0) begin $display("FAIL: a device without general call support acknowledged it"); errors = errors + 1; end
// 5 -- THE START BYTE. Same seven address bits as the general call, R/W = 1.
// "No device is allowed to acknowledge at the reception of the START
// byte" -- so the direction bit alone flips a broadcast into a
// must-not-answer.
pulse_stop();
nG = 0; nN = 0;
address_phase(7'b0000000, 1'b1);
chk(addrG, 1'b0, 5); chk(gcG, 1'b0, 5);
if (nG != 0 || nN != 0) begin
$display("FAIL: the START byte was acknowledged (G=%0d N=%0d)", nG, nN); errors = errors + 1; end
// 6 -- THE PROHIBITION WINS OVER A MATCH. Device C was given the CBUS
// address. It matches, and it must still not respond.
pulse_stop();
nC = 0;
address_phase(7'b0000001, 1'b0);
chk(addrC, 1'b0, 6);
if (nC != 0) begin
$display("FAIL: a device assigned the CBUS address responded to it"); errors = errors + 1; end
// 7 -- a STOP releases the device.
pulse_stop();
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 7);
pulse_stop();
repeat (2) @(negedge clk);
chk(addrG, 1'b0, 7);
// 8 -- Sr TO A DIFFERENT ADDRESS must release us. A device that stayed
// selected across a repeated START would answer somebody else's
// transfer -- and Module 5 established that Sr does not release the bus,
// so nothing else would tell it to let go.
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 8);
address_phase(7'h49, 1'b0); // Sr, then another device's address
chk(addrG, 1'b0, 8);
// 8b -- THE DEVICE MUST LET GO AT THE Sr ITSELF, not merely once the new
// address resolves eight clocks later. A device still reporting
// `addressed` in that window is claiming a transfer that may belong
// to somebody else.
pulse_stop();
address_phase(7'h48, 1'b0);
chk(addrG, 1'b1, 82);
pulse_start(); // the Sr alone -- no address byte yet
repeat (2) @(negedge clk);
chk(addrG, 1'b0, 82); // released already, mid-address-phase
// 9 -- Sr back to US re-selects, with the new direction.
address_phase(7'h48, 1'b1);
chk(addrG, 1'b1, 9); chk(dirG, 1'b1, 9);
if (errors == 0)
$display("PASS: own address matched both directions, general call optional, prohibitions win, Sr releases");
else $display("FAIL: %0d error(s)", errors);
$finish;
end
endmodule library ieee;
use ieee.std_logic_1164.all;
-- The slave-side address decoder: shift, classify, compare, decide, respond.
-- ASSEMBLED from the two blocks built earlier in this module rather than
-- reimplementing them -- i2c_address_byte_capture and i2c_address_classifier.
entity i2c_address_decoder is
generic (
OWN_ADDR : std_logic_vector(6 downto 0) := "1001000"; -- 0x48
-- General call support is OPTIONAL in the specification (Table 2).
ANSWER_GEN_CALL : boolean := true
);
port (
clk : in std_logic;
rst_n : in std_logic;
scl_in : in std_logic; -- observed bus level
sda_in : in std_logic; -- observed bus level
frame_start : in std_logic; -- pulse: S or Sr
frame_stop : in std_logic; -- pulse: P
addressed : out std_logic; -- this transfer is ours
direction : out std_logic; -- 1 = master READS
gen_call_active : out std_logic; -- selected by general call
ack_request : out std_logic; -- pulse: ACK the address
last_addr : out std_logic_vector(6 downto 0) -- for software and debug
);
end entity;
architecture rtl of i2c_address_decoder is
constant CLS_GENERAL_CALL : std_logic_vector(3 downto 0) := x"1";
-- stage 1
signal cap_addr : std_logic_vector(6 downto 0);
signal cap_rw : std_logic;
signal cap_valid : std_logic;
signal capturing : std_logic;
-- stage 2
signal cls_reserved, cls_must_not_ack : std_logic;
signal cls_class : std_logic_vector(3 downto 0);
-- stage 3
signal own_match, gen_call_match : std_logic;
begin
u_capture : entity work.i2c_address_byte_capture
port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
frame_start => frame_start, frame_stop => frame_stop,
addr => cap_addr, rw => cap_rw, addr_valid => cap_valid,
capturing => capturing);
u_classify : entity work.i2c_address_classifier
port map (addr => cap_addr, rw => cap_rw, is_reserved => cls_reserved,
addr_class => cls_class, must_not_ack => cls_must_not_ack);
own_match <= '1' when cap_addr = OWN_ADDR else '0';
gen_call_match <= '1' when (cls_class = CLS_GENERAL_CALL and ANSWER_GEN_CALL) else '0';
process (clk)
begin
if rising_edge(clk) then
if rst_n = '0' then
addressed <= '0';
direction <= '0';
gen_call_active <= '0';
ack_request <= '0';
last_addr <= (others => '0');
else
ack_request <= '0';
if frame_stop = '1' then
addressed <= '0';
gen_call_active <= '0';
elsif frame_start = '1' then
-- S or Sr: stop being addressed until the new address says so.
addressed <= '0';
gen_call_active <= '0';
elsif cap_valid = '1' then
last_addr <= cap_addr;
-- The prohibition WINS over a match: the footnotes forbidding a
-- response make no exception for a device given that address.
if cls_must_not_ack = '1' then
addressed <= '0';
gen_call_active <= '0';
elsif own_match = '1' then
addressed <= '1';
direction <= cap_rw;
gen_call_active <= '0';
ack_request <= '1';
elsif gen_call_match = '1' then
-- The general call is a broadcast WRITE: there is no readable
-- direction, because every device would transmit at once.
addressed <= '1';
direction <= '0';
gen_call_active <= '1';
ack_request <= '1';
else
addressed <= '0';
gen_call_active <= '0';
end if;
end if;
end if;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
entity i2c_address_decoder_tb is
end entity;
architecture sim of i2c_address_decoder_tb is
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal scl_in : std_logic := '1';
signal sda_in : std_logic := '1';
signal frame_start : std_logic := '0';
signal frame_stop : std_logic := '0';
-- THREE devices, separating three different decisions:
-- G -- an ordinary part at 0x48 that answers the general call
-- N -- the same part with general call support NOT built
-- C -- a part mis-assigned the CBUS address
signal addrG, dirG, gcG, ackG : std_logic;
signal addrN, dirN, gcN, ackN : std_logic;
signal addrC, dirC, gcC, ackC : std_logic;
signal laG, laN, laC : std_logic_vector(6 downto 0);
signal nG, nN, nC : natural := 0;
signal test_done : std_logic := '0';
begin
dutG : entity work.i2c_address_decoder
generic map (OWN_ADDR => "1001000", ANSWER_GEN_CALL => true)
port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
frame_start => frame_start, frame_stop => frame_stop,
addressed => addrG, direction => dirG, gen_call_active => gcG,
ack_request => ackG, last_addr => laG);
dutN : entity work.i2c_address_decoder
generic map (OWN_ADDR => "1001000", ANSWER_GEN_CALL => false)
port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
frame_start => frame_start, frame_stop => frame_stop,
addressed => addrN, direction => dirN, gen_call_active => gcN,
ack_request => ackN, last_addr => laN);
dutC : entity work.i2c_address_decoder
generic map (OWN_ADDR => "0000001", ANSWER_GEN_CALL => false)
port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
frame_start => frame_start, frame_stop => frame_stop,
addressed => addrC, direction => dirC, gen_call_active => gcC,
ack_request => ackC, last_addr => laC);
clk <= not clk after 5 ns;
watchdog : process
begin
wait for 80 us;
if test_done = '0' then
report "watchdog expired -- the design never reached the expected state"
severity failure;
end if;
wait;
end process;
count : process (clk)
begin
if rising_edge(clk) then
if rst_n = '1' then
if ackG = '1' then nG <= nG + 1; end if;
if ackN = '1' then nN <= nN + 1; end if;
if ackC = '1' then nC <= nC + 1; end if;
end if;
end if;
end process;
stim : process
variable errs : natural := 0;
variable bG, bN, bC : natural := 0;
procedure waitn (n : in positive) is
begin
for i in 1 to n loop wait until falling_edge(clk); end loop;
end procedure;
procedure send_bit (b : in std_logic) is
begin
scl_in <= '0'; waitn(2);
sda_in <= b; waitn(2);
scl_in <= '1'; waitn(2);
scl_in <= '0'; waitn(1);
end procedure;
procedure pulse_start is
begin
frame_start <= '1'; waitn(1); frame_start <= '0';
end procedure;
procedure pulse_stop is
begin
frame_stop <= '1'; waitn(1); frame_stop <= '0';
end procedure;
procedure address_phase (a : in std_logic_vector(6 downto 0); dir : in std_logic) is
begin
pulse_start;
for k in 6 downto 0 loop send_bit(a(k)); end loop;
send_bit(dir);
waitn(2);
end procedure;
procedure chk (got, want : in std_logic; step : in natural) is
begin
if got /= want then
report "step " & integer'image(step) & ": signal mismatch" severity error;
errs := errs + 1;
end if;
end procedure;
begin
waitn(3);
chk(addrG, '0', 0); chk(gcG, '0', 0);
rst_n <= '1'; waitn(1);
-- Our own address, WRITE.
bG := nG;
address_phase("1001000", '0');
chk(addrG, '1', 1); chk(dirG, '0', 1); chk(gcG, '0', 1);
if (nG - bG) /= 1 then
report "expected exactly one ACK for our address" severity error; errs := errs + 1; end if;
if laG /= "1001000" then
report "last_addr wrong" severity error; errs := errs + 1; end if;
-- Our own address, READ.
pulse_stop; bG := nG;
address_phase("1001000", '1');
chk(addrG, '1', 2); chk(dirG, '1', 2);
if (nG - bG) /= 1 then
report "read address not acknowledged once" severity error; errs := errs + 1; end if;
-- Somebody else's address.
pulse_stop; bG := nG;
address_phase("1001001", '0');
chk(addrG, '0', 3);
if (nG - bG) /= 0 then
report "acknowledged another device's address" severity error; errs := errs + 1; end if;
-- THE GENERAL CALL: optional, so both behaviours are compliant.
pulse_stop; bG := nG; bN := nN;
address_phase("0000000", '0');
chk(addrG, '1', 4); chk(gcG, '1', 4); chk(dirG, '0', 4);
chk(addrN, '0', 4); chk(gcN, '0', 4);
if (nG - bG) /= 1 then
report "general call not acknowledged by the supporting device" severity error;
errs := errs + 1; end if;
if (nN - bN) /= 0 then
report "a device without general call support acknowledged it" severity error;
errs := errs + 1; end if;
-- THE START BYTE: same seven address bits, R/W = 1, must never be acked.
pulse_stop; bG := nG; bN := nN;
address_phase("0000000", '1');
chk(addrG, '0', 5); chk(gcG, '0', 5);
if (nG - bG) /= 0 or (nN - bN) /= 0 then
report "the START byte was acknowledged" severity error; errs := errs + 1; end if;
-- THE PROHIBITION WINS OVER A MATCH.
pulse_stop; bC := nC;
address_phase("0000001", '0');
chk(addrC, '0', 6);
if (nC - bC) /= 0 then
report "a device assigned the CBUS address responded to it" severity error;
errs := errs + 1; end if;
-- A STOP releases the device.
pulse_stop;
address_phase("1001000", '0');
chk(addrG, '1', 7);
pulse_stop; waitn(2);
chk(addrG, '0', 7);
-- Sr TO A DIFFERENT ADDRESS must release us.
address_phase("1001000", '0');
chk(addrG, '1', 8);
address_phase("1001001", '0');
chk(addrG, '0', 8);
-- THE DEVICE MUST LET GO AT THE Sr ITSELF, not merely once the new address
-- resolves eight clocks later. A device still reporting `addressed` in that
-- window is claiming a transfer that may belong to somebody else.
pulse_stop;
address_phase("1001000", '0');
chk(addrG, '1', 82);
pulse_start; -- the Sr alone -- no address byte yet
waitn(2);
chk(addrG, '0', 82); -- released already, mid-address-phase
-- Sr back to US re-selects, with the new direction.
address_phase("1001000", '1');
chk(addrG, '1', 9); chk(dirG, '1', 9);
if errs = 0 then
report "i2c_address_decoder self-check complete: own address matched both "
& "directions, general call optional, prohibitions win, Sr releases"
severity note;
else
report "i2c_address_decoder self-check FAILED" severity error;
end if;
test_done <= '1';
wait;
end process;
end architecture;5a. Verified Execution
Each language's testbench was compiled with its decoder and both sub-modules, and run:
| language | files compiled together | simulator | result | completes at |
|---|---|---|---|---|
| SystemVerilog | decoder + capture + classifier + TB | Icarus Verilog, -g2012 | PASS | 6660 ns |
| Verilog-2005 | decoder + capture + classifier + TB | Icarus Verilog, -g2005 | PASS | 6660 ns |
| VHDL | all three analysed into the work library, then elaborated | nvc 1.23.0 | PASS | 6660 ns |
5b. Cross-Language Parity
| SystemVerilog | Verilog-2005 | VHDL | |
|---|---|---|---|
| own address | parameter logic [6:0] | parameter [6:0] | std_logic_vector(6 downto 0) generic |
| general call option | parameter bit | parameter | boolean generic |
| instantiation | named port connections | named port connections | entity work.<name> direct instantiation |
| option in a condition | && ANSWER_GEN_CALL | && ANSWER_GEN_CALL | and ANSWER_GEN_CALL on a boolean |
The VHDL version uses direct entity instantiation (u_capture : entity work.i2c_address_byte_capture) rather than a component declaration. Both are legal; direct instantiation avoids restating every port in a component declaration, which is one fewer place for a port list to drift out of step with the entity it describes.
i2c_address_decoder — its own address, write
10 cyclesi2c_address_decoder — the START byte, refused
10 cycles6. What the Testbench Proves
The suite instantiates three decoders, because three different decisions need three different configurations:
| instance | configuration | what only it can show |
|---|---|---|
| G | 0x48, general call supported | the ordinary match, both directions, and a general call answered |
| N | 0x48, general call not built | that ignoring a general call is compliant, not broken |
| C | 0x01 (the CBUS address), general call off | that the prohibition beats a match |
| step | stimulus | required result |
|---|---|---|
| 1 | own address, write | addressed, direction = 0, one ACK |
| 2 | own address, read | addressed, direction = 1, one ACK |
| 3 | another device's address | not addressed, no ACK |
| 4 | general call | G answers, N stays silent — both compliant |
| 5 | START byte (same seven bits, R/W = 1) | neither answers |
| 6 | the CBUS address, to device C which holds it | C does not answer |
| 7 | STOP | released |
| 8 | Sr, then another device's address | released |
| 8b | Sr alone, checked before the new byte completes | already released |
| 9 | Sr back to our address, read | re-selected, new direction |
Step 4 needs two instances. "General call support is optional" is a statement about two compliant behaviours, and one device cannot exhibit both. Instantiating G and N side by side turns it into a checked property rather than a sentence — the same topology argument Chapter 6.2 made for its two matchers.
Step 6 needs a deliberately misconfigured instance. Device C is given the CBUS address, which no sane board would do on purpose. That is the point: the prohibition has to hold for a device that does hold the address, and only an instance configured that way can test it.
Steps 5 and 6 together are the reason the classifier takes eight bits. Step 5 differs from step 4 in one bit of one byte and inverts the required behaviour.
7. An Assertion for the Ordering Rule
The priority of §2 is a natural assertion, because it is a statement about every address phase rather than about a particular one.
// The strongest single property in this chapter: whatever else is true, a byte
// the specification forbids answering is never answered. Note it does NOT
// mention own_match -- that is exactly the point. No matching condition, now or
// added later, may create an exception.
property prohibition_always_wins;
@(posedge clk) disable iff (!rst_n)
(addr_valid && must_not_ack) |=> !ack_request;
endproperty
assert property (prohibition_always_wins)
else $error("acknowledged a byte the specification forbids answering");
// And the pulse property, for the same reason it mattered in Chapter 6.1: a
// consumer in the byte-transfer layer counts acknowledge requests.
property ack_request_is_a_pulse;
@(posedge clk) disable iff (!rst_n)
ack_request |=> !ack_request;
endproperty
// Addressed must not survive a framing event -- section 4's window.
property framing_releases_immediately;
@(posedge clk) disable iff (!rst_n)
(frame_start || frame_stop) |=> !addressed;
endpropertyThe first property is worth writing even though the procedural suite checks the same thing, and the reason is its shape. It says nothing about which addresses match, so it keeps holding when somebody later adds 10-bit matching, or a second own-address, or a general-call variant. A test checks the design as it is; this property constrains every future version of it.
The third property is the one that would have caught §8's surviving mutation immediately, without needing step 8b — an illustration of assertions catching windows that a check-afterwards test walks straight past.
8. Mutation Testing
Six faults injected into the verified RTL. Five caught immediately; one survived and was a real gap.
| mutation | what it breaks | result |
|---|---|---|
| an address match beats the prohibition | the §2 ordering, reversed | FAIL — step 6 |
| general call answered regardless of the build option | a device answers broadcasts it does not implement | FAIL — step 4 |
ack_request is a level, not a pulse | the byte-transfer layer double-counts | FAIL — three ACKs seen |
| a STOP does not release the device | selection leaks past the transfer | FAIL — step 7 |
| a repeated START does not release the device | see below | initially PASSED |
| the general call latches the wire direction | — | survived, provably equivalent |
The mutation that survived and mattered. Removing the addressed <= 1'b0 from the framing-START branch left the suite passing. It is not equivalent: the device stays addressed from the Sr until the new address byte completes, eight clock pulses later, and during that window it is claiming a transfer that may belong to another device.
The original test only checked addressed after the new address had resolved — by which point the capture's addr_valid had fired and the decision stage had cleared the flag for a different reason. The design was right and the test was checking the wrong instant.
The fix is step 8b: issue the Sr alone and check immediately, before any address bits follow. Two lines, and the mutation is now caught.
The mutation that survived and did not matter. Changing the general-call branch's direction <= 1'b0 to direction <= cap_rw is provably equivalent, and the proof is in the classifier: CLS_GENERAL_CALL is assigned only for the pattern 0000000 with R/W = 0. So whenever that branch executes, cap_rw is zero. The two assignments are identical under every reachable input.
The explicit 1'b0 is still the better code — it states that a general call has no meaningful direction because a broadcast read is nonsense, rather than leaving a reader to derive that from the classifier — but it is a readability choice, not a behavioural one, and no test can distinguish them.
Module 6 totals: 27 mutations injected, 24 caught, 3 proven behaviourally equivalent. The three equivalents are documented where they arise — this chapter's direction assignment, Chapter 6.1's shifter clear, and Chapter 6.3's shadowed case item — and each was proven rather than assumed, because "probably equivalent" and "provably equivalent" are different claims and only one of them justifies not writing a test.
9. Verification Connection — The Slave Agent's Address Phase
A slave agent's address phase is where a verification environment decides whether it is modelling a device or the protocol.
// The agent's addressing behaviour is CONFIGURATION, not code, because the
// whole point of a reusable slave agent is that one class models many parts.
class i2c_slave_addr_cfg extends uvm_object;
`uvm_object_utils(i2c_slave_addr_cfg)
bit [6:0] own_addr = 7'h48;
bit answer_gen_call = 1'b0; // OPTIONAL per Table 2 -- default off
bit ten_bit_mode = 1'b0;
bit [9:0] own_addr_10 = 10'h000;
// The escape hatch of Chapter 6.4, as an explicit opt-in rather than an
// absent check -- so an agent at a reserved address is a deliberate test,
// not an accident nobody notices.
bit allow_reserved = 1'b0;
function new(string name = "i2c_slave_addr_cfg"); super.new(name); endfunction
endclass
// The responder mirrors the RTL's ORDERING, deliberately. A model that checked
// the match first would disagree with a correct DUT on exactly the case the
// prohibition exists for -- and a disagreeing model reports a DUT bug.
function bit i2c_slave_responder::should_ack(bit [6:0] a, bit rw);
if (is_start_byte(a, rw) || is_cbus(a)) return 0; // FIRST
if (a == cfg.own_addr) return 1;
if (cfg.answer_gen_call && a == 7'h00 && rw == 1'b0) return 1;
return 0;
endfunctionTwo observations, and the second is the one that bites in practice.
Addressing behaviour is configuration. A slave agent whose address is a parameter models one part; one whose address, general-call support and 10-bit mode are configuration models a family. The three-instance testbench in §6 is the block-level version of the same idea.
The reference model must share the RTL's ordering, and for a non-obvious reason. If the model checks the match before the prohibition, then against a correct DUT the two disagree on the CBUS case — and the environment reports a DUT bug that does not exist. A reference model encodes the specification's priority, and if that priority was derived rather than stated, the derivation has to be written down somewhere both the model and the RTL point at. §2 is that place for this design.
10. FPGA and ASIC Implications
The whole decoder is tiny, and the hierarchy costs nothing. Eight flip-flops for the shifter, four for the counter, a handful for the decision state, and a combinational decode. Synthesis flattens the hierarchy; the module boundaries exist for verification and review, not for area.
Keep the hierarchy anyway. Each block is independently testable — Chapter 6.3's classifier gets an exhaustive 256-combination sweep precisely because it is a separate combinational block with no state. Folding it into the decoder would make that sweep impossible and the reserved-map decode unreviewable against the table.
All the reset state must share one domain. addressed, the capture's capturing and bit_count, and — in a 10-bit device — the matcher's was_selected must all be cleared by the same reset. Splitting them lets a peripheral reset leave a device addressed for a transfer that no longer exists, or remembering a selection from a previous transaction. Chapter 5.4 made the same point for its classifier and it is the same failure.
An always-on decoder is normal on an ASIC. A peripheral that can be woken by being addressed needs framing detection and address decoding running in an ungated domain, which makes these few dozen flip-flops part of the always-on partition and their reset a power-on reset. Chapter 5.2 §11 covered the consequence: such a block comes out of reset onto a bus that may have been live for hours, so its assumptions about initial state are load-bearing.
Expose last_addr. What the device observed is different information from what it was configured with — Chapter 6.4 exposed the latter — and having both readable turns "why is this device not responding" into two register reads instead of a capture session.
11. Debugging — The Device That Answered Two Transfers at Once
A slave that corrupted a transfer it was not part of
Pitfall — releasing the addressed flag when the new address resolves instead of at the repeated START
// A decoder handles the release conditions, and handles them almost right. It
// clears 'addressed' on a STOP, and relies on the new address byte to clear it
// after a repeated START:
//
// if (frame_stop) begin
// addressed <= 1'b0;
// end else if (cap_valid) begin
// if (own_match) addressed <= 1'b1;
// else addressed <= 1'b0; // <-- a foreign address clears it
// end
//
// The reasoning is sound as far as it goes: after an Sr a new address arrives, and
// if it is not ours the else-branch releases us. Correct, eventually.
//
// It passes a test that issues an Sr, sends another device's address, and then
// checks that we let go -- which is the obvious test to write, and which this
// design passes, because by then the new address HAS resolved.Two devices on the bus, a sensor at 0x48 and an EEPROM at 0x50, and a controller that uses combined transfers -- write a register pointer to the sensor, repeated START, read it back; then a repeated START to address the EEPROM without releasing the bus.
The sensor works. The EEPROM works. Interleaved, the EEPROM read occasionally returns data with bits cleared -- and only when it directly follows a sensor transfer in the same transaction, never when the EEPROM is addressed after a STOP.
It is intermittent in a way that tracks bus speed: slower buses fail less. That points at timing, and the timing measures clean. It also tracks which register the sensor was addressed for, which makes no sense at all and sends the investigation into the sensor's datasheet errata.
Captures are clean. Framing is legal, both addresses are correct on the wire, and the EEPROM's data is well formed apart from being wrong.
The sensor stayed 'addressed' across the repeated START, and for eight clock pulses it and the EEPROM were both addressed at once.
Section 4 is the rule: a device must release at the framing event ITSELF, not once the new address resolves. Between the Sr and the completion of the new address byte there is a window -- eight SCL pulses -- in which this design still reported addressed = 1. During that window the EEPROM's address byte was on the wire, the EEPROM correctly became addressed too, and the sensor had not yet been told to let go.
Nothing bad happens from being addressed alone. What happens is that anything DOWNSTREAM of 'addressed' acts on it. In this device the byte-transfer layer used 'addressed' to decide whether to drive SDA during the acknowledge slot -- so the sensor acknowledged the EEPROM's address byte, and then, because the transfer was a read, began transmitting alongside the EEPROM.
Two transmitters on SDA resolve as the wired-AND (Chapter 6.4, section 7), so the controller read the bitwise AND of the EEPROM's data and whatever the sensor was putting out. That explains every confusing feature:
- bits CLEARED, never set -- AND can only clear - depends on the sensor's register pointer -- that determined what the sensor transmitted, which is one operand of the AND - worse at speed -- the window is a fixed number of SCL pulses, so it is a larger fraction of a fast transfer's timeline - never after a STOP -- a STOP released the sensor correctly; only the Sr path was broken - captures clean -- the bus carried exactly what both devices drove
The test that passed was checking the right signal at the wrong INSTANT. It looked after the new address had resolved, which is the one moment the buggy design is correct.
// Release at the framing event:
//
// end else if (frame_start) begin
// addressed <= 1'b0; // S or Sr: not the target until the new
// gen_call_active <= 1'b0; // address says so
// end else if (cap_valid) begin
// ...
//
// Section 8 confirms it: injecting this exact fault into the corrected design is
// caught by step 8b and by nothing else in the suite.
//
// The verification lessons, in order of how much they generalise.
//
// 1. CHECK THE WINDOW, NOT JUST THE OUTCOME. The original test asked "did we
// let go?" and the answer was yes -- eventually. The right question is "when
// did we let go?", and answering it requires a check between the two events,
// which is step 8b: issue the Sr ALONE and check before any address bits
// follow. Two lines.
//
// 2. THIS IS WHAT ASSERTIONS ARE FOR. Section 7's third property --
// (frame_start || frame_stop) |=> !addressed -- catches it on the first
// transfer of the first test, with no thought about instants at all, because
// it constrains EVERY cycle rather than a chosen one. A procedural test has
// to know where to look; an assertion does not.
//
// 3. SUSPECT "STALE CLAIM" WHEN BITS ARE CLEARED AND NEVER SET. The wired-AND
// signature from Chapter 6.4 appears again here, and it means the same thing:
// two devices transmitting. The new information is that they need not both be
// misconfigured -- one of them can simply have failed to let go.
//
// The design habit: for any flag meaning "I am currently the target", write down
// the exact EVENT that clears it, not the condition that will eventually be
// false. "Cleared when a foreign address arrives" and "cleared at the framing
// event" differ by a window, and a window is where two devices overlap.12. Common Misconceptions
"The decoder decides whether to acknowledge by comparing its address." Comparing is necessary and not sufficient. A prohibition from the reserved map beats a match, and a decoder that checks the match first is non-compliant in exactly the case the prohibition exists for.
"A device given the CBUS address should respond to it." It must not. The footnote forbids I²C-compatible devices from responding, with no exception for a device that holds the address.
"The general call address and the START byte are different addresses." They are the same seven address bits and differ only in the direction bit — which is why the classifier takes all eight and why §6's steps 4 and 5 differ by one bit and invert the required behaviour.
"A device that ignores a general call is broken." General call support is optional in every configuration. Ignoring it is a compliant choice, and §6 instantiates two devices specifically to make both behaviours checkable.
"A repeated START does not need to release the device, because the new address will." It will, eight clock pulses later. In between, the device is claiming a transfer that may belong to somebody else — and §11 is what downstream logic does with that stale claim.
"direction tells the device what to do." It tells the device what the master asked for. Acting on it is the byte-transfer layer's job, and separating the two is what lets the decoder be verified without modelling the ninth clock.
"ack_request should be a level while the device is addressed." It is an event: one pulse per byte to be acknowledged. A level makes one address byte indistinguishable from several, and §8's third mutation shows a consumer counting three where there was one.
"A surviving mutation means the test suite has a hole." Sometimes. Of this module's three survivors, all three were proven equivalent, and one further survivor in this chapter was a genuine gap that needed a new test. Telling them apart requires a proof, not an assumption.
13. Reason It Through
A device is strapped to 0000001. A transfer addresses 0000001. Its comparator matches. What must it do, and what does that imply about the RTL's structure?
It must not respond, because the CBUS footnote forbids it unconditionally. The structural implication is that the classification result has to be available and evaluated first when the match is acted on — so the classifier is a combinational stage feeding the decision, and the decision's first test is the prohibition. Checking the match first produces a device that is compliant except in the one case the rule was written for.
Why must a decoder take the direction bit into account when classifying, not just when latching direction?
Because 0000000 with R/W = 0 is a general call that a device may answer and 0000000 with R/W = 1 is the START byte that no device may answer. A classifier working on seven bits has merged two rows of Table 3 that mean opposite things, and it will acknowledge a byte the specification forbids.
Why must addressed be cleared at a repeated START, while Chapter 6.2's was_selected must survive one?
Because they mean different things. addressed is a claim about the present — I am the target of the transfer now — and after an Sr that is not yet known, so holding it asserts something false for eight clock pulses. was_selected is a record of the past — I matched all ten bits earlier in this transaction — which remains true across the Sr and is the only thing that makes the 10-bit read re-address unambiguous.
A test issues a repeated START, sends another device's address, and confirms this device released. What can that test not detect?
That the device released too late. By the time the new address has resolved, a design that releases on the foreign address and a design that releases at the framing event are indistinguishable — the flag is clear either way. Detecting the difference requires checking in the window between the two events, which means issuing the Sr alone.
Why does the reference model in a verification environment have to share the RTL's decision ordering?
Because if it checks the match before the prohibition, it disagrees with a correct DUT on the CBUS and START-byte cases — and an environment whose model disagrees with a correct design reports a bug that does not exist. Since the ordering is derived rather than stated in the specification, the derivation has to live somewhere both the model and the RTL refer to.
The decoder could be one module instead of three. What is lost?
Independent verification and reviewability. The classifier as a separate combinational block gets an exhaustive 256-combination sweep against an independently-written model; folded in, that is impossible because it would have state and framing dependencies. The reserved-map decode would also stop being a line-by-line transcription of the table, which is the property that makes it checkable by review at all. Synthesis flattens the hierarchy regardless, so the boundaries cost nothing and buy both.
14. Understanding Check
15. Summary
The decoder is assembled, not written. Framing from Module 5, capture from Chapter 6.1, classification from Chapter 6.3, and one new decision stage.
The ordering is the content: prohibition, then own match, then general call. That priority is nowhere stated in the specification and has to be derived from what the footnotes are for — a match is necessary and not sufficient.
Classification needs all eight bits, because two rows of the reserved table share an address and mean opposite things.
ack_request is a decision, not a driver. Whether to acknowledge belongs here; driving the ninth clock slot belongs to Module 7, and the split is what lets this block be verified without modelling the acknowledge cycle.
Release happens at the framing event, not when the next address resolves. The window between them is where two devices overlap, and §11 shows what downstream logic does with a stale claim.
addressed and a 10-bit was_selected have opposite rules across a repeated START, because one is a claim about the present and the other a record of the past.
Three instances were needed to verify three claims: an ordinary match, that ignoring a general call is compliant, and that a prohibition beats a match on a device deliberately given a forbidden address.
One survivor was a real gap and three were provably equivalent. Module 6 totals 27 mutations, 24 caught, 3 proven equivalent — and the proofs matter, because "probably equivalent" would not justify skipping a test.
16. What Comes Next
Module 6 is complete. A transfer can now be framed, addressed, and recognised by exactly the device it was meant for — and that device knows which direction the master asked for.
What happens next is the thing every chapter in this module has deferred to the same place: the acknowledge. Four chapters have produced an ack_request without once saying what an acknowledge is on the wire, which clock pulse carries it, who drives it, or what it does and does not prove.
Module 7 — Byte Transfer & the ACK/NACK Cycle supplies all of it: the byte as the atomic unit of nine clock pulses, the acknowledge slot as a handshake the receiver drives, why the direction of the byte rather than the role of the device decides who acknowledges, and why a master deliberately NACKs the last byte of a read.
Browse the full path on the I²C tutorials index. For the blocks this chapter assembles, see The Address Byte and Reserved Addresses; for the framing it depends on, The START Condition.
Continue learning
Related tutorials
- Related topic
Reading a Complete I²C Frame
Every chapter so far has worked on a fragment — one edge, one byte, one slot. This one assembles them: a method for reading a raw capture by eye, and a passive decoder that turns two wires into framing, address, direction, data and every acknowledge.
- Related topic
The Data-Valid Rule — SDA Stable While SCL Is High
One sentence governs every bit on an I²C bus, and it is derived rather than decreed: the receiver needs a settled value at the instant it looks. What falls out is that an SDA edge while SCL is HIGH cannot be data — which is why the bus reserves it for framing.
- 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
START/STOP Timing and Malformed Framing
Three framing margins, each with two anchor events, all of them minimums: the hold after a START, the setup before a repeated START, and the setup before a STOP. Build a sequencer that generates all three and refuses an illegal configuration, then catalogue the malformed framing the margins exist to prevent.
