I²C · Module 15
General Call and Broadcast Addressing
Address 0x00 reaches every device at once, participation is optional, and declining is done by staying silent. Explains why one acknowledge hides every refusal, why the meaning lives in the second byte, and why bit 0 of that byte turns a command into a master announcing itself.
Fourteen modules have described a bus where a master addresses one device and that device answers. The general call is the exception: address 0000 0000 reaches every device on the bus simultaneously.
That much is easy. What makes the general call worth a chapter is the thing underneath it:
Participation is optional, and a device declines by not acknowledging. So a not-acknowledge here is not an error — it is the documented way to say "this is not for me."
And because the acknowledge lives on a wired-AND, that creates an asymmetry with a consequence the specification states twice in one paragraph: a master broadcasting to a bus full of devices cannot find out how many of them were listening.
1. One Address, Every Device
Two sentences, two rules. The first is the broadcast. The second is that the broadcast is an invitation, not a command — and that it is declined by silence rather than by any positive signal.
That is a design decision worth pausing on, because there was an alternative. The specification could have required every device to acknowledge the general call and then ignore data it did not need. Instead it made the acknowledge itself carry the opt-in. The consequence is that the count of interested devices becomes partially observable — and only partially, as §2 shows.
Note what is immediately adjacent. The general call is 0000 0000 and the START byte is 0000 0001 — the same seven address bits with the direction bit set. A decoder that matches on the address field and ignores the direction bit treats the START byte of Chapter 6.3 as a general call, which is mutation W1 in §9 and the first thing §8's handler is tested against.
2. One Acknowledge Hides Every Refusal
Here is the sentence this chapter exists for, and the specification says the same thing twice because it matters twice.
The mechanism is Chapter 2.5's wired-AND, applied to the acknowledge bit. A device acknowledges by pulling SDA low; it declines by releasing. On a shared open-drain line, one device pulling low and nine releasing produces exactly the same voltage as ten devices pulling low.
So the acknowledge in a broadcast is not a count. It is a logical OR of "at least one device is interested", and the master reads that one bit whatever the truth behind it.
Which gives the master exactly two distinguishable outcomes on a general call:
| what the master reads | what it actually means |
|---|---|
| ACK (someone pulled low) | at least one device is interested. One, or all of them. Unknowable. |
| NACK (nobody pulled) | no device is interested. This one is definite. |
The negative is informative and the positive is not, which is the inverse of the usual situation on an addressed transfer — there, an ACK tells you the device is present and a NACK is ambiguous between "absent", "busy" and "refusing" (Chapter 7.2).
This is the same epistemic limit Chapter 13.5 closed on, in a different costume: the wired-AND preserves the value and destroys the attribution. There it meant a master could learn it had lost arbitration but never to whom. Here it means a master can learn somebody is listening but never how many.
§8's testbench demonstrates it directly by instantiating three handlers with different policies and wiring their acknowledges together — because one device cannot demonstrate a property of three.
3. The Meaning Is In The Second Byte
The general call address says only "this is a broadcast". What kind of broadcast is in the byte after it.
So bit 0 of the second byte is a one-bit opcode that splits the whole feature in half. Get it wrong and a master announcing its own address is obeyed as a reset command — which is mutation W7, and §5 is about why that particular confusion is so easy to make.
3a. Bit B = 0 — The Command Codes
Four rules, and each is a different kind of rule:
06h and 04h differ by exactly one action. Both latch the hardware-strapped part of the address; only 06h also resets. A design that conflates them still passes any test that checks the address was latched, and mutation W4 is exactly that conflation — caught only because §8's bench checks do_reset separately from take_addr.
00h is forbidden as a second byte — not undefined, not reserved: not allowed. §8's handler reports it and acts on nothing, because obeying a forbidden code is worse than ignoring it. This is the distinction Chapter 13.5 §3 drew between undefined and illegal, now on the other side: here the specification really does prohibit something.
Unfixed codes must be ignored. "Ignore" is a third outcome, distinct from obeying and from reporting an error, and §8 gives it its own output so it can be counted. A device that treated an unfixed code as illegal would report a fault on a bus doing nothing wrong — mutation W6.
And the precaution about the supply is a real hazard, not boilerplate. A device emerging from a reset while still pulling SDA or SCL low blocks the bus for everybody, and the recovery for that is Chapter 15.4's subject. A general call that resets forty devices at once is forty simultaneous opportunities to get it wrong.
3b. Bit B = 1 — A Master Announcing Itself
This is a different feature wearing the general call's clothes. The second byte is not a command at all — it is an address, and the device's job is to remember who is talking rather than to do anything.
The problem it solves is a real one. A simple hardware master — the specification's example is a keyboard scanner — has data to send and no idea where to send it. It cannot be configured with a destination address because it has no software. So it broadcasts, says "I am device 0x2C", and leaves it to whichever intelligent device on the bus cares about 0x2C to take the data.
A hardware general call is the bus equivalent of shouting your own name into a room. The protocol carries the identity; the policy of who answers lives somewhere else entirely.
And there is a detail in that last quoted sentence with a consequence worth extracting. If the hardware master can also act as a slave, its slave address equals its master address. So a hardware general call announcing an address that some other device already answers to is an address collision — the conflict of Chapter 6.4, discovered from a broadcast rather than from a failed transfer. §8's handler reports it, because a broadcast is a cheap way to find one.
4. Software Reset Is General Call 06h
Worth stating plainly because the specification gives it its own numbered section and that makes it look like a separate mechanism.
§3.1.14 restates §3.1.13's first sub-code under its own heading. There is no separate software-reset address, no separate command, no separate framing. Software reset is general call 06h, and the only thing §3.1.14 adds is the explicit statement that the feature is optional.
Which matters for a device that does not implement it: not implementing an optional service means not acknowledging it. §8's handler accepts the general call address and then declines the command, which is the correct and slightly counter-intuitive behaviour — the device joined the broadcast and then refused the instruction. Mutation W10 acknowledges a service it does not implement, and the bench catches it.
Chapter 15.2 takes up what "optional" costs a system that wants to rely on it.
5. The Broadcast, Drawn
A general call with 06h: three devices, three policies, and one acknowledge that hides two refusals
10 cyclesRead the bottom two rows together. In the second acknowledge slot device B is refusing a command it does not implement — exactly as the specification requires — and the master reads an acknowledge anyway, because device A pulled low. B's correct refusal is invisible.
The master therefore believes every device on the bus has reset. Two of the three have not, and nothing on the wire says so.
6. What This Means For A Real Design
The asymmetry is not a curiosity; it changes what a general call can be used for.
A general call is safe for idempotent, verify-later operations. "Reset yourselves" is a reasonable broadcast because the follow-up is to read each device individually and check it came back. The broadcast saves time; the per-device read provides the evidence.
A general call is unsafe as the only step in anything that must be confirmed. There is no aggregate confirmation available, and building one out of the acknowledge is not possible — §2 is why.
A NACK on a general call is the useful signal. It means nothing on the bus is interested, which on a system that expects several listeners is a strong indication that the bus, the addressing or the power is wrong. §8's handler counts general calls seen separately from general calls joined for exactly this reason: the ratio is diagnostic and the acknowledge is not.
And the two-byte structure has to be decoded as a unit. The address alone means "broadcast" and carries no instruction. A device that acts on the general call address before seeing the second byte has acted on no information at all.
7. The General Call Handler in Three Languages
The design is the slave-side receiver. It decodes the address, decides whether to participate, interprets the second byte in both of its forms, and — the part most implementations get wrong — distinguishes ignoring from refusing from reporting an error, because the specification asks for all three and they are different outputs.
Bytes arrive already deserialised. Bit-level framing was built in Chapter 7.1 and re-deriving it here would bury the only thing this block is about.
// -----------------------------------------------------------------------------
// i2c_general_call_handler.sv
// Slave-side general call receiver (UM10204 3.1.13, 3.1.14, Table 3).
//
// The general call is address 0000 0000 with R/W = 0, and it is the one address
// every device on the bus decodes at once. What makes it interesting is not the
// broadcast; it is that participation is OPTIONAL and declining is done by
// staying silent:
//
// "if a device does not need any of the data supplied within the general call
// structure, it can ignore this address by not issuing an acknowledgment."
//
// So a NACK here is not an error. It is the documented way to opt out, and a
// design that reports it as a fault is reporting correct behaviour as a bug.
//
// Eight obligations are encoded:
//
// 1. Recognise 0000 0000 with R/W = 0 as the general call. Nothing else.
// 2. Acknowledge only if this device actually wants the data. Opting out is
// legal and must leave no error flag set.
// 3. Having opted out, ignore every following byte of that general call --
// not just decline to act on it, but do not even interpret it.
// 4. The MEANING is in the second byte, never the first.
// 5. Second byte 0000 0110 (06h): reset AND take in the programmable part of
// the address. Second byte 0000 0100 (04h): take the address, no reset.
// The two differ by exactly one action and are otherwise identical.
// 6. Second byte 0000 0000 (00h) is NOT ALLOWED as a second byte. Report it;
// do not act on it.
// 7. Every other code with B = 0 "has not been fixed and devices must ignore
// them". Ignoring is an action with a name here, so it can be counted.
// 8. If the second byte's LSB (bit B) is 1, the sequence is a HARDWARE general
// call and the upper seven bits are the sending master's OWN address, not a
// command. Misreading a hardware general call as a command code is the
// classic general-call bug, and bit 0 is the only thing that distinguishes
// them. Because a hardware master that can also act as a slave uses the same
// address for both, an announced address equal to ours is an address
// collision -- reported, since a broadcast is a cheap way to discover one.
//
// A device may also refuse an individual byte it cannot process, and the
// specification is explicit that the master will not learn of it if any other
// device acknowledged. That asymmetry is a property of the bus rather than of
// this block, so it is demonstrated in the testbench by wiring three of these
// handlers together -- not modelled here.
//
// Bytes arrive already deserialised. Bit-level framing was built in Module 7 and
// re-deriving it here would obscure the only thing this block is about.
// -----------------------------------------------------------------------------
module i2c_general_call_handler #(
parameter int CNT_W = 8
) (
input logic clk,
input logic rst_n,
// ---- byte-level bus interface ------------------------------------------
input logic start_seen, // a START or repeated START occurred
input logic stop_seen, // a STOP occurred
input logic byte_valid, // byte_in is a complete received byte
input logic [7:0] byte_in,
input logic is_addr_byte, // this byte is the first after a START
// ---- device configuration ----------------------------------------------
input logic [6:0] my_addr,
input logic wants_gc_data, // does this device use general call data?
input logic supports_sw_reset, // does it implement 06h?
input logic supports_prog_addr, // does it implement the programmable address?
input logic [6:0] prog_addr_in, // the hardware-strapped address to take in
// ---- outputs ------------------------------------------------------------
output logic ack, // pull SDA low in the ninth bit
output logic gc_active, // inside a general call we joined
output logic do_reset, // pulse: perform a software reset
output logic take_addr, // pulse: latch prog_addr_in
output logic [6:0] latched_addr,
output logic illegal_second_byte, // 00h appeared as the second byte
output logic ignored_code, // an unfixed code was seen and ignored
output logic hw_gc_seen, // the sequence was a hardware general call
output logic [6:0] hw_master_addr, // that master's own address
output logic hw_addr_collision, // that address is also OURS
output logic opted_out, // we declined this general call
output logic [2:0] state,
output logic [CNT_W-1:0] gc_seen_count, // general calls addressed to the bus
output logic [CNT_W-1:0] gc_joined_count // ones we acknowledged
);
localparam [7:0] ADDR_GENERAL_CALL = 8'b0000_0000; // address 0000000 + W
localparam [7:0] SECOND_RESET_ADDR = 8'b0000_0110; // 06h
localparam [7:0] SECOND_TAKE_ADDR = 8'b0000_0100; // 04h
localparam [7:0] SECOND_ILLEGAL = 8'b0000_0000; // 00h
localparam [2:0] S_IDLE = 3'd0,
S_ADDR = 3'd1, // the general call address was just accepted
S_SECOND = 3'd2, // awaiting the byte that carries the meaning
S_DATA = 3'd3, // further data bytes of a command sequence
S_HWGC = 3'd4, // a hardware general call: data is for the host
S_MUTE = 3'd5; // we opted out; ignore everything until STOP
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
ack <= 1'b0;
gc_active <= 1'b0;
do_reset <= 1'b0;
take_addr <= 1'b0;
latched_addr <= 7'd0;
illegal_second_byte <= 1'b0;
ignored_code <= 1'b0;
hw_gc_seen <= 1'b0;
hw_master_addr <= 7'd0;
hw_addr_collision <= 1'b0;
opted_out <= 1'b0;
gc_seen_count <= {CNT_W{1'b0}};
gc_joined_count <= {CNT_W{1'b0}};
end else begin
// Single-cycle outputs.
ack <= 1'b0;
do_reset <= 1'b0;
take_addr <= 1'b0;
// A STOP ends any general call. A START restarts the decode, which is
// what lets a repeated START begin a fresh general call without a STOP.
if (stop_seen) begin
state <= S_IDLE;
gc_active <= 1'b0;
end else if (start_seen) begin
state <= S_IDLE;
gc_active <= 1'b0;
end else if (byte_valid) begin
case (state)
// ---------------------------------------------------------------
// Obligation 1: only 0000 0000 is the general call. In particular
// 0000 0001 is the START byte and must NOT be treated as one.
// ---------------------------------------------------------------
S_IDLE: begin
if (is_addr_byte && (byte_in == ADDR_GENERAL_CALL)) begin
gc_seen_count <= gc_seen_count + 1'b1;
// Obligation 2: acknowledge only if we want the data.
// Declining is legal and sets no error.
if (wants_gc_data) begin
ack <= 1'b1;
gc_active <= 1'b1;
opted_out <= 1'b0;
gc_joined_count <= gc_joined_count + 1'b1;
state <= S_SECOND;
end else begin
// Obligation 3: silence now, and silence for the rest of
// this general call.
opted_out <= 1'b1;
state <= S_MUTE;
end
end
// Any other address byte is not our business here; the ordinary
// address decoder of Chapter 6.5 handles it.
end
// ---------------------------------------------------------------
// Obligation 4: the second byte carries the meaning.
// Obligation 8: bit 0 selects command vs hardware general call.
// ---------------------------------------------------------------
S_SECOND: begin
if (byte_in[0]) begin
// A hardware general call. The upper seven bits are the
// sending master's own address, NOT a command.
hw_gc_seen <= 1'b1;
hw_master_addr <= byte_in[7:1];
// 3.1.13: "If the hardware master can also act as a slave,
// the slave address is identical to the master address." So a
// hardware master announcing OUR address means two devices
// answer to it -- the conflict of Chapter 6.4, discovered from
// a broadcast rather than from a failed transfer.
if (byte_in[7:1] == my_addr) hw_addr_collision <= 1'b1;
ack <= 1'b1;
state <= S_HWGC;
end else begin
case (byte_in)
SECOND_ILLEGAL: begin
// Obligation 6: 00h is not allowed here. Report it and
// take no action -- acting on a forbidden code is worse
// than ignoring it.
illegal_second_byte <= 1'b1;
state <= S_MUTE;
end
SECOND_RESET_ADDR: begin
// Obligation 5, the 06h half: reset AND take address.
if (supports_sw_reset) begin
do_reset <= 1'b1;
ack <= 1'b1;
if (supports_prog_addr) begin
take_addr <= 1'b1;
latched_addr <= prog_addr_in;
end
state <= S_DATA;
end else begin
// "This feature is optional and not all devices
// respond to this command." Not supporting it means
// not acknowledging it.
state <= S_MUTE;
end
end
SECOND_TAKE_ADDR: begin
// Obligation 5, the 04h half: identical, minus the reset.
if (supports_prog_addr) begin
take_addr <= 1'b1;
latched_addr <= prog_addr_in;
ack <= 1'b1;
state <= S_DATA;
end else begin
state <= S_MUTE;
end
end
default: begin
// Obligation 7: "The remaining codes have not been
// fixed and devices must ignore them."
ignored_code <= 1'b1;
state <= S_MUTE;
end
endcase
end
end
// Further bytes of a command sequence we joined.
S_DATA: begin
ack <= 1'b1;
end
// A hardware general call's payload is addressed to whichever
// intelligent device recognises that master, so this device
// continues to accept bytes but performs no command decode.
S_HWGC: begin
ack <= 1'b1;
end
// Obligation 3: opted out, or shut down by an illegal or unfixed
// code. Interpret nothing until the transaction ends.
S_MUTE: begin
ack <= 1'b0;
end
default: state <= S_IDLE;
endcase
end
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_general_call_handler_tb.sv
// Independent oracle for i2c_general_call_handler.
//
// The bench instantiates THREE handlers with different opt-in policies and wires
// their acknowledges together through a wired-AND, because the property the
// specification is most emphatic about is not a property of one device:
//
// "if one or more slaves acknowledge, the not-acknowledge will not be seen by
// the master."
//
// One device cannot demonstrate that. Three can, and test 3 does: two devices
// decline, one accepts, and the value the master reads back is indistinguishable
// from every device having accepted.
// -----------------------------------------------------------------------------
module i2c_general_call_handler_tb;
localparam [2:0] S_IDLE = 3'd0, S_ADDR = 3'd1, S_SECOND = 3'd2,
S_DATA = 3'd3, S_HWGC = 3'd4, S_MUTE = 3'd5;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic start_seen = 1'b0;
logic stop_seen = 1'b0;
logic byte_valid = 1'b0;
logic [7:0] byte_in = 8'h00;
logic is_addr_byte = 1'b0;
// Three devices, three policies.
// A wants general call data, supports reset and the programmable address.
// B wants general call data but implements NEITHER optional service.
// C does not want general call data at all.
logic [6:0] a_my_addr = 7'h20, b_my_addr = 7'h21, c_my_addr = 7'h22;
logic a_wants = 1'b1, b_wants = 1'b1, c_wants = 1'b0;
logic a_swrst = 1'b1, b_swrst = 1'b0, c_swrst = 1'b1;
logic a_prog = 1'b1, b_prog = 1'b0, c_prog = 1'b1;
logic [6:0] a_prog_addr = 7'h4A, b_prog_addr = 7'h4B, c_prog_addr = 7'h4C;
logic a_ack, b_ack, c_ack;
logic a_gc_active, b_gc_active, c_gc_active;
logic a_do_reset, b_do_reset, c_do_reset;
logic a_take_addr, b_take_addr, c_take_addr;
logic [6:0] a_latched, b_latched, c_latched;
logic a_illegal, b_illegal, c_illegal;
logic a_ignored, b_ignored, c_ignored;
logic a_hwgc, b_hwgc, c_hwgc;
logic [6:0] a_hwaddr, b_hwaddr, c_hwaddr;
logic a_collide, b_collide, c_collide;
logic a_opted, b_opted, c_opted;
logic [2:0] a_state, b_state, c_state;
logic [7:0] a_seen, b_seen, c_seen;
logic [7:0] a_joined, b_joined, c_joined;
integer errors = 0;
integer n;
// THE WIRED-AND. The master sees a LOW in the ninth bit if ANY device pulls,
// so the observable acknowledge is the OR of the three devices' pulls.
wire master_sees_ack = a_ack | b_ack | c_ack;
i2c_general_call_handler #(.CNT_W(8)) dev_a (
.clk(clk), .rst_n(rst_n), .start_seen(start_seen), .stop_seen(stop_seen),
.byte_valid(byte_valid), .byte_in(byte_in), .is_addr_byte(is_addr_byte),
.my_addr(a_my_addr), .wants_gc_data(a_wants), .supports_sw_reset(a_swrst),
.supports_prog_addr(a_prog), .prog_addr_in(a_prog_addr),
.ack(a_ack), .gc_active(a_gc_active), .do_reset(a_do_reset),
.take_addr(a_take_addr), .latched_addr(a_latched),
.illegal_second_byte(a_illegal), .ignored_code(a_ignored),
.hw_gc_seen(a_hwgc), .hw_master_addr(a_hwaddr),
.hw_addr_collision(a_collide), .opted_out(a_opted), .state(a_state),
.gc_seen_count(a_seen), .gc_joined_count(a_joined));
i2c_general_call_handler #(.CNT_W(8)) dev_b (
.clk(clk), .rst_n(rst_n), .start_seen(start_seen), .stop_seen(stop_seen),
.byte_valid(byte_valid), .byte_in(byte_in), .is_addr_byte(is_addr_byte),
.my_addr(b_my_addr), .wants_gc_data(b_wants), .supports_sw_reset(b_swrst),
.supports_prog_addr(b_prog), .prog_addr_in(b_prog_addr),
.ack(b_ack), .gc_active(b_gc_active), .do_reset(b_do_reset),
.take_addr(b_take_addr), .latched_addr(b_latched),
.illegal_second_byte(b_illegal), .ignored_code(b_ignored),
.hw_gc_seen(b_hwgc), .hw_master_addr(b_hwaddr),
.hw_addr_collision(b_collide), .opted_out(b_opted), .state(b_state),
.gc_seen_count(b_seen), .gc_joined_count(b_joined));
i2c_general_call_handler #(.CNT_W(8)) dev_c (
.clk(clk), .rst_n(rst_n), .start_seen(start_seen), .stop_seen(stop_seen),
.byte_valid(byte_valid), .byte_in(byte_in), .is_addr_byte(is_addr_byte),
.my_addr(c_my_addr), .wants_gc_data(c_wants), .supports_sw_reset(c_swrst),
.supports_prog_addr(c_prog), .prog_addr_in(c_prog_addr),
.ack(c_ack), .gc_active(c_gc_active), .do_reset(c_do_reset),
.take_addr(c_take_addr), .latched_addr(c_latched),
.illegal_second_byte(c_illegal), .ignored_code(c_ignored),
.hw_gc_seen(c_hwgc), .hw_master_addr(c_hwaddr),
.hw_addr_collision(c_collide), .opted_out(c_opted), .state(c_state),
.gc_seen_count(c_seen), .gc_joined_count(c_joined));
always #5 clk = ~clk;
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset_all;
begin
@(negedge clk);
rst_n = 1'b0; start_seen = 1'b0; stop_seen = 1'b0;
byte_valid = 1'b0; is_addr_byte = 1'b0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
task pulse_start;
begin @(negedge clk); start_seen = 1'b1; @(posedge clk); @(negedge clk); start_seen = 1'b0; end
endtask
task pulse_stop;
begin @(negedge clk); stop_seen = 1'b1; @(posedge clk); @(negedge clk); stop_seen = 1'b0; end
endtask
task send_addr (input [7:0] b);
begin
@(negedge clk); byte_in = b; is_addr_byte = 1'b1; byte_valid = 1'b1;
@(posedge clk); @(negedge clk); byte_valid = 1'b0; is_addr_byte = 1'b0;
end
endtask
task send_data (input [7:0] b);
begin
@(negedge clk); byte_in = b; is_addr_byte = 1'b0; byte_valid = 1'b1;
@(posedge clk); @(negedge clk); byte_valid = 1'b0;
end
endtask
task ck_int (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input got, input exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0b expected %0b", what, got, exp);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_general_call_handler: broadcast with optional participation ===");
// ----------------------------------------------------------------
// T1. The general call address is 0000 0000 and nothing else. In
// particular 0000 0001 is the START byte and 0000 0010 is a CBUS
// address; neither must be mistaken for a general call.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0001); // START byte
step;
$display("T1 only 0000 0000 is the general call");
ck_int("T1 A saw no general call", a_seen, 0);
ck_bit("T1 A did not acknowledge", a_ack, 1'b0);
pulse_stop;
pulse_start;
send_addr(8'b0000_0010); // CBUS address
step;
ck_int("T1 CBUS not counted", a_seen, 0);
pulse_stop;
pulse_start;
send_addr(8'b0000_0000); // the real thing
step;
ck_int("T1 general call counted", a_seen, 1);
// ----------------------------------------------------------------
// T2. Opting out. Device C does not want general call data, so it must
// NACK the address, set no error flag, and ignore the rest.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
step;
$display("T2 declining a general call is legal, not an error");
ck_bit("T2 C did not acknowledge", c_ack, 1'b0);
ck_bit("T2 C recorded opting out", c_opted, 1'b1);
ck_bit("T2 C flagged no error", c_illegal, 1'b0);
ck_bit("T2 C flagged no ignored code", c_ignored, 1'b0);
ck_int("T2 C counted the call", c_seen, 1);
ck_int("T2 C joined nothing", c_joined, 0);
ck_int("T2 C went mute", c_state, S_MUTE);
// and A, which does want the data, acknowledged
ck_bit("T2 A acknowledged", a_gc_active, 1'b1);
ck_int("T2 A joined", a_joined, 1);
// ----------------------------------------------------------------
// T3. THE ASYMMETRY. Two devices decline, one accepts. The wired-AND
// gives the master a LOW in the ninth bit, which is exactly what it
// would see if all three had accepted. The master cannot tell.
// ----------------------------------------------------------------
do_reset_all;
@(negedge clk); a_wants = 1'b1; b_wants = 1'b0; c_wants = 1'b0;
pulse_start;
send_addr(8'b0000_0000);
$display("T3 one acknowledge masks every not-acknowledge");
ck_bit("T3 A pulls", a_ack, 1'b1);
ck_bit("T3 B stays silent", b_ack, 1'b0);
ck_bit("T3 C stays silent", c_ack, 1'b0);
ck_bit("T3 the master sees ACK", master_sees_ack, 1'b1);
step;
// Now the case the master genuinely can distinguish: NOBODY wants it.
do_reset_all;
@(negedge clk); a_wants = 1'b0; b_wants = 1'b0; c_wants = 1'b0;
pulse_start;
send_addr(8'b0000_0000);
ck_bit("T3 with nobody interested the master sees NACK", master_sees_ack, 1'b0);
step;
ck_bit("T3 all three opted out (A)", a_opted, 1'b1);
ck_bit("T3 all three opted out (B)", b_opted, 1'b1);
ck_bit("T3 all three opted out (C)", c_opted, 1'b1);
@(negedge clk); a_wants = 1'b1; b_wants = 1'b1; c_wants = 1'b0;
// ----------------------------------------------------------------
// T4. Second byte 06h: reset AND take the programmable address.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h06);
$display("T4 06h resets and takes the programmable address");
ck_bit("T4 A reset", a_do_reset, 1'b1);
ck_bit("T4 A took the address", a_take_addr, 1'b1);
step;
ck_int("T4 A latched 0x4A", a_latched, 7'h4A);
ck_bit("T4 A raised no error", a_illegal, 1'b0);
// ----------------------------------------------------------------
// T5. Second byte 04h: the SAME, minus the reset. The two codes differ
// by exactly one action, and a design that conflates them passes
// every test that does not check do_reset separately.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h04);
$display("T5 04h takes the address WITHOUT resetting");
ck_bit("T5 A did NOT reset", a_do_reset, 1'b0);
ck_bit("T5 A took the address", a_take_addr, 1'b1);
step;
ck_int("T5 A latched 0x4A", a_latched, 7'h4A);
// ----------------------------------------------------------------
// T6. Second byte 00h is not allowed. Report it and act on nothing.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h00);
$display("T6 00h as a second byte is reported and not obeyed");
ck_bit("T6 A flagged it illegal", a_illegal, 1'b1);
ck_bit("T6 A did not reset", a_do_reset, 1'b0);
ck_bit("T6 A did not take an address", a_take_addr, 1'b0);
step;
ck_int("T6 A went mute", a_state, S_MUTE);
ck_bit("T6 A stopped acknowledging", a_ack, 1'b0);
// ----------------------------------------------------------------
// T7. An unfixed code must be IGNORED -- a distinct outcome from both
// obeying it and from calling it illegal.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h08); // not 00h, 04h or 06h; B = 0
$display("T7 an unfixed command code is ignored, not obeyed");
ck_bit("T7 A flagged it ignored", a_ignored, 1'b1);
ck_bit("T7 A did NOT call it illegal", a_illegal, 1'b0);
ck_bit("T7 A did not reset", a_do_reset, 1'b0);
ck_bit("T7 A did not take an address", a_take_addr, 1'b0);
step;
ck_int("T7 A went mute", a_state, S_MUTE);
// ----------------------------------------------------------------
// T8. THE HARDWARE GENERAL CALL. Bit 0 of the second byte is the only
// thing distinguishing a command from a master announcing itself.
// 0000 0111 has B = 1, so it is NOT the 06h reset command with a
// stray bit -- it is hardware master 0000 011.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'b0000_0111); // B = 1 -> hardware general call
$display("T8 bit 0 set makes it a hardware general call, not a command");
ck_bit("T8 A saw a hardware general call", a_hwgc, 1'b1);
ck_bit("T8 A did NOT reset", a_do_reset, 1'b0);
ck_bit("T8 A did NOT take an address", a_take_addr, 1'b0);
ck_bit("T8 A raised no illegal flag", a_illegal, 1'b0);
ck_bit("T8 A raised no ignored flag", a_ignored, 1'b0);
step;
ck_int("T8 A captured master address 0x03", a_hwaddr, 7'h03);
ck_int("T8 A is in the hardware GC state", a_state, S_HWGC);
// ----------------------------------------------------------------
// T9. A hardware master announcing OUR address is an address collision.
// Device A is at 0x20, so a master announcing 0x20 collides.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data({7'h20, 1'b1}); // hardware master says "I am 0x20"
step;
$display("T9 a hardware master announcing our address is a collision");
ck_bit("T9 A detected the collision", a_collide, 1'b1);
ck_int("T9 A recorded the address", a_hwaddr, 7'h20);
ck_bit("T9 B saw no collision", b_collide, 1'b0);
ck_int("T9 B recorded it too", b_hwaddr, 7'h20);
// ----------------------------------------------------------------
// T10. An optional service that is NOT implemented must not be
// acknowledged. Device B wants general call data but implements
// neither 06h nor the programmable address, so it accepts the
// address and then declines the command.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
ck_bit("T10 B accepted the general call address", b_ack, 1'b1);
send_data(8'h06);
$display("T10 an unimplemented optional service is not acknowledged");
ck_bit("T10 B did not reset", b_do_reset, 1'b0);
ck_bit("T10 B took no address", b_take_addr, 1'b0);
ck_bit("T10 B did not acknowledge the command", b_ack, 1'b0);
// and A, which does implement it, did
ck_bit("T10 A reset", a_do_reset, 1'b1);
step;
ck_int("T10 B went mute", b_state, S_MUTE);
// ----------------------------------------------------------------
// T11. A repeated START begins a fresh general call with no STOP, and
// a STOP ends one. Both must clear gc_active.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h04);
step;
ck_bit("T11 A is in a general call", a_gc_active, 1'b1);
$display("T11 a repeated START begins a fresh general call; a STOP ends one");
pulse_start; // repeated START, no STOP
ck_bit("T11 repeated START cleared it", a_gc_active, 1'b0);
send_addr(8'b0000_0000);
step;
ck_int("T11 a second general call was counted", a_seen, 2);
pulse_stop;
ck_bit("T11 STOP cleared it", a_gc_active, 1'b0);
// ----------------------------------------------------------------
// T12. Having gone mute, further bytes change nothing. Five data bytes
// after an illegal second byte must leave every output alone.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h00); // illegal -> mute
for (n = 0; n < 5; n = n + 1) begin
send_data(8'h06); // would be a reset command if interpreted
ck_bit("T12 muted device never resets", a_do_reset, 1'b0);
ck_bit("T12 muted device never acks", a_ack, 1'b0);
end
$display("T12 a muted device interprets nothing until the transaction ends");
ck_int("T12 still mute", a_state, S_MUTE);
if (errors == 0)
$display("=== i2c_general_call_handler: ALL CHECKS PASSED ===");
else
$display("=== i2c_general_call_handler: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule // -----------------------------------------------------------------------------
// i2c_general_call_handler.sv
// Slave-side general call receiver (UM10204 3.1.13, 3.1.14, Table 3).
//
// The general call is address 0000 0000 with R/W = 0, and it is the one address
// every device on the bus decodes at once. What makes it interesting is not the
// broadcast; it is that participation is OPTIONAL and declining is done by
// staying silent:
//
// "if a device does not need any of the data supplied within the general call
// structure, it can ignore this address by not issuing an acknowledgment."
//
// So a NACK here is not an error. It is the documented way to opt out, and a
// design that reports it as a fault is reporting correct behaviour as a bug.
//
// Eight obligations are encoded:
//
// 1. Recognise 0000 0000 with R/W = 0 as the general call. Nothing else.
// 2. Acknowledge only if this device actually wants the data. Opting out is
// legal and must leave no error flag set.
// 3. Having opted out, ignore every following byte of that general call --
// not just decline to act on it, but do not even interpret it.
// 4. The MEANING is in the second byte, never the first.
// 5. Second byte 0000 0110 (06h): reset AND take in the programmable part of
// the address. Second byte 0000 0100 (04h): take the address, no reset.
// The two differ by exactly one action and are otherwise identical.
// 6. Second byte 0000 0000 (00h) is NOT ALLOWED as a second byte. Report it;
// do not act on it.
// 7. Every other code with B = 0 "has not been fixed and devices must ignore
// them". Ignoring is an action with a name here, so it can be counted.
// 8. If the second byte's LSB (bit B) is 1, the sequence is a HARDWARE general
// call and the upper seven bits are the sending master's OWN address, not a
// command. Misreading a hardware general call as a command code is the
// classic general-call bug, and bit 0 is the only thing that distinguishes
// them. Because a hardware master that can also act as a slave uses the same
// address for both, an announced address equal to ours is an address
// collision -- reported, since a broadcast is a cheap way to discover one.
//
// A device may also refuse an individual byte it cannot process, and the
// specification is explicit that the master will not learn of it if any other
// device acknowledged. That asymmetry is a property of the bus rather than of
// this block, so it is demonstrated in the testbench by wiring three of these
// handlers together -- not modelled here.
//
// Bytes arrive already deserialised. Bit-level framing was built in Module 7 and
// re-deriving it here would obscure the only thing this block is about.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_general_call_handler #(
parameter CNT_W = 8
) (
input wire clk,
input wire rst_n,
// ---- byte-level bus interface ------------------------------------------
input wire start_seen, // a START or repeated START occurred
input wire stop_seen, // a STOP occurred
input wire byte_valid, // byte_in is a complete received byte
input wire [7:0] byte_in,
input wire is_addr_byte, // this byte is the first after a START
// ---- device configuration ----------------------------------------------
input wire [6:0] my_addr,
input wire wants_gc_data, // does this device use general call data?
input wire supports_sw_reset, // does it implement 06h?
input wire supports_prog_addr, // does it implement the programmable address?
input wire [6:0] prog_addr_in, // the hardware-strapped address to take in
// ---- outputs ------------------------------------------------------------
output reg ack, // pull SDA low in the ninth bit
output reg gc_active, // inside a general call we joined
output reg do_reset, // pulse: perform a software reset
output reg take_addr, // pulse: latch prog_addr_in
output reg [6:0] latched_addr,
output reg illegal_second_byte, // 00h appeared as the second byte
output reg ignored_code, // an unfixed code was seen and ignored
output reg hw_gc_seen, // the sequence was a hardware general call
output reg [6:0] hw_master_addr, // that master's own address
output reg hw_addr_collision, // that address is also OURS
output reg opted_out, // we declined this general call
output reg [2:0] state,
output reg [CNT_W-1:0] gc_seen_count, // general calls addressed to the bus
output reg [CNT_W-1:0] gc_joined_count // ones we acknowledged
);
localparam [7:0] ADDR_GENERAL_CALL = 8'b0000_0000; // address 0000000 + W
localparam [7:0] SECOND_RESET_ADDR = 8'b0000_0110; // 06h
localparam [7:0] SECOND_TAKE_ADDR = 8'b0000_0100; // 04h
localparam [7:0] SECOND_ILLEGAL = 8'b0000_0000; // 00h
localparam [2:0] S_IDLE = 3'd0,
S_ADDR = 3'd1, // the general call address was just accepted
S_SECOND = 3'd2, // awaiting the byte that carries the meaning
S_DATA = 3'd3, // further data bytes of a command sequence
S_HWGC = 3'd4, // a hardware general call: data is for the host
S_MUTE = 3'd5; // we opted out; ignore everything until STOP
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
ack <= 1'b0;
gc_active <= 1'b0;
do_reset <= 1'b0;
take_addr <= 1'b0;
latched_addr <= 7'd0;
illegal_second_byte <= 1'b0;
ignored_code <= 1'b0;
hw_gc_seen <= 1'b0;
hw_master_addr <= 7'd0;
hw_addr_collision <= 1'b0;
opted_out <= 1'b0;
gc_seen_count <= {CNT_W{1'b0}};
gc_joined_count <= {CNT_W{1'b0}};
end else begin
// Single-cycle outputs.
ack <= 1'b0;
do_reset <= 1'b0;
take_addr <= 1'b0;
// A STOP ends any general call. A START restarts the decode, which is
// what lets a repeated START begin a fresh general call without a STOP.
if (stop_seen) begin
state <= S_IDLE;
gc_active <= 1'b0;
end else if (start_seen) begin
state <= S_IDLE;
gc_active <= 1'b0;
end else if (byte_valid) begin
case (state)
// ---------------------------------------------------------------
// Obligation 1: only 0000 0000 is the general call. In particular
// 0000 0001 is the START byte and must NOT be treated as one.
// ---------------------------------------------------------------
S_IDLE: begin
if (is_addr_byte && (byte_in == ADDR_GENERAL_CALL)) begin
gc_seen_count <= gc_seen_count + 1'b1;
// Obligation 2: acknowledge only if we want the data.
// Declining is legal and sets no error.
if (wants_gc_data) begin
ack <= 1'b1;
gc_active <= 1'b1;
opted_out <= 1'b0;
gc_joined_count <= gc_joined_count + 1'b1;
state <= S_SECOND;
end else begin
// Obligation 3: silence now, and silence for the rest of
// this general call.
opted_out <= 1'b1;
state <= S_MUTE;
end
end
// Any other address byte is not our business here; the ordinary
// address decoder of Chapter 6.5 handles it.
end
// ---------------------------------------------------------------
// Obligation 4: the second byte carries the meaning.
// Obligation 8: bit 0 selects command vs hardware general call.
// ---------------------------------------------------------------
S_SECOND: begin
if (byte_in[0]) begin
// A hardware general call. The upper seven bits are the
// sending master's own address, NOT a command.
hw_gc_seen <= 1'b1;
hw_master_addr <= byte_in[7:1];
// 3.1.13: "If the hardware master can also act as a slave,
// the slave address is identical to the master address." So a
// hardware master announcing OUR address means two devices
// answer to it -- the conflict of Chapter 6.4, discovered from
// a broadcast rather than from a failed transfer.
if (byte_in[7:1] == my_addr) hw_addr_collision <= 1'b1;
ack <= 1'b1;
state <= S_HWGC;
end else begin
case (byte_in)
SECOND_ILLEGAL: begin
// Obligation 6: 00h is not allowed here. Report it and
// take no action -- acting on a forbidden code is worse
// than ignoring it.
illegal_second_byte <= 1'b1;
state <= S_MUTE;
end
SECOND_RESET_ADDR: begin
// Obligation 5, the 06h half: reset AND take address.
if (supports_sw_reset) begin
do_reset <= 1'b1;
ack <= 1'b1;
if (supports_prog_addr) begin
take_addr <= 1'b1;
latched_addr <= prog_addr_in;
end
state <= S_DATA;
end else begin
// "This feature is optional and not all devices
// respond to this command." Not supporting it means
// not acknowledging it.
state <= S_MUTE;
end
end
SECOND_TAKE_ADDR: begin
// Obligation 5, the 04h half: identical, minus the reset.
if (supports_prog_addr) begin
take_addr <= 1'b1;
latched_addr <= prog_addr_in;
ack <= 1'b1;
state <= S_DATA;
end else begin
state <= S_MUTE;
end
end
default: begin
// Obligation 7: "The remaining codes have not been
// fixed and devices must ignore them."
ignored_code <= 1'b1;
state <= S_MUTE;
end
endcase
end
end
// Further bytes of a command sequence we joined.
S_DATA: begin
ack <= 1'b1;
end
// A hardware general call's payload is addressed to whichever
// intelligent device recognises that master, so this device
// continues to accept bytes but performs no command decode.
S_HWGC: begin
ack <= 1'b1;
end
// Obligation 3: opted out, or shut down by an illegal or unfixed
// code. Interpret nothing until the transaction ends.
S_MUTE: begin
ack <= 1'b0;
end
default: state <= S_IDLE;
endcase
end
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_general_call_handler_tb.sv
// Independent oracle for i2c_general_call_handler.
//
// The bench instantiates THREE handlers with different opt-in policies and wires
// their acknowledges together through a wired-AND, because the property the
// specification is most emphatic about is not a property of one device:
//
// "if one or more slaves acknowledge, the not-acknowledge will not be seen by
// the master."
//
// One device cannot demonstrate that. Three can, and test 3 does: two devices
// decline, one accepts, and the value the master reads back is indistinguishable
// from every device having accepted.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_general_call_handler_tb;
localparam [2:0] S_IDLE = 3'd0, S_ADDR = 3'd1, S_SECOND = 3'd2,
S_DATA = 3'd3, S_HWGC = 3'd4, S_MUTE = 3'd5;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg start_seen = 1'b0;
reg stop_seen = 1'b0;
reg byte_valid = 1'b0;
reg [7:0] byte_in = 8'h00;
reg is_addr_byte = 1'b0;
// Three devices, three policies.
// A wants general call data, supports reset and the programmable address.
// B wants general call data but implements NEITHER optional service.
// C does not want general call data at all.
reg [6:0] a_my_addr = 7'h20, b_my_addr = 7'h21, c_my_addr = 7'h22;
reg a_wants = 1'b1, b_wants = 1'b1, c_wants = 1'b0;
reg a_swrst = 1'b1, b_swrst = 1'b0, c_swrst = 1'b1;
reg a_prog = 1'b1, b_prog = 1'b0, c_prog = 1'b1;
reg [6:0] a_prog_addr = 7'h4A, b_prog_addr = 7'h4B, c_prog_addr = 7'h4C;
wire a_ack, b_ack, c_ack;
wire a_gc_active, b_gc_active, c_gc_active;
wire a_do_reset, b_do_reset, c_do_reset;
wire a_take_addr, b_take_addr, c_take_addr;
wire [6:0] a_latched, b_latched, c_latched;
wire a_illegal, b_illegal, c_illegal;
wire a_ignored, b_ignored, c_ignored;
wire a_hwgc, b_hwgc, c_hwgc;
wire [6:0] a_hwaddr, b_hwaddr, c_hwaddr;
wire a_collide, b_collide, c_collide;
wire a_opted, b_opted, c_opted;
wire [2:0] a_state, b_state, c_state;
wire [7:0] a_seen, b_seen, c_seen;
wire [7:0] a_joined, b_joined, c_joined;
integer errors = 0;
integer n;
// THE WIRED-AND. The master sees a LOW in the ninth bit if ANY device pulls,
// so the observable acknowledge is the OR of the three devices' pulls.
wire master_sees_ack = a_ack | b_ack | c_ack;
i2c_general_call_handler #(.CNT_W(8)) dev_a (
.clk(clk), .rst_n(rst_n), .start_seen(start_seen), .stop_seen(stop_seen),
.byte_valid(byte_valid), .byte_in(byte_in), .is_addr_byte(is_addr_byte),
.my_addr(a_my_addr), .wants_gc_data(a_wants), .supports_sw_reset(a_swrst),
.supports_prog_addr(a_prog), .prog_addr_in(a_prog_addr),
.ack(a_ack), .gc_active(a_gc_active), .do_reset(a_do_reset),
.take_addr(a_take_addr), .latched_addr(a_latched),
.illegal_second_byte(a_illegal), .ignored_code(a_ignored),
.hw_gc_seen(a_hwgc), .hw_master_addr(a_hwaddr),
.hw_addr_collision(a_collide), .opted_out(a_opted), .state(a_state),
.gc_seen_count(a_seen), .gc_joined_count(a_joined));
i2c_general_call_handler #(.CNT_W(8)) dev_b (
.clk(clk), .rst_n(rst_n), .start_seen(start_seen), .stop_seen(stop_seen),
.byte_valid(byte_valid), .byte_in(byte_in), .is_addr_byte(is_addr_byte),
.my_addr(b_my_addr), .wants_gc_data(b_wants), .supports_sw_reset(b_swrst),
.supports_prog_addr(b_prog), .prog_addr_in(b_prog_addr),
.ack(b_ack), .gc_active(b_gc_active), .do_reset(b_do_reset),
.take_addr(b_take_addr), .latched_addr(b_latched),
.illegal_second_byte(b_illegal), .ignored_code(b_ignored),
.hw_gc_seen(b_hwgc), .hw_master_addr(b_hwaddr),
.hw_addr_collision(b_collide), .opted_out(b_opted), .state(b_state),
.gc_seen_count(b_seen), .gc_joined_count(b_joined));
i2c_general_call_handler #(.CNT_W(8)) dev_c (
.clk(clk), .rst_n(rst_n), .start_seen(start_seen), .stop_seen(stop_seen),
.byte_valid(byte_valid), .byte_in(byte_in), .is_addr_byte(is_addr_byte),
.my_addr(c_my_addr), .wants_gc_data(c_wants), .supports_sw_reset(c_swrst),
.supports_prog_addr(c_prog), .prog_addr_in(c_prog_addr),
.ack(c_ack), .gc_active(c_gc_active), .do_reset(c_do_reset),
.take_addr(c_take_addr), .latched_addr(c_latched),
.illegal_second_byte(c_illegal), .ignored_code(c_ignored),
.hw_gc_seen(c_hwgc), .hw_master_addr(c_hwaddr),
.hw_addr_collision(c_collide), .opted_out(c_opted), .state(c_state),
.gc_seen_count(c_seen), .gc_joined_count(c_joined));
always #5 clk = ~clk;
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset_all;
begin
@(negedge clk);
rst_n = 1'b0; start_seen = 1'b0; stop_seen = 1'b0;
byte_valid = 1'b0; is_addr_byte = 1'b0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
step;
end
endtask
task pulse_start;
begin @(negedge clk); start_seen = 1'b1; @(posedge clk); @(negedge clk); start_seen = 1'b0; end
endtask
task pulse_stop;
begin @(negedge clk); stop_seen = 1'b1; @(posedge clk); @(negedge clk); stop_seen = 1'b0; end
endtask
task send_addr (input [7:0] b);
begin
@(negedge clk); byte_in = b; is_addr_byte = 1'b1; byte_valid = 1'b1;
@(posedge clk); @(negedge clk); byte_valid = 1'b0; is_addr_byte = 1'b0;
end
endtask
task send_data (input [7:0] b);
begin
@(negedge clk); byte_in = b; is_addr_byte = 1'b0; byte_valid = 1'b1;
@(posedge clk); @(negedge clk); byte_valid = 1'b0;
end
endtask
task ck_int (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input got, input exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0b expected %0b", what, got, exp);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_general_call_handler: broadcast with optional participation ===");
// ----------------------------------------------------------------
// T1. The general call address is 0000 0000 and nothing else. In
// particular 0000 0001 is the START byte and 0000 0010 is a CBUS
// address; neither must be mistaken for a general call.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0001); // START byte
step;
$display("T1 only 0000 0000 is the general call");
ck_int("T1 A saw no general call", a_seen, 0);
ck_bit("T1 A did not acknowledge", a_ack, 1'b0);
pulse_stop;
pulse_start;
send_addr(8'b0000_0010); // CBUS address
step;
ck_int("T1 CBUS not counted", a_seen, 0);
pulse_stop;
pulse_start;
send_addr(8'b0000_0000); // the real thing
step;
ck_int("T1 general call counted", a_seen, 1);
// ----------------------------------------------------------------
// T2. Opting out. Device C does not want general call data, so it must
// NACK the address, set no error flag, and ignore the rest.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
step;
$display("T2 declining a general call is legal, not an error");
ck_bit("T2 C did not acknowledge", c_ack, 1'b0);
ck_bit("T2 C recorded opting out", c_opted, 1'b1);
ck_bit("T2 C flagged no error", c_illegal, 1'b0);
ck_bit("T2 C flagged no ignored code", c_ignored, 1'b0);
ck_int("T2 C counted the call", c_seen, 1);
ck_int("T2 C joined nothing", c_joined, 0);
ck_int("T2 C went mute", c_state, S_MUTE);
// and A, which does want the data, acknowledged
ck_bit("T2 A acknowledged", a_gc_active, 1'b1);
ck_int("T2 A joined", a_joined, 1);
// ----------------------------------------------------------------
// T3. THE ASYMMETRY. Two devices decline, one accepts. The wired-AND
// gives the master a LOW in the ninth bit, which is exactly what it
// would see if all three had accepted. The master cannot tell.
// ----------------------------------------------------------------
do_reset_all;
@(negedge clk); a_wants = 1'b1; b_wants = 1'b0; c_wants = 1'b0;
pulse_start;
send_addr(8'b0000_0000);
$display("T3 one acknowledge masks every not-acknowledge");
ck_bit("T3 A pulls", a_ack, 1'b1);
ck_bit("T3 B stays silent", b_ack, 1'b0);
ck_bit("T3 C stays silent", c_ack, 1'b0);
ck_bit("T3 the master sees ACK", master_sees_ack, 1'b1);
step;
// Now the case the master genuinely can distinguish: NOBODY wants it.
do_reset_all;
@(negedge clk); a_wants = 1'b0; b_wants = 1'b0; c_wants = 1'b0;
pulse_start;
send_addr(8'b0000_0000);
ck_bit("T3 with nobody interested the master sees NACK", master_sees_ack, 1'b0);
step;
ck_bit("T3 all three opted out (A)", a_opted, 1'b1);
ck_bit("T3 all three opted out (B)", b_opted, 1'b1);
ck_bit("T3 all three opted out (C)", c_opted, 1'b1);
@(negedge clk); a_wants = 1'b1; b_wants = 1'b1; c_wants = 1'b0;
// ----------------------------------------------------------------
// T4. Second byte 06h: reset AND take the programmable address.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h06);
$display("T4 06h resets and takes the programmable address");
ck_bit("T4 A reset", a_do_reset, 1'b1);
ck_bit("T4 A took the address", a_take_addr, 1'b1);
step;
ck_int("T4 A latched 0x4A", a_latched, 7'h4A);
ck_bit("T4 A raised no error", a_illegal, 1'b0);
// ----------------------------------------------------------------
// T5. Second byte 04h: the SAME, minus the reset. The two codes differ
// by exactly one action, and a design that conflates them passes
// every test that does not check do_reset separately.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h04);
$display("T5 04h takes the address WITHOUT resetting");
ck_bit("T5 A did NOT reset", a_do_reset, 1'b0);
ck_bit("T5 A took the address", a_take_addr, 1'b1);
step;
ck_int("T5 A latched 0x4A", a_latched, 7'h4A);
// ----------------------------------------------------------------
// T6. Second byte 00h is not allowed. Report it and act on nothing.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h00);
$display("T6 00h as a second byte is reported and not obeyed");
ck_bit("T6 A flagged it illegal", a_illegal, 1'b1);
ck_bit("T6 A did not reset", a_do_reset, 1'b0);
ck_bit("T6 A did not take an address", a_take_addr, 1'b0);
step;
ck_int("T6 A went mute", a_state, S_MUTE);
ck_bit("T6 A stopped acknowledging", a_ack, 1'b0);
// ----------------------------------------------------------------
// T7. An unfixed code must be IGNORED -- a distinct outcome from both
// obeying it and from calling it illegal.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h08); // not 00h, 04h or 06h; B = 0
$display("T7 an unfixed command code is ignored, not obeyed");
ck_bit("T7 A flagged it ignored", a_ignored, 1'b1);
ck_bit("T7 A did NOT call it illegal", a_illegal, 1'b0);
ck_bit("T7 A did not reset", a_do_reset, 1'b0);
ck_bit("T7 A did not take an address", a_take_addr, 1'b0);
step;
ck_int("T7 A went mute", a_state, S_MUTE);
// ----------------------------------------------------------------
// T8. THE HARDWARE GENERAL CALL. Bit 0 of the second byte is the only
// thing distinguishing a command from a master announcing itself.
// 0000 0111 has B = 1, so it is NOT the 06h reset command with a
// stray bit -- it is hardware master 0000 011.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'b0000_0111); // B = 1 -> hardware general call
$display("T8 bit 0 set makes it a hardware general call, not a command");
ck_bit("T8 A saw a hardware general call", a_hwgc, 1'b1);
ck_bit("T8 A did NOT reset", a_do_reset, 1'b0);
ck_bit("T8 A did NOT take an address", a_take_addr, 1'b0);
ck_bit("T8 A raised no illegal flag", a_illegal, 1'b0);
ck_bit("T8 A raised no ignored flag", a_ignored, 1'b0);
step;
ck_int("T8 A captured master address 0x03", a_hwaddr, 7'h03);
ck_int("T8 A is in the hardware GC state", a_state, S_HWGC);
// ----------------------------------------------------------------
// T9. A hardware master announcing OUR address is an address collision.
// Device A is at 0x20, so a master announcing 0x20 collides.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data({7'h20, 1'b1}); // hardware master says "I am 0x20"
step;
$display("T9 a hardware master announcing our address is a collision");
ck_bit("T9 A detected the collision", a_collide, 1'b1);
ck_int("T9 A recorded the address", a_hwaddr, 7'h20);
ck_bit("T9 B saw no collision", b_collide, 1'b0);
ck_int("T9 B recorded it too", b_hwaddr, 7'h20);
// ----------------------------------------------------------------
// T10. An optional service that is NOT implemented must not be
// acknowledged. Device B wants general call data but implements
// neither 06h nor the programmable address, so it accepts the
// address and then declines the command.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
ck_bit("T10 B accepted the general call address", b_ack, 1'b1);
send_data(8'h06);
$display("T10 an unimplemented optional service is not acknowledged");
ck_bit("T10 B did not reset", b_do_reset, 1'b0);
ck_bit("T10 B took no address", b_take_addr, 1'b0);
ck_bit("T10 B did not acknowledge the command", b_ack, 1'b0);
// and A, which does implement it, did
ck_bit("T10 A reset", a_do_reset, 1'b1);
step;
ck_int("T10 B went mute", b_state, S_MUTE);
// ----------------------------------------------------------------
// T11. A repeated START begins a fresh general call with no STOP, and
// a STOP ends one. Both must clear gc_active.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h04);
step;
ck_bit("T11 A is in a general call", a_gc_active, 1'b1);
$display("T11 a repeated START begins a fresh general call; a STOP ends one");
pulse_start; // repeated START, no STOP
ck_bit("T11 repeated START cleared it", a_gc_active, 1'b0);
send_addr(8'b0000_0000);
step;
ck_int("T11 a second general call was counted", a_seen, 2);
pulse_stop;
ck_bit("T11 STOP cleared it", a_gc_active, 1'b0);
// ----------------------------------------------------------------
// T12. Having gone mute, further bytes change nothing. Five data bytes
// after an illegal second byte must leave every output alone.
// ----------------------------------------------------------------
do_reset_all;
pulse_start;
send_addr(8'b0000_0000);
send_data(8'h00); // illegal -> mute
for (n = 0; n < 5; n = n + 1) begin
send_data(8'h06); // would be a reset command if interpreted
ck_bit("T12 muted device never resets", a_do_reset, 1'b0);
ck_bit("T12 muted device never acks", a_ack, 1'b0);
end
$display("T12 a muted device interprets nothing until the transaction ends");
ck_int("T12 still mute", a_state, S_MUTE);
if (errors == 0)
$display("=== i2c_general_call_handler: ALL CHECKS PASSED ===");
else
$display("=== i2c_general_call_handler: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_general_call_handler.vhd
-- Slave-side general call receiver (UM10204 3.1.13, 3.1.14, Table 3).
-- Behavioural twin of i2c_general_call_handler.sv / .v.
--
-- The general call is address 0000 0000 with R/W = 0, and participation is
-- OPTIONAL. Declining is done by staying silent:
--
-- "if a device does not need any of the data supplied within the general call
-- structure, it can ignore this address by not issuing an acknowledgment."
--
-- So a NACK here is not an error. It is the documented way to opt out, and a
-- design that reports it as a fault is reporting correct behaviour as a bug.
--
-- Eight obligations are encoded:
-- 1. Recognise 0000 0000 with R/W = 0 as the general call. Nothing else.
-- 2. Acknowledge only if this device actually wants the data.
-- 3. Having opted out, ignore every following byte of that general call.
-- 4. The MEANING is in the second byte, never the first.
-- 5. 06h resets AND takes the programmable address; 04h takes it without
-- resetting. The two differ by exactly one action.
-- 6. 00h is NOT ALLOWED as a second byte. Report it; do not act on it.
-- 7. Every other code with B = 0 must be IGNORED.
-- 8. If the second byte's LSB is 1 it is a HARDWARE general call and the upper
-- seven bits are the sending master's OWN address, not a command. Because a
-- hardware master that can also act as a slave uses the same address for
-- both, an announced address equal to ours is an address collision.
--
-- Bytes arrive already deserialised; bit-level framing was built in Module 7.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_general_call_handler is
generic (
CNT_W : integer := 8
);
port (
clk : in std_logic;
rst_n : in std_logic;
-- byte-level bus interface
start_seen : in std_logic; -- a START or repeated START
stop_seen : in std_logic; -- a STOP
byte_valid : in std_logic; -- byte_in is complete
byte_in : in std_logic_vector(7 downto 0);
is_addr_byte : in std_logic; -- first byte after a START
-- device configuration
my_addr : in std_logic_vector(6 downto 0);
wants_gc_data : in std_logic; -- does this device use general call data?
supports_sw_reset : in std_logic; -- does it implement 06h?
supports_prog_addr : in std_logic; -- does it implement the programmable address?
prog_addr_in : in std_logic_vector(6 downto 0);
-- outputs
ack : out std_logic; -- pull SDA low in the ninth bit
gc_active : out std_logic;
do_reset : out std_logic; -- pulse: perform a software reset
take_addr : out std_logic; -- pulse: latch prog_addr_in
latched_addr : out std_logic_vector(6 downto 0);
illegal_second_byte : out std_logic;
ignored_code : out std_logic;
hw_gc_seen : out std_logic;
hw_master_addr : out std_logic_vector(6 downto 0);
hw_addr_collision : out std_logic;
opted_out : out std_logic;
state : out unsigned(2 downto 0);
gc_seen_count : out unsigned(CNT_W-1 downto 0);
gc_joined_count : out unsigned(CNT_W-1 downto 0)
);
end entity i2c_general_call_handler;
architecture rtl of i2c_general_call_handler is
constant ADDR_GENERAL_CALL : std_logic_vector(7 downto 0) := "00000000";
constant SECOND_RESET_ADDR : std_logic_vector(7 downto 0) := "00000110"; -- 06h
constant SECOND_TAKE_ADDR : std_logic_vector(7 downto 0) := "00000100"; -- 04h
constant SECOND_ILLEGAL : std_logic_vector(7 downto 0) := "00000000"; -- 00h
-- The numeric encoding is part of the interface, so it is spelled out.
constant ST_IDLE : integer := 0;
constant ST_ADDR : integer := 1; -- reserved: the address was just accepted
constant ST_SECOND : integer := 2; -- awaiting the byte that carries the meaning
constant ST_DATA : integer := 3; -- further data bytes of a command sequence
constant ST_HWGC : integer := 4; -- a hardware general call
constant ST_MUTE : integer := 5; -- opted out; ignore everything until STOP
signal st : integer := ST_IDLE;
signal n_seen : integer := 0;
signal n_join : integer := 0;
begin
state <= to_unsigned(st, 3);
process (clk, rst_n)
begin
if rst_n = '0' then
st <= ST_IDLE;
ack <= '0';
gc_active <= '0';
do_reset <= '0';
take_addr <= '0';
latched_addr <= (others => '0');
illegal_second_byte <= '0';
ignored_code <= '0';
hw_gc_seen <= '0';
hw_master_addr <= (others => '0');
hw_addr_collision <= '0';
opted_out <= '0';
n_seen <= 0;
n_join <= 0;
gc_seen_count <= (others => '0');
gc_joined_count <= (others => '0');
elsif rising_edge(clk) then
-- Single-cycle outputs.
ack <= '0';
do_reset <= '0';
take_addr <= '0';
-- A STOP ends any general call. A START restarts the decode, which is
-- what lets a repeated START begin a fresh general call without a STOP.
if stop_seen = '1' then
st <= ST_IDLE;
gc_active <= '0';
elsif start_seen = '1' then
st <= ST_IDLE;
gc_active <= '0';
elsif byte_valid = '1' then
case st is
-- Obligation 1: only 0000 0000 is the general call. In particular
-- 0000 0001 is the START byte and must NOT be treated as one.
when ST_IDLE =>
if is_addr_byte = '1' and byte_in = ADDR_GENERAL_CALL then
n_seen <= n_seen + 1;
gc_seen_count <= to_unsigned(n_seen + 1, CNT_W);
-- Obligation 2: acknowledge only if we want the data.
if wants_gc_data = '1' then
ack <= '1';
gc_active <= '1';
opted_out <= '0';
n_join <= n_join + 1;
gc_joined_count <= to_unsigned(n_join + 1, CNT_W);
st <= ST_SECOND;
else
-- Obligation 3: silence now, and for the rest of this call.
opted_out <= '1';
st <= ST_MUTE;
end if;
end if;
-- Obligation 4: the second byte carries the meaning.
-- Obligation 8: bit 0 selects command vs hardware general call.
when ST_SECOND =>
if byte_in(0) = '1' then
-- A hardware general call. The upper seven bits are the
-- sending master's own address, NOT a command.
hw_gc_seen <= '1';
hw_master_addr <= byte_in(7 downto 1);
-- 3.1.13: a hardware master that can also act as a slave
-- uses the same address for both, so an announced address
-- equal to ours is an address collision.
if byte_in(7 downto 1) = my_addr then
hw_addr_collision <= '1';
end if;
ack <= '1';
st <= ST_HWGC;
elsif byte_in = SECOND_ILLEGAL then
-- Obligation 6: 00h is not allowed here. Report and act on
-- nothing -- obeying a forbidden code is worse than ignoring it.
illegal_second_byte <= '1';
st <= ST_MUTE;
elsif byte_in = SECOND_RESET_ADDR then
-- Obligation 5, the 06h half: reset AND take address.
if supports_sw_reset = '1' then
do_reset <= '1';
ack <= '1';
if supports_prog_addr = '1' then
take_addr <= '1';
latched_addr <= prog_addr_in;
end if;
st <= ST_DATA;
else
-- "This feature is optional and not all devices respond
-- to this command." Not supporting it means not acking.
st <= ST_MUTE;
end if;
elsif byte_in = SECOND_TAKE_ADDR then
-- Obligation 5, the 04h half: identical, minus the reset.
if supports_prog_addr = '1' then
take_addr <= '1';
latched_addr <= prog_addr_in;
ack <= '1';
st <= ST_DATA;
else
st <= ST_MUTE;
end if;
else
-- Obligation 7: "The remaining codes have not been fixed and
-- devices must ignore them."
ignored_code <= '1';
st <= ST_MUTE;
end if;
-- Further bytes of a command sequence we joined.
when ST_DATA =>
ack <= '1';
-- A hardware general call's payload is addressed to whichever
-- intelligent device recognises that master, so this device keeps
-- accepting bytes but performs no command decode.
when ST_HWGC =>
ack <= '1';
-- Obligation 3: opted out, or shut down by an illegal or unfixed
-- code. Interpret nothing until the transaction ends.
when ST_MUTE =>
ack <= '0';
when others =>
st <= ST_IDLE;
end case;
end if;
end if;
end process;
end architecture rtl; -- ---------------------------------------------------------------------------
-- i2c_general_call_handler_tb.vhd
-- Independent oracle for i2c_general_call_handler. Behavioural twin of the
-- SystemVerilog and Verilog benches.
--
-- The bench instantiates THREE handlers with different opt-in policies and wires
-- their acknowledges together, because the property the specification is most
-- emphatic about is not a property of one device:
--
-- "if one or more slaves acknowledge, the not-acknowledge will not be seen by
-- the master."
--
-- One device cannot demonstrate that. Three can, and test 3 does.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_general_call_handler_tb is
end entity i2c_general_call_handler_tb;
architecture sim of i2c_general_call_handler_tb is
constant CNT_W : integer := 8;
constant TCLK : time := 10 ns;
constant ST_IDLE : integer := 0;
constant ST_SECOND : integer := 2;
constant ST_DATA : integer := 3;
constant ST_HWGC : integer := 4;
constant ST_MUTE : integer := 5;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal start_seen : std_logic := '0';
signal stop_seen : std_logic := '0';
signal byte_valid : std_logic := '0';
signal byte_in : std_logic_vector(7 downto 0) := (others => '0');
signal is_addr_byte : std_logic := '0';
-- Three devices, three policies.
signal a_my_addr : std_logic_vector(6 downto 0) := "0100000"; -- 0x20
signal b_my_addr : std_logic_vector(6 downto 0) := "0100001"; -- 0x21
signal c_my_addr : std_logic_vector(6 downto 0) := "0100010"; -- 0x22
signal a_wants : std_logic := '1';
signal b_wants : std_logic := '1';
signal c_wants : std_logic := '0';
signal a_swrst : std_logic := '1';
signal b_swrst : std_logic := '0';
signal c_swrst : std_logic := '1';
signal a_prog : std_logic := '1';
signal b_prog : std_logic := '0';
signal c_prog : std_logic := '1';
signal a_prog_addr : std_logic_vector(6 downto 0) := "1001010"; -- 0x4A
signal b_prog_addr : std_logic_vector(6 downto 0) := "1001011"; -- 0x4B
signal c_prog_addr : std_logic_vector(6 downto 0) := "1001100"; -- 0x4C
signal a_ack, b_ack, c_ack : std_logic;
signal a_gc, b_gc, c_gc : std_logic;
signal a_rst, b_rst, c_rst : std_logic;
signal a_take, b_take, c_take : std_logic;
signal a_latched, b_latched, c_latched : std_logic_vector(6 downto 0);
signal a_ill, b_ill, c_ill : std_logic;
signal a_ign, b_ign, c_ign : std_logic;
signal a_hwgc, b_hwgc, c_hwgc : std_logic;
signal a_hwaddr, b_hwaddr, c_hwaddr : std_logic_vector(6 downto 0);
signal a_col, b_col, c_col : std_logic;
signal a_opt, b_opt, c_opt : std_logic;
signal a_st, b_st, c_st : unsigned(2 downto 0);
signal a_seen, b_seen, c_seen : unsigned(CNT_W-1 downto 0);
signal a_join, b_join, c_join : unsigned(CNT_W-1 downto 0);
-- THE WIRED-AND. The master sees a LOW in the ninth bit if ANY device pulls.
signal master_sees_ack : std_logic;
signal halt : boolean := false;
begin
master_sees_ack <= a_ack or b_ack or c_ack;
dev_a : entity work.i2c_general_call_handler
generic map (CNT_W => CNT_W)
port map (clk => clk, rst_n => rst_n, start_seen => start_seen,
stop_seen => stop_seen, byte_valid => byte_valid, byte_in => byte_in,
is_addr_byte => is_addr_byte, my_addr => a_my_addr,
wants_gc_data => a_wants, supports_sw_reset => a_swrst,
supports_prog_addr => a_prog, prog_addr_in => a_prog_addr,
ack => a_ack, gc_active => a_gc, do_reset => a_rst, take_addr => a_take,
latched_addr => a_latched, illegal_second_byte => a_ill,
ignored_code => a_ign, hw_gc_seen => a_hwgc, hw_master_addr => a_hwaddr,
hw_addr_collision => a_col, opted_out => a_opt, state => a_st,
gc_seen_count => a_seen, gc_joined_count => a_join);
dev_b : entity work.i2c_general_call_handler
generic map (CNT_W => CNT_W)
port map (clk => clk, rst_n => rst_n, start_seen => start_seen,
stop_seen => stop_seen, byte_valid => byte_valid, byte_in => byte_in,
is_addr_byte => is_addr_byte, my_addr => b_my_addr,
wants_gc_data => b_wants, supports_sw_reset => b_swrst,
supports_prog_addr => b_prog, prog_addr_in => b_prog_addr,
ack => b_ack, gc_active => b_gc, do_reset => b_rst, take_addr => b_take,
latched_addr => b_latched, illegal_second_byte => b_ill,
ignored_code => b_ign, hw_gc_seen => b_hwgc, hw_master_addr => b_hwaddr,
hw_addr_collision => b_col, opted_out => b_opt, state => b_st,
gc_seen_count => b_seen, gc_joined_count => b_join);
dev_c : entity work.i2c_general_call_handler
generic map (CNT_W => CNT_W)
port map (clk => clk, rst_n => rst_n, start_seen => start_seen,
stop_seen => stop_seen, byte_valid => byte_valid, byte_in => byte_in,
is_addr_byte => is_addr_byte, my_addr => c_my_addr,
wants_gc_data => c_wants, supports_sw_reset => c_swrst,
supports_prog_addr => c_prog, prog_addr_in => c_prog_addr,
ack => c_ack, gc_active => c_gc, do_reset => c_rst, take_addr => c_take,
latched_addr => c_latched, illegal_second_byte => c_ill,
ignored_code => c_ign, hw_gc_seen => c_hwgc, hw_master_addr => c_hwaddr,
hw_addr_collision => c_col, opted_out => c_opt, state => c_st,
gc_seen_count => c_seen, gc_joined_count => c_join);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for TCLK/2;
clk <= '1'; wait for TCLK/2;
end loop;
wait;
end process;
stim : process
variable err : integer := 0;
procedure ck_int (what : string; got : integer; exp : integer) is
begin
if got /= exp then
report " FAIL " & what & ": got " & integer'image(got)
& " expected " & integer'image(exp) severity note;
err := err + 1;
end if;
end procedure;
procedure ck_bit (what : string; got : std_logic; exp : std_logic) is
begin
if got /= exp then
report " FAIL " & what & ": got " & std_logic'image(got)
& " expected " & std_logic'image(exp) 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_all is
begin
wait until falling_edge(clk);
rst_n <= '0'; start_seen <= '0'; stop_seen <= '0';
byte_valid <= '0'; is_addr_byte <= '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 pulse_start is
begin
wait until falling_edge(clk); start_seen <= '1';
wait until rising_edge(clk); wait until falling_edge(clk); start_seen <= '0';
end procedure;
procedure pulse_stop is
begin
wait until falling_edge(clk); stop_seen <= '1';
wait until rising_edge(clk); wait until falling_edge(clk); stop_seen <= '0';
end procedure;
procedure send_addr (b : std_logic_vector(7 downto 0)) is
begin
wait until falling_edge(clk);
byte_in <= b; is_addr_byte <= '1'; byte_valid <= '1';
wait until rising_edge(clk); wait until falling_edge(clk);
byte_valid <= '0'; is_addr_byte <= '0';
end procedure;
procedure send_data (b : std_logic_vector(7 downto 0)) is
begin
wait until falling_edge(clk);
byte_in <= b; is_addr_byte <= '0'; byte_valid <= '1';
wait until rising_edge(clk); wait until falling_edge(clk);
byte_valid <= '0';
end procedure;
begin
report "=== i2c_general_call_handler: broadcast with optional participation ==="
severity note;
-- T1. Only 0000 0000 is the general call. 0000 0001 is the START byte and
-- 0000 0010 is a CBUS address; neither is a general call.
do_reset_all;
pulse_start;
send_addr("00000001"); -- START byte
step;
report "T1 only 0000 0000 is the general call" severity note;
ck_int("T1 A saw no general call", to_integer(a_seen), 0);
ck_bit("T1 A did not acknowledge", a_ack, '0');
pulse_stop;
pulse_start;
send_addr("00000010"); -- CBUS address
step;
ck_int("T1 CBUS not counted", to_integer(a_seen), 0);
pulse_stop;
pulse_start;
send_addr("00000000"); -- the real thing
step;
ck_int("T1 general call counted", to_integer(a_seen), 1);
-- T2. Opting out is legal and sets no error.
do_reset_all;
pulse_start;
send_addr("00000000");
step;
report "T2 declining a general call is legal, not an error" severity note;
ck_bit("T2 C did not acknowledge", c_ack, '0');
ck_bit("T2 C recorded opting out", c_opt, '1');
ck_bit("T2 C flagged no error", c_ill, '0');
ck_bit("T2 C flagged no ignored code", c_ign, '0');
ck_int("T2 C counted the call", to_integer(c_seen), 1);
ck_int("T2 C joined nothing", to_integer(c_join), 0);
ck_int("T2 C went mute", to_integer(c_st), ST_MUTE);
ck_bit("T2 A acknowledged", a_gc, '1');
ck_int("T2 A joined", to_integer(a_join), 1);
-- T3. THE ASYMMETRY. Two decline, one accepts: the master cannot tell.
do_reset_all;
wait until falling_edge(clk);
a_wants <= '1'; b_wants <= '0'; c_wants <= '0';
pulse_start;
send_addr("00000000");
report "T3 one acknowledge masks every not-acknowledge" severity note;
ck_bit("T3 A pulls", a_ack, '1');
ck_bit("T3 B stays silent", b_ack, '0');
ck_bit("T3 C stays silent", c_ack, '0');
ck_bit("T3 the master sees ACK", master_sees_ack, '1');
step;
-- The case the master genuinely CAN distinguish: nobody wants it.
do_reset_all;
wait until falling_edge(clk);
a_wants <= '0'; b_wants <= '0'; c_wants <= '0';
pulse_start;
send_addr("00000000");
ck_bit("T3 with nobody interested the master sees NACK", master_sees_ack, '0');
step;
ck_bit("T3 all three opted out (A)", a_opt, '1');
ck_bit("T3 all three opted out (B)", b_opt, '1');
ck_bit("T3 all three opted out (C)", c_opt, '1');
wait until falling_edge(clk);
a_wants <= '1'; b_wants <= '1'; c_wants <= '0';
-- T4. 06h resets and takes the programmable address.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data(x"06");
report "T4 06h resets and takes the programmable address" severity note;
ck_bit("T4 A reset", a_rst, '1');
ck_bit("T4 A took the address", a_take, '1');
step;
ck_int("T4 A latched 0x4A", to_integer(unsigned(a_latched)), 16#4A#);
ck_bit("T4 A raised no error", a_ill, '0');
-- T5. 04h is the same, minus the reset.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data(x"04");
report "T5 04h takes the address WITHOUT resetting" severity note;
ck_bit("T5 A did NOT reset", a_rst, '0');
ck_bit("T5 A took the address", a_take, '1');
step;
ck_int("T5 A latched 0x4A", to_integer(unsigned(a_latched)), 16#4A#);
-- T6. 00h is not allowed as a second byte.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data(x"00");
report "T6 00h as a second byte is reported and not obeyed" severity note;
ck_bit("T6 A flagged it illegal", a_ill, '1');
ck_bit("T6 A did not reset", a_rst, '0');
ck_bit("T6 A did not take an address", a_take, '0');
step;
ck_int("T6 A went mute", to_integer(a_st), ST_MUTE);
ck_bit("T6 A stopped acknowledging", a_ack, '0');
-- T7. An unfixed code must be IGNORED -- distinct from illegal.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data(x"08");
report "T7 an unfixed command code is ignored, not obeyed" severity note;
ck_bit("T7 A flagged it ignored", a_ign, '1');
ck_bit("T7 A did NOT call it illegal", a_ill, '0');
ck_bit("T7 A did not reset", a_rst, '0');
ck_bit("T7 A did not take an address", a_take, '0');
step;
ck_int("T7 A went mute", to_integer(a_st), ST_MUTE);
-- T8. Bit 0 set makes it a hardware general call, not a command.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data("00000111"); -- B = 1 -> hardware general call
report "T8 bit 0 set makes it a hardware general call, not a command"
severity note;
ck_bit("T8 A saw a hardware general call", a_hwgc, '1');
ck_bit("T8 A did NOT reset", a_rst, '0');
ck_bit("T8 A did NOT take an address", a_take, '0');
ck_bit("T8 A raised no illegal flag", a_ill, '0');
ck_bit("T8 A raised no ignored flag", a_ign, '0');
step;
ck_int("T8 A captured master address 0x03",
to_integer(unsigned(a_hwaddr)), 3);
ck_int("T8 A is in the hardware GC state", to_integer(a_st), ST_HWGC);
-- T9. A hardware master announcing OUR address is a collision.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data("0100000" & '1'); -- "I am 0x20", which is device A
step;
report "T9 a hardware master announcing our address is a collision"
severity note;
ck_bit("T9 A detected the collision", a_col, '1');
ck_int("T9 A recorded the address", to_integer(unsigned(a_hwaddr)), 16#20#);
ck_bit("T9 B saw no collision", b_col, '0');
ck_int("T9 B recorded it too", to_integer(unsigned(b_hwaddr)), 16#20#);
-- T10. An unimplemented optional service must not be acknowledged.
do_reset_all;
pulse_start;
send_addr("00000000");
ck_bit("T10 B accepted the general call address", b_ack, '1');
send_data(x"06");
report "T10 an unimplemented optional service is not acknowledged"
severity note;
ck_bit("T10 B did not reset", b_rst, '0');
ck_bit("T10 B took no address", b_take, '0');
ck_bit("T10 B did not acknowledge the command", b_ack, '0');
ck_bit("T10 A reset", a_rst, '1');
step;
ck_int("T10 B went mute", to_integer(b_st), ST_MUTE);
-- T11. A repeated START begins a fresh general call; a STOP ends one.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data(x"04");
step;
ck_bit("T11 A is in a general call", a_gc, '1');
report "T11 a repeated START begins a fresh general call; a STOP ends one"
severity note;
pulse_start; -- repeated START, no STOP
ck_bit("T11 repeated START cleared it", a_gc, '0');
send_addr("00000000");
step;
ck_int("T11 a second general call was counted", to_integer(a_seen), 2);
pulse_stop;
ck_bit("T11 STOP cleared it", a_gc, '0');
-- T12. Having gone mute, further bytes change nothing.
do_reset_all;
pulse_start;
send_addr("00000000");
send_data(x"00"); -- illegal -> mute
for k in 0 to 4 loop
send_data(x"06"); -- would be a reset if interpreted
ck_bit("T12 muted device never resets", a_rst, '0');
ck_bit("T12 muted device never acks", a_ack, '0');
end loop;
report "T12 a muted device interprets nothing until the transaction ends"
severity note;
ck_int("T12 still mute", to_integer(a_st), ST_MUTE);
if err = 0 then
report "=== i2c_general_call_handler: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_general_call_handler: " & integer'image(err)
& " CHECK(S) FAILED ===" severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;7a. Seven Decisions Worth Defending
The address match is on all eight bits, not seven. 0000 0000 is the general call; 0000 0001 is the START byte. Matching the address field and ignoring the direction bit conflates them, and mutation W1 is that mistake. One character of difference between a broadcast and a synchronisation aid.
Declining sets no error flag. opted_out is a status output, not a fault. A device that raised an error when it correctly declined a broadcast would fill a system log with reports of itself behaving properly — mutation W3, caught by a bench that asserts the absence of the error flags as well as the presence of the opt-out.
Opting out is sticky for the whole transaction. The S_MUTE state exists so that a device which declined the address does not then interpret the second byte. "Ignore this address" means ignore what follows it, and mutation W11 is the version that keeps acknowledging.
ignored_code and illegal_second_byte are separate outputs. The specification asks for two different responses — "must ignore" for unfixed codes, "not allowed" for 00h — and collapsing them into one flag loses the distinction a debugging engineer needs. Mutations W5 and W6 confuse them in both directions.
Bit 0 is tested before the command decode, not inside it. Structuring it the other way round — decoding the byte and then checking bit 0 — is how a hardware general call from master 0000 011 gets obeyed as the 06h reset, because 0000 0111 and 0000 0110 differ only in that bit. Testing it first makes the confusion unrepresentable.
The hardware master's address is compared against our own. The specification's remark that a dual-role hardware master shares one address for both roles turns an announcement into a collision check for free. hw_addr_collision costs one comparator and finds a class of board bug that otherwise shows up as intermittent transfers.
An unimplemented optional service is declined at the command, not at the address. The device joins the broadcast — it is a general call participant — and then refuses the specific instruction. That two-stage behaviour is what §4's "optional" requires, and mutation W10 short-circuits it.
7b. Verified Execution
$ iverilog -g2012 -o d i2c_general_call_handler.sv i2c_general_call_handler_tb.sv && ./d
=== i2c_general_call_handler: broadcast with optional participation ===
T1 only 0000 0000 is the general call
T2 declining a general call is legal, not an error
T3 one acknowledge masks every not-acknowledge
T4 06h resets and takes the programmable address
T5 04h takes the address WITHOUT resetting
T6 00h as a second byte is reported and not obeyed
T7 an unfixed command code is ignored, not obeyed
T8 bit 0 set makes it a hardware general call, not a command
T9 a hardware master announcing our address is a collision
T10 an unimplemented optional service is not acknowledged
T11 a repeated START begins a fresh general call; a STOP ends one
T12 a muted device interprets nothing until the transaction ends
=== i2c_general_call_handler: ALL CHECKS PASSED ===
i2c_general_call_handler_tb.sv:380: $finish called at 1810000 (1ps)
$ iverilog -g2005 -o v i2c_general_call_handler.v i2c_general_call_handler_tb.v && ./v
=== i2c_general_call_handler: broadcast with optional participation ===
T1 only 0000 0000 is the general call
T2 declining a general call is legal, not an error
T3 one acknowledge masks every not-acknowledge
T4 06h resets and takes the programmable address
T5 04h takes the address WITHOUT resetting
T6 00h as a second byte is reported and not obeyed
T7 an unfixed command code is ignored, not obeyed
T8 bit 0 set makes it a hardware general call, not a command
T9 a hardware master announcing our address is a collision
T10 an unimplemented optional service is not acknowledged
T11 a repeated START begins a fresh general call; a STOP ends one
T12 a muted device interprets nothing until the transaction ends
=== i2c_general_call_handler: ALL CHECKS PASSED ===
i2c_general_call_handler_tb.v:381: $finish called at 1810000 (1ps)
$ nvc --std=2008 -a i2c_general_call_handler.vhd i2c_general_call_handler_tb.vhd
$ nvc --std=2008 -e i2c_general_call_handler_tb && nvc --std=2008 -r i2c_general_call_handler_tb --stop-time=500us
** Note: 0ms+0: === i2c_general_call_handler: broadcast with optional participation ===
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 100ns+1: T1 only 0000 0000 is the general call
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 340ns+1: T2 declining a general call is legal, not an error
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 440ns+1: T3 one acknowledge masks every not-acknowledge
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 680ns+1: T4 06h resets and takes the programmable address
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 800ns+1: T5 04h takes the address WITHOUT resetting
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 920ns+1: T6 00h as a second byte is reported and not obeyed
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1040ns+1: T7 an unfixed command code is ignored, not obeyed
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1160ns+1: T8 bit 0 set makes it a hardware general call, not a command
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1290ns+1: T9 a hardware master announcing our address is a collision
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1400ns+1: T10 an unimplemented optional service is not acknowledged
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1530ns+1: T11 a repeated START begins a fresh general call; a STOP ends one
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1810ns+1: T12 a muted device interprets nothing until the transaction ends
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131
** Note: 1810ns+1: === i2c_general_call_handler: ALL CHECKS PASSED ===
Process :i2c_general_call_handler_tb:stim at i2c_general_call_handler_tb.vhd:131All three at 1810 ns. The suite instantiates the handler three times over and drives one shared byte stream into all of them, so every test exercises three different configurations simultaneously — which is how test 3 can make a statement about what the master sees rather than about what one device does.
7c. What The Testbench Proves
| # | scenario | what it establishes |
|---|---|---|
| 1 | the START byte, a CBUS address, then 0x00 | only 0000 0000 is the general call |
| 2 | device C, which wants no general call data | declines, records it, and sets no error |
| 3 | one device interested, two not | the master reads ACK; with nobody interested, NACK |
| 4 | second byte 06h | reset and address latched |
| 5 | second byte 04h | address latched, no reset |
| 6 | second byte 00h | reported illegal, nothing obeyed |
| 7 | second byte 08h | reported ignored, not illegal, nothing obeyed |
| 8 | second byte 0000 0111 | a hardware general call, not the 06h command |
| 9 | a hardware master announcing 0x20 | an address collision with device A |
| 10 | 06h at a device that does not implement it | address accepted, command declined |
| 11 | a repeated START, then a STOP | each begins or ends a general call |
| 12 | five bytes after an illegal second byte | a muted device interprets nothing |
Test 3 is the chapter's central claim and it needs three devices. The first half shows one pull and two releases reading as an acknowledge; the second half shows that the absence of interest is genuinely observable. Together they establish that the acknowledge is an OR and not a count.
Tests 4 and 5 are the same test with one expectation flipped. Both latch the address; only one resets. The pair is the only thing that separates 06h from 04h, and mutation W4 survives any suite that checks only the latch.
Tests 6 and 7 assert three outcomes where a careless design has two. 00h is illegal-and-ignored; 08h is ignored-and-not-illegal. A handler with a single "bad code" flag cannot express the difference, and the specification asks for it.
Test 8's byte is chosen adversarially. 0000 0111 is 06h with bit 0 set. Any decoder that masks or ignores bit 0 reads it as the reset command, resets the device, and is then wrong about everything downstream.
Test 12 asserts an absence five times. A muted device must not reset, not acknowledge, and not interpret — checked on every one of five bytes that would be a reset command if interpreted, because a state machine that leaks out of its mute state would do so on some particular byte rather than on all of them.
8. Mutation Testing
Eleven defects injected into the SystemVerilog handler.
| # | injected defect | outcome |
|---|---|---|
| W1 | the START byte decoded as a general call | killed — test 1 |
| W2 | acknowledge regardless of interest | killed — 9 checks |
| W3 | opting out reported as an error | killed — test 2 |
| W4 | 04h made to reset as well, conflating it with 06h | killed — test 5 |
| W5 | 00h obeyed as a reset | killed — test 6 |
| W6 | an unfixed code called illegal instead of ignored | killed — test 7 |
| W7 | a hardware general call decoded as a command | killed — 7 checks |
| W8 | the hardware master address captured one bit off | killed — test 8 |
| W9 | the address collision missed | killed — test 9 |
| W10 | an unimplemented optional service acknowledged | killed — test 10 |
| W11 | acknowledging continued after going mute | killed — 5 checks |
Eleven of eleven. Three observations.
W3, W4, W5, W6, W9 and W10 each kill with exactly one or two checks. That is the signature of a suite testing distinctions rather than gross behaviour: each of those mutations changes one narrow judgement, and only the test built for that judgement notices. A suite where every mutation kills broadly is a suite testing one thing several times.
W2 kills with nine. Acknowledging unconditionally breaks the opt-out, the counters, and test 3's whole premise at once. The contrast with W3's single check is the useful part — the same feature, broken two ways, with wildly different observable surfaces.
W7's seven checks are the hardware general call being obeyed as a command. It fires on the reset that should not have happened, the address that should not have been latched, the flags that should not have been raised, and the state that should have been S_HWGC. A confusion this fundamental is loud, which is fortunate, because the byte that triggers it is one bit away from a legitimate command.
9. Verification Connection — Checking A Property Of The Bus, Not Of A Device
The general call's defining property belongs to the aggregate, and a per-agent UVM environment has no natural place to put it. A monitor watching one slave sees that slave's acknowledge; the thing worth asserting is about all of them at once.
// The general call's central property is NOT a property of any one device:
//
// "if one or more slaves acknowledge, the not-acknowledge will not be seen
// by the master."
//
// Checking it needs the intent of EVERY participant plus the value on the wire,
// which is more than any single agent's monitor can see. So the checker sits at
// the environment level and is given an array of per-device intents.
class i2c_gc_broadcast_checker extends uvm_component;
`uvm_component_utils(i2c_gc_broadcast_checker)
// One entry per slave agent: did that device intend to acknowledge?
bit intent_ack[];
bit wire_ack; // what the master actually read
int n_devices;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// THE PROPERTY. The wire must equal the OR of the intents -- never the AND,
// and never a count. Asserting equality with the OR is the precise statement,
// and it fails in BOTH directions: a device that pulled low while the wire
// read high would be a driver fault, and a wire reading low with every device
// released would be a stuck line (Chapter 15.4).
function void check_broadcast_ack();
bit expected_or = 0;
int n_intending = 0;
foreach (intent_ack[i]) begin
expected_or |= intent_ack[i];
if (intent_ack[i]) n_intending++;
end
if (wire_ack !== expected_or)
`uvm_error("GC_ACK", $sformatf(
"wire read %0b but %0d of %0d devices intended to acknowledge (OR = %0b)",
wire_ack, n_intending, n_devices, expected_or))
// And the statement that is NOT checkable, recorded so nobody tries:
// n_intending is invisible to the master. The scoreboard knows it only
// because the bench supplied every device's intent.
`uvm_info("GC_ACK", $sformatf(
"general call: %0d of %0d devices participated; the master can only "
"observe that the count was %s zero",
n_intending, n_devices, (n_intending > 0) ? "above" : ""), UVM_HIGH)
endfunction
endclass
// ------------------------------------------------------------------------
// Coverage. The assertion above is TRUE and USELESS on a bus where every
// device always participates, because then the OR and the AND agree and the
// masking never happens. These bins are what prove the interesting cases ran.
// ------------------------------------------------------------------------
covergroup cg_general_call (int n_devices) with function sample (int n_intending,
bit [7:0] second_byte);
option.per_instance = 1;
cp_participation : coverpoint n_intending {
bins none = {0}; // the only case the master can read
bins one = {1}; // the masking case: one hides the rest
bins some = {[2:$]} with (item < n_devices);
bins all = {n_devices};
}
// The second byte's two worlds, and the adversarial pair inside them.
cp_second : coverpoint second_byte {
bins reset_06 = {8'h06};
bins take_addr_04 = {8'h04};
bins illegal_00 = {8'h00};
bins unfixed = {8'h02, 8'h08, 8'h0A, 8'h0C};
bins hardware_gc = {[8'h01:8'hFF]} with (item[0] == 1'b1);
}
// The cross that matters: a MIXED population on a COMMAND byte is where a
// refusal gets masked. Everything else is either unanimous or harmless.
x_mixed_on_command : cross cp_participation, cp_second {
ignore_bins unanimous = binsof(cp_participation.none) ||
binsof(cp_participation.all);
}
endgroup10. FPGA and ASIC Implications
The general call decoder is a second address comparator, not a special case of the first. 0000 0000 has to be matched alongside the device's own address, and both may hit in the same transaction on a bus where a device's own address is being written by a general call. Building it as an escape hatch inside the main address decoder of Chapter 6.5 tangles two independent decisions.
The 06h reset must not leave the pins pulling low. §3a's precaution is an obligation on the reset implementation: whatever the general call resets must bring the SDA and SCL output stages up released, not driven. A synchronous reset that clears a driver enable to its default is fine; one that clears a release signal to zero pulls the bus down on forty devices at once, and the only way out is Chapter 15.4.
The programmable address needs a real source. 04h and 06h both "take in the programmable part of their address by hardware", which means strapping pins, fuses or an external resistor network — sampled at the moment the command arrives, not at power-on. A design that latches the straps only at reset will ignore 04h entirely while appearing to implement it.
Participation should be a configuration input, not a constant. §8's handler takes wants_gc_data as a port for a reason: the same RTL is used in devices that care about broadcasts and devices that do not, and hard-coding the acknowledge makes two variants of a block that should have one.
Count what you decline. gc_seen_count and gc_joined_count cost two counters and answer a question that is otherwise unanswerable from outside: is this device seeing broadcasts and refusing them, or not seeing them at all? On a bus where a general call is not taking effect, those two numbers separate a decode problem from a policy problem immediately.
11. Debugging — The Reset That Reset Two Devices Out Of Five
A board has five I²C devices that all document general-call software reset. Firmware issues a general call with 06h at startup and reads an acknowledge, so it proceeds. Two of the five devices come up with correct defaults; three keep stale configuration from before the reset and misbehave until power is cycled.
Nothing was faulty and nothing was missed on the wire. Three devices correctly declined the general call because the optional feature was not enabled in them, and UM10204 §3.1.13 says declining is done by not acknowledging. The wired-AND then made their refusals invisible: devices 1 and 4 pulled SDA low, so the master read an acknowledge identical to what five participants would have produced. The firmware treated that acknowledge as confirmation that all five had reset, which the acknowledge cannot mean — it only ever means at least one.
Stop treating the general-call acknowledge as aggregate confirmation. Either enable the reset feature in every device before broadcasting, and then verify per device by reading a known register; or abandon the broadcast and reset each device individually, where the acknowledge does identify one device. The ordering bug is separate and also real: the enable bit must be written before the reset that depends on it, not after.Three things generalise from that.
Every device behaved correctly, including the three that did nothing. There is no defect to find in any component. The defect is in an inference the firmware drew from one bit.
The capture was not just unhelpful, it was actively misleading. A clean general call with two acknowledges looks exactly like success. Diagnosing it required probing individual device pins during one bit period — which is a scope job, not a protocol-analyser job, because the protocol analyser sees the same single wire the master does.
The fix is architectural, not a patch. No amount of care in the broadcast recovers the information. Confirmation has to come from somewhere the wired-AND has not flattened, which means per-device reads.
12. Common Misconceptions
"A general call reaches every device, so every device acts on it." Participation is optional and is declined by silence. §1.
"An acknowledge on a general call means the devices got it." It means at least one did. The count is not recoverable. §2.
"A NACK on a general call is an error." It means nothing on the bus was interested, which is a legal outcome and is the only aggregate fact the acknowledge can convey. §2.
"Software reset is its own command." It is general call 0000 0000 followed by 0000 0110. §3.1.14 restates §3.1.13's first sub-code. §4.
"06h and 04h are the same thing." Both latch the programmable address; only 06h also resets. §3a.
"An unrecognised second byte should be reported as an error." Unfixed codes "must be ignored"; only 00h is not allowed. Three outcomes, not two. §3a.
"The second byte is always a command." If bit 0 is set it is a hardware master's own address. 0000 0111 is not 06h with a stray bit — it is master 0000 011 announcing itself. §3b.
"The general call address is 0000 000 with any direction bit." With the direction bit set it is the START byte. §1.
"A device that supports general call must support software reset." The reset is separately optional, and not implementing it means not acknowledging that command while still acknowledging the address. §4.
13. Reason It Through
A master broadcasts 06h on a bus of eight devices. Three acknowledge. What does the master read, and what can it conclude?
It reads a single acknowledge — SDA low in the ninth bit — because three devices pulling low is electrically indistinguishable from one or from eight. It can conclude that at least one device participated. It cannot conclude three, cannot identify which, and cannot detect the five refusals. §2.
The same bus, and no device implements general call at all. Now what?
The master reads a NACK, and that is genuinely informative: nothing on the bus is interested. This is the one aggregate fact the acknowledge conveys reliably, and it is the negative one.
A second byte of 0000 0111 arrives. What should a device do?
Bit 0 is set, so it is a hardware general call, and the upper seven bits — 0000 011 — are the sending master's own address. The device records who is talking and performs no command. It must specifically not reset, even though 0000 0111 differs from the 06h reset command by one bit. §3b.
Why can a device not simply acknowledge every general call and ignore data it does not want?
It can, functionally — the data would be discarded either way. What it loses is the one bit of information the acknowledge carries: with every device acknowledging unconditionally, a master can no longer distinguish "somebody is interested" from "nobody is interested", because the answer is always yes. The opt-out is what makes the NACK meaningful. Mutation W2 is this design, and it breaks test 3's second half.
A general call with second byte 04h is sent. A device latches its strapping pins and does not reset. Is that correct?
Yes, exactly. 04h "behaves as above, but the device does not reset". The strapping is sampled when the command arrives, which is why a design that only samples straps at power-on will appear to implement 04h and actually ignore it. §10.
Why is the precaution about pulling down SDA or SCL after applying the supply in the general call section rather than in a section about reset?
Because the general call is the one operation that resets many devices at once. A single device emerging from reset holding a line low is a bug; forty doing so simultaneously is the same bug multiplied, with no working device left to drive the recovery. The hazard scales with the broadcast, so the warning lives with the broadcast.
14. Understanding Check
15. Summary
The general call is address 0000 0000 with R/W clear, and 0000 0001 — the same address field with the direction bit set — is the START byte. One bit apart, completely different features.
Participation is optional and declining is silence. A not-acknowledge on a general call is the documented opt-out, not a fault, and a design that reports it as one reports correct behaviour as a bug.
So the acknowledge is an OR, not a count. One device pulling low is electrically identical to all of them; the master learns that somebody is interested and never how many. The NACK is the informative case.
That is the wired-AND again, flattening attribution exactly as it does in arbitration — the same limit Chapter 13.5 closed on, reached from the acknowledge bit instead.
The meaning is always in the second byte, and bit 0 of it splits the feature in half: clear for a command code, set for a hardware master announcing its own address.
06h and 04h differ by one action — both latch the programmable address, only 06h resets. 00h is forbidden as a second byte. Everything else must be ignored. Three distinct outcomes, and a design needs three outputs to express them.
Software reset is general call 06h, restated under its own heading, and its one addition is that the feature is optional — which means an unimplemented reset is declined at the command while the address is still accepted.
A hardware general call can expose an address collision for the cost of one comparator, because a dual-role master shares one address between its roles.
And a broadcast is safe only for operations you will verify separately. No aggregate confirmation exists, and none can be built from the acknowledge.
16. What Comes Next
The general call established that a feature can be optional, and that optional has a precise protocol meaning: not implemented means not acknowledged.
Chapter 15.2 takes that idea further with the two reserved-address services a design might actually want to rely on. Software reset is already in hand — it is §4's 06h. The other is the Device ID, and it is the most intricate exchange in the base specification: a three-byte read-only identity whose 24 bits are packed 12, 9 and 3 across three bytes, reached by a seven-step procedure that needs two addressing phases and a slave state machine that survives a repeated START but is destroyed by a STOP.
That fragility is the point. A Device ID read is the first transaction in this curriculum whose correctness depends on the atomicity Chapter 10.3 described — and the specification says twice, in two separate Remarks, exactly which events break it.
Continue learning
Related tutorials
- Related topic
Reserved Addresses and the I²C Address Map
Sixteen of the 128 seven-bit addresses are spoken for, two carry outright prohibitions on responding, and one address means two opposite things depending on a single direction bit. Lay out the map, decode it in hardware, and verify it exhaustively against an independent model.
- Related topic
From Parallel Buses to Two Wires
Derive the bus rather than meet it. Trading wires for time gives serialisation; trading exclusivity for coordination gives a shared medium; losing the wire as an implicit address forces a logical one. Each step is a deliberate exchange, and what falls out is a two-wire addressed bus — which is what Philips specified as I²C.
- Related topic
Wired-AND — Many Drivers, One Line, No Contention
Put several open-drain devices on one node and a logic function appears in the wiring: any participant asserting LOW wins, and HIGH requires unanimous release. Derive dominant LOW, see why two devices pulling together is agreement rather than conflict, and watch three of the protocol's mechanisms become predictable.
- Related topic
The Shared Bus — Many Targets, Sometimes Many Masters
One pair of conductors serves an entire board. Physically the bus is a broadcast medium — every device sees everything — while participation is logically selective. That gap is the central abstraction of I²C, and it also opens the multi-controller case.
