Skip to content
VLSI Mentor

I²C · Module 6

Address Decoding Inside a Slave

The first piece of slave hardware in the curriculum, and it is assembled rather than written: byte capture, reserved-map classification, one equality test, and a decision whose ordering matters — because a prohibition has to beat an address match.

Four chapters have described addressing from outside a device: the byte on the wire, the two-byte extension, the reserved map, and how a board assigns addresses. Every one of them assumed a device that watches the bus and recognises its own address.

This chapter builds it. It is the first slave-side hardware in the curriculum, and the most interesting thing about it is how little of it is new — three of its four parts already exist, and the part that does not is a decision with an ordering that the specification never states.

1. Four Stages, Three of Them Already Built

The decoder's job decomposes cleanly, and the decomposition is the design:

stagequestionwhere it came from
framinghas a transfer begun, and is this byte an address?Module 5 — S, Sr and P detection
capturewhat are the eight bits?Chapter 6.1 — i2c_address_byte_capture
classifywhat kind of address is it?Chapter 6.3 — i2c_address_classifier
decideshould this device respond, and how?this chapter

So three stages are instantiations and one is new logic. That is worth dwelling on, because the alternative — one module that shifts, decodes the reserved map, and decides, all in one always block — is how this block is usually written and is considerably worse: the reserved-map decode becomes unreviewable against the table, the capture's edge detection gets tangled with the decision, and none of the three can be verified in isolation.

A pipeline of four stages. The observed SDA and SCL levels and the framing events from Module 5 both feed a byte capture stage. The captured byte feeds two parallel consumers: a reserved-address classifier and an own-address comparator. Both of those feed a final decide-and-respond stage, in which the classifier's prohibition takes priority over the comparator's match.Framing eventsS, Sr and P from Module 5SDA and SCLobserved, alreadysynchronisedByte captureeight bits, MSB firstClassifierreserved map andprohibitionsOwn-address compareone equality testDecide and respondprohibition beats a match12
Figure 1 — the decoder as a pipeline of four stages. The observed bus levels and the framing events both feed the capture stage, which produces a complete byte. That byte fans out to two independent consumers: the reserved-map classifier and a single equality test against this device's own address. The decision stage combines their verdicts, and the ordering between them is the subject of section 2.

2. The Ordering Rule

The decision stage evaluates three conditions, and the order is not arbitrary:

Azvya Education Pvt. Ltd.VLSI Mentor
ARCHITECTURAL PSEUDOCODE — the decision, and why this order
   if      (must_not_ack)   -> do NOT respond           <-- FIRST, always
   else if (own_match)      -> respond, latch direction
   else if (gen_call_match) -> respond as a broadcast receiver
   else                     -> stay silent

   Why the prohibition is first:

     [Chapter 6.3] established that two Table 3 footnotes forbid responding --
     the START byte and the CBUS address -- and that neither makes an exception
     for a device that happens to hold that address. A device strapped to the
     CBUS address by a board error, or assigned it deliberately under the
     specification's escape hatch, still must not answer it.

     So `own_match` being true is NOT sufficient to respond. Checking the match
     first and the prohibition afterwards produces a device that is compliant in
     every ordinary case and non-compliant in exactly the case the footnote was
     written for.

   Why the general call is last:

     It is the broadest condition -- it matches an address no device owns -- so it
     must not shadow a specific match. It cannot in practice, because the general
     call address and a device's own address are different addresses, but keeping
     the specific case ahead of the broad one is the habit that survives a future
     edit adding a third broad case.

That first rule is the single most important line in the chapter, and it is invisible in the specification: Table 3's footnotes and the address-matching requirement live in different sections and nothing says which wins. The priority has to be derived from what the footnotes are for.

3. What "Respond" Means Here — and What It Does Not

The decoder's response output is ack_request: a one-cycle pulse meaning this device wants to acknowledge the byte just seen. It is deliberately not an SDA driver.

The distinction matters because the acknowledge is a bus cycle, not a signal level. It occupies the ninth clock pulse of the byte, the receiver drives SDA low during it, and it has its own timing obligations and its own failure modes. All of that is Module 7, and putting it in the decoder would mean the decoder could not be verified without also modelling the ninth clock.

So the boundary is:

this chapter ownsModule 7 owns
deciding whether to acknowledgedriving the acknowledge in the ninth clock slot
addressed and direction as statewhat a transmitter does with direction
releasing on P or a foreign Sracknowledging data bytes
the general call as a build optionwhat the general call's second byte means

direction is worth one note. It is latched from the eighth bit and it says what the master asked for, not what this device will do about it. A device that is addressed for a read has to become a transmitter, and that is the byte-transfer layer's problem — but it cannot start until this stage has told it which way the transfer goes, which is why direction is an output of addressing rather than of data handling.

4. Releasing the Device

Two release conditions, both inherited directly from Module 5:

A STOP releases unconditionally. Chapter 5.3 established that a STOP ends the transfer for every device on the segment.

A framing START — including a repeated START — also releases. This is the one that looks optional and is not. Chapter 5.4 established that a repeated START does not release the bus, so nothing else is going to tell this device to let go. If a device stayed addressed across an Sr, it would remain addressed while another device's address was being transmitted, and anything downstream acting on addressed would be acting on a stale claim.

The subtlety is when. The device must release at the framing event itself, not merely once the new address resolves eight clock pulses later. Between those two moments there is a window in which the device is not the target and has not yet been told so, and §8 injects a fault that leaves it addressed for exactly that window — a mutation that survived the first version of the testbench because the test only looked afterwards.

Note the contrast with Chapter 6.2's matcher, which had to keep something across an Sr. There, was_selected survives because the 10-bit read re-address is interpreted against it. Here, addressed must not survive, because it is a claim about right now. Two pieces of state, two opposite rules, and the difference is what each one means.

5. The Decoder in Three Languages

Three files per language: the decoder plus the two blocks it instantiates. All three must be compiled together.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_address_decoder.sv — SYNTHESIZABLE RTL. Assembled from the capture and classifier blocks; the prohibition is tested first.
   // The slave-side address decoder: shift, classify, compare, decide, respond.
   // It is ASSEMBLED from the two blocks built earlier in this module rather than
   // reimplementing them -- i2c_address_byte_capture and i2c_address_classifier.
   module i2c_address_decoder #(
       parameter logic [6:0] OWN_ADDR        = 7'h48,
       // General call support is OPTIONAL in the specification (Table 2), so it is
       // a build-time decision and not something every device must implement.
       parameter bit         ANSWER_GEN_CALL = 1'b1
   )(
       input  logic clk,
       input  logic rst_n,
       input  logic scl_in,             // observed bus level
       input  logic sda_in,             // observed bus level
       input  logic frame_start,        // pulse: S or Sr observed (Module 5)
       input  logic frame_stop,         // pulse: P observed (Module 5)
       output logic       addressed,       // this transfer is for this device
       output logic       direction,       // 1 = master READS from us
       output logic       gen_call_active, // selected by the general call, not by address
       output logic       ack_request,     // pulse: acknowledge the address byte
       output logic [6:0] last_addr        // the address seen, for software and debug
   );
       localparam logic [3:0] CLS_GENERAL_CALL = 4'd1;

       // ---- stage 1: get the byte off the wire ----
       logic [6:0] cap_addr;
       logic       cap_rw, cap_valid, capturing;

       i2c_address_byte_capture u_capture (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
           .frame_start(frame_start), .frame_stop(frame_stop),
           .addr(cap_addr), .rw(cap_rw), .addr_valid(cap_valid), .capturing(capturing)
       );

       // ---- stage 2: decide what kind of address it is ----
       logic       cls_reserved, cls_must_not_ack;
       logic [3:0] cls_class;

       i2c_address_classifier u_classify (
           .addr(cap_addr), .rw(cap_rw),
           .is_reserved(cls_reserved), .addr_class(cls_class), .must_not_ack(cls_must_not_ack)
       );

       // ---- stage 3: compare and decide ----
       logic own_match, gen_call_match;
       assign own_match      = (cap_addr == OWN_ADDR);
       assign gen_call_match = (cls_class == CLS_GENERAL_CALL) && ANSWER_GEN_CALL;

       always_ff @(posedge clk) begin
           if (!rst_n) begin
               addressed       <= 1'b0;
               direction       <= 1'b0;
               gen_call_active <= 1'b0;
               ack_request     <= 1'b0;
               last_addr       <= 7'h00;
           end else begin
               ack_request <= 1'b0;

               if (frame_stop) begin
                   // A STOP releases this device unconditionally.
                   addressed       <= 1'b0;
                   gen_call_active <= 1'b0;
               end else if (frame_start) begin
                   // S or Sr: stop being addressed until the new address says otherwise.
                   // A device that stayed selected across an Sr would answer a transfer
                   // aimed at somebody else.
                   addressed       <= 1'b0;
                   gen_call_active <= 1'b0;
               end else if (cap_valid) begin
                   last_addr <= cap_addr;

                   // The prohibition WINS over a match. Two Table 3 footnotes forbid
                   // responding at all, and they do not make an exception for a device
                   // that happens to have been given that address.
                   if (cls_must_not_ack) begin
                       addressed       <= 1'b0;
                       gen_call_active <= 1'b0;
                   end else if (own_match) begin
                       addressed       <= 1'b1;
                       direction       <= cap_rw;
                       gen_call_active <= 1'b0;
                       ack_request     <= 1'b1;
                   end else if (gen_call_match) begin
                       // The general call is a broadcast WRITE: there is no readable
                       // direction, because every device would be transmitting at once.
                       addressed       <= 1'b1;
                       direction       <= 1'b0;
                       gen_call_active <= 1'b1;
                       ack_request     <= 1'b1;
                   end else begin
                       addressed       <= 1'b0;
                       gen_call_active <= 1'b0;
                   end
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_address_decoder_tb.sv — SELF-CHECKING TESTBENCH, SIMULATION ONLY. Three devices; general call optional; prohibition beats a match.
   module i2c_address_decoder_tb;
       logic clk = 1'b0, rst_n, scl_in, sda_in, frame_start, frame_stop;
       int errors = 0;

       // THREE devices, to separate three different decisions:
       //   G  -- an ordinary part at 0x48 that answers the general call
       //   N  -- the same part with general call support NOT built
       //   C  -- a part mis-assigned the CBUS address, to prove the prohibition wins
       logic addrG, dirG, gcG, ackG; logic [6:0] laG;
       logic addrN, dirN, gcN, ackN; logic [6:0] laN;
       logic addrC, dirC, gcC, ackC; logic [6:0] laC;

       i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b1)) dutG
           (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
            .frame_start(frame_start), .frame_stop(frame_stop),
            .addressed(addrG), .direction(dirG), .gen_call_active(gcG),
            .ack_request(ackG), .last_addr(laG));

       i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b0)) dutN
           (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
            .frame_start(frame_start), .frame_stop(frame_stop),
            .addressed(addrN), .direction(dirN), .gen_call_active(gcN),
            .ack_request(ackN), .last_addr(laN));

       i2c_address_decoder #(.OWN_ADDR(7'b0000001), .ANSWER_GEN_CALL(1'b0)) dutC
           (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
            .frame_start(frame_start), .frame_stop(frame_stop),
            .addressed(addrC), .direction(dirC), .gen_call_active(gcC),
            .ack_request(ackC), .last_addr(laC));

       always #5 clk = ~clk;
       initial begin #80000; $display("FAIL: watchdog expired"); $finish; end

       int nG = 0, nN = 0, nC = 0;
       always @(posedge clk) if (rst_n) begin
           if (ackG) nG++;
           if (ackN) nN++;
           if (ackC) nC++;
       end

       task automatic send_bit(input logic b);
           scl_in = 1'b0;             repeat (2) @(negedge clk);
           sda_in = b;                repeat (2) @(negedge clk);
           scl_in = 1'b1;             repeat (2) @(negedge clk);
           scl_in = 1'b0;             repeat (1) @(negedge clk);
       endtask
       task automatic pulse_start(); frame_start = 1'b1; @(negedge clk); frame_start = 1'b0; endtask
       task automatic pulse_stop();  frame_stop  = 1'b1; @(negedge clk); frame_stop  = 1'b0; endtask

       // A full addressing phase: framing START then the eight-bit address byte.
       task automatic address_phase(input logic [6:0] a, input logic dir);
           pulse_start();
           for (int i = 6; i >= 0; i--) send_bit(a[i]);
           send_bit(dir);
           repeat (2) @(negedge clk);
       endtask

       task automatic chk(input logic got, input logic want, input int step);
           if (got !== want) begin
               $display("FAIL: step %0d -- signal was %0b, expected %0b", step, got, want);
               errors++;
           end
       endtask

       initial begin
           rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; frame_start = 1'b0; frame_stop = 1'b0;
           repeat (3) @(negedge clk);
           chk(addrG, 1'b0, 0); chk(gcG, 1'b0, 0);
           rst_n = 1'b1; @(negedge clk);

           // 1 -- our own address, WRITE.
           nG = 0;
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 1); chk(dirG, 1'b0, 1); chk(gcG, 1'b0, 1);
           if (nG != 1) begin $display("FAIL: expected exactly one ACK for our address, saw %0d", nG); errors++; end
           if (laG !== 7'h48) begin $display("FAIL: last_addr = 0x%02h", laG); errors++; end

           // 2 -- our own address, READ. Same match, opposite direction.
           pulse_stop();
           nG = 0;
           address_phase(7'h48, 1'b1);
           chk(addrG, 1'b1, 2); chk(dirG, 1'b1, 2);
           if (nG != 1) begin $display("FAIL: read address not acknowledged once"); errors++; end

           // 3 -- somebody else's address. No ACK, and not addressed.
           pulse_stop();
           nG = 0;
           address_phase(7'h49, 1'b0);
           chk(addrG, 1'b0, 3);
           if (nG != 0) begin $display("FAIL: acknowledged another device's address"); errors++; end

           // 4 -- THE GENERAL CALL. The device that supports it answers; the device
           //      built without support stays silent. Both are compliant, because the
           //      specification makes general call OPTIONAL.
           pulse_stop();
           nG = 0; nN = 0;
           address_phase(7'b0000000, 1'b0);
           chk(addrG, 1'b1, 4); chk(gcG, 1'b1, 4); chk(dirG, 1'b0, 4);
           chk(addrN, 1'b0, 4); chk(gcN, 1'b0, 4);
           if (nG != 1) begin $display("FAIL: general call not acknowledged by the supporting device"); errors++; end
           if (nN != 0) begin $display("FAIL: a device without general call support acknowledged it"); errors++; end

           // 5 -- THE START BYTE. Same seven address bits as the general call, R/W = 1.
           //      "No device is allowed to acknowledge at the reception of the START
           //      byte" -- so the direction bit alone flips a broadcast into a
           //      must-not-answer.
           pulse_stop();
           nG = 0; nN = 0;
           address_phase(7'b0000000, 1'b1);
           chk(addrG, 1'b0, 5); chk(gcG, 1'b0, 5);
           if (nG != 0 || nN != 0) begin
               $display("FAIL: the START byte was acknowledged (G=%0d N=%0d)", nG, nN); errors++; end

           // 6 -- THE PROHIBITION WINS OVER A MATCH. Device C was given the CBUS
           //      address. It matches, and it must still not respond.
           pulse_stop();
           nC = 0;
           address_phase(7'b0000001, 1'b0);
           chk(addrC, 1'b0, 6);
           if (nC != 0) begin
               $display("FAIL: a device assigned the CBUS address responded to it"); errors++; end

           // 7 -- a STOP releases the device.
           pulse_stop();
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 7);
           pulse_stop();
           repeat (2) @(negedge clk);
           chk(addrG, 1'b0, 7);

           // 8 -- Sr TO A DIFFERENT ADDRESS must release us. A device that stayed
           //      selected across a repeated START would answer somebody else's
           //      transfer -- and Module 5 established that Sr does not release the bus,
           //      so nothing else would tell it to let go.
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 8);
           address_phase(7'h49, 1'b0);              // Sr, then another device's address
           chk(addrG, 1'b0, 8);

           // 8b -- THE DEVICE MUST LET GO AT THE Sr ITSELF, not merely once the new
           //       address resolves eight clocks later. Between the Sr and the end of
           //       the new address byte, a device that is still reporting `addressed`
           //       is claiming a transfer that may belong to somebody else -- and
           //       anything downstream acting on that flag acts on a stale claim.
           pulse_stop();
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 82);
           pulse_start();                       // the Sr alone -- no address byte yet
           repeat (2) @(negedge clk);
           chk(addrG, 1'b0, 82);                // released already, mid-address-phase

           // 9 -- Sr back to US re-selects, with the new direction.
           address_phase(7'h48, 1'b1);
           chk(addrG, 1'b1, 9); chk(dirG, 1'b1, 9);

           if (errors == 0)
               $display("PASS: own address matched both directions, general call optional, prohibitions win, Sr releases");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_address_decoder.v — SYNTHESIZABLE RTL. The same decoder in Verilog-2005.
   // The slave-side address decoder: shift, classify, compare, decide, respond.
   // ASSEMBLED from the two blocks built earlier in this module rather than
   // reimplementing them -- i2c_address_byte_capture and i2c_address_classifier.
   module i2c_address_decoder #(
       parameter [6:0] OWN_ADDR        = 7'h48,
       // General call support is OPTIONAL in the specification (Table 2).
       parameter       ANSWER_GEN_CALL = 1'b1
   )(
       input  wire clk,
       input  wire rst_n,
       input  wire scl_in,             // observed bus level
       input  wire sda_in,             // observed bus level
       input  wire frame_start,        // pulse: S or Sr observed (Module 5)
       input  wire frame_stop,         // pulse: P observed (Module 5)
       output reg        addressed,       // this transfer is for this device
       output reg        direction,       // 1 = master READS from us
       output reg        gen_call_active, // selected by the general call
       output reg        ack_request,     // pulse: acknowledge the address byte
       output reg  [6:0] last_addr        // the address seen, for software and debug
   );
       localparam [3:0] CLS_GENERAL_CALL = 4'd1;

       // ---- stage 1: get the byte off the wire ----
       wire [6:0] cap_addr;
       wire       cap_rw, cap_valid, capturing;

       i2c_address_byte_capture u_capture (
           .clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
           .frame_start(frame_start), .frame_stop(frame_stop),
           .addr(cap_addr), .rw(cap_rw), .addr_valid(cap_valid), .capturing(capturing)
       );

       // ---- stage 2: decide what kind of address it is ----
       wire       cls_reserved, cls_must_not_ack;
       wire [3:0] cls_class;

       i2c_address_classifier u_classify (
           .addr(cap_addr), .rw(cap_rw),
           .is_reserved(cls_reserved), .addr_class(cls_class), .must_not_ack(cls_must_not_ack)
       );

       // ---- stage 3: compare and decide ----
       wire own_match      = (cap_addr == OWN_ADDR);
       wire gen_call_match = (cls_class == CLS_GENERAL_CALL) && ANSWER_GEN_CALL;

       always @(posedge clk) begin
           if (!rst_n) begin
               addressed       <= 1'b0;
               direction       <= 1'b0;
               gen_call_active <= 1'b0;
               ack_request     <= 1'b0;
               last_addr       <= 7'h00;
           end else begin
               ack_request <= 1'b0;

               if (frame_stop) begin
                   addressed       <= 1'b0;
                   gen_call_active <= 1'b0;
               end else if (frame_start) begin
                   // S or Sr: stop being addressed until the new address says otherwise.
                   addressed       <= 1'b0;
                   gen_call_active <= 1'b0;
               end else if (cap_valid) begin
                   last_addr <= cap_addr;

                   // The prohibition WINS over a match: the footnotes forbidding a
                   // response make no exception for a device given that address.
                   if (cls_must_not_ack) begin
                       addressed       <= 1'b0;
                       gen_call_active <= 1'b0;
                   end else if (own_match) begin
                       addressed       <= 1'b1;
                       direction       <= cap_rw;
                       gen_call_active <= 1'b0;
                       ack_request     <= 1'b1;
                   end else if (gen_call_match) begin
                       // The general call is a broadcast WRITE: there is no readable
                       // direction, because every device would be transmitting at once.
                       addressed       <= 1'b1;
                       direction       <= 1'b0;
                       gen_call_active <= 1'b1;
                       ack_request     <= 1'b1;
                   end else begin
                       addressed       <= 1'b0;
                       gen_call_active <= 1'b0;
                   end
               end
           end
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_address_decoder_tb.v — SELF-CHECKING TESTBENCH, SIMULATION ONLY. The same cases in Verilog idiom.
   module i2c_address_decoder_tb;
       reg clk, rst_n, scl_in, sda_in, frame_start, frame_stop;
       integer errors;

       // THREE devices, to separate three different decisions:
       //   G  -- an ordinary part at 0x48 that answers the general call
       //   N  -- the same part with general call support NOT built
       //   C  -- a part mis-assigned the CBUS address, to prove the prohibition wins
       wire addrG, dirG, gcG, ackG; wire [6:0] laG;
       wire addrN, dirN, gcN, ackN; wire [6:0] laN;
       wire addrC, dirC, gcC, ackC; wire [6:0] laC;

       i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b1)) dutG
           (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
            .frame_start(frame_start), .frame_stop(frame_stop),
            .addressed(addrG), .direction(dirG), .gen_call_active(gcG),
            .ack_request(ackG), .last_addr(laG));

       i2c_address_decoder #(.OWN_ADDR(7'h48), .ANSWER_GEN_CALL(1'b0)) dutN
           (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
            .frame_start(frame_start), .frame_stop(frame_stop),
            .addressed(addrN), .direction(dirN), .gen_call_active(gcN),
            .ack_request(ackN), .last_addr(laN));

       i2c_address_decoder #(.OWN_ADDR(7'b0000001), .ANSWER_GEN_CALL(1'b0)) dutC
           (.clk(clk), .rst_n(rst_n), .scl_in(scl_in), .sda_in(sda_in),
            .frame_start(frame_start), .frame_stop(frame_stop),
            .addressed(addrC), .direction(dirC), .gen_call_active(gcC),
            .ack_request(ackC), .last_addr(laC));

       initial clk = 1'b0;
       always #5 clk = ~clk;
       initial begin #80000; $display("FAIL: watchdog expired"); $finish; end

       integer nG, nN, nC, i;
       always @(posedge clk) if (rst_n) begin
           if (ackG) nG = nG + 1;
           if (ackN) nN = nN + 1;
           if (ackC) nC = nC + 1;
       end

       task send_bit; input b; begin
           scl_in = 1'b0;             repeat (2) @(negedge clk);
           sda_in = b;                repeat (2) @(negedge clk);
           scl_in = 1'b1;             repeat (2) @(negedge clk);
           scl_in = 1'b0;             repeat (1) @(negedge clk);
       end endtask
       task pulse_start; begin frame_start = 1'b1; @(negedge clk); frame_start = 1'b0; end endtask
       task pulse_stop;  begin frame_stop  = 1'b1; @(negedge clk); frame_stop  = 1'b0; end endtask

       // A full addressing phase: framing START then the eight-bit address byte.
       task address_phase; input [6:0] a; input dir; integer k; begin
           pulse_start();
           for (k = 6; k >= 0; k = k - 1) send_bit(a[k]);
           send_bit(dir);
           repeat (2) @(negedge clk);
       end endtask

       task chk; input got; input want; input integer step; begin
           if (got !== want) begin
               $display("FAIL: step %0d -- signal was %0b, expected %0b", step, got, want);
               errors = errors + 1;
           end
       end endtask

       initial begin
           errors = 0; nG = 0; nN = 0; nC = 0;
           rst_n = 1'b0; scl_in = 1'b1; sda_in = 1'b1; frame_start = 1'b0; frame_stop = 1'b0;
           repeat (3) @(negedge clk);
           chk(addrG, 1'b0, 0); chk(gcG, 1'b0, 0);
           rst_n = 1'b1; @(negedge clk);

           // 1 -- our own address, WRITE.
           nG = 0;
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 1); chk(dirG, 1'b0, 1); chk(gcG, 1'b0, 1);
           if (nG != 1) begin $display("FAIL: expected exactly one ACK for our address, saw %0d", nG); errors = errors + 1; end
           if (laG !== 7'h48) begin $display("FAIL: last_addr = 0x%02h", laG); errors = errors + 1; end

           // 2 -- our own address, READ. Same match, opposite direction.
           pulse_stop();
           nG = 0;
           address_phase(7'h48, 1'b1);
           chk(addrG, 1'b1, 2); chk(dirG, 1'b1, 2);
           if (nG != 1) begin $display("FAIL: read address not acknowledged once"); errors = errors + 1; end

           // 3 -- somebody else's address. No ACK, and not addressed.
           pulse_stop();
           nG = 0;
           address_phase(7'h49, 1'b0);
           chk(addrG, 1'b0, 3);
           if (nG != 0) begin $display("FAIL: acknowledged another device's address"); errors = errors + 1; end

           // 4 -- THE GENERAL CALL. The device that supports it answers; the device
           //      built without support stays silent. Both are compliant, because the
           //      specification makes general call OPTIONAL.
           pulse_stop();
           nG = 0; nN = 0;
           address_phase(7'b0000000, 1'b0);
           chk(addrG, 1'b1, 4); chk(gcG, 1'b1, 4); chk(dirG, 1'b0, 4);
           chk(addrN, 1'b0, 4); chk(gcN, 1'b0, 4);
           if (nG != 1) begin $display("FAIL: general call not acknowledged by the supporting device"); errors = errors + 1; end
           if (nN != 0) begin $display("FAIL: a device without general call support acknowledged it"); errors = errors + 1; end

           // 5 -- THE START BYTE. Same seven address bits as the general call, R/W = 1.
           //      "No device is allowed to acknowledge at the reception of the START
           //      byte" -- so the direction bit alone flips a broadcast into a
           //      must-not-answer.
           pulse_stop();
           nG = 0; nN = 0;
           address_phase(7'b0000000, 1'b1);
           chk(addrG, 1'b0, 5); chk(gcG, 1'b0, 5);
           if (nG != 0 || nN != 0) begin
               $display("FAIL: the START byte was acknowledged (G=%0d N=%0d)", nG, nN); errors = errors + 1; end

           // 6 -- THE PROHIBITION WINS OVER A MATCH. Device C was given the CBUS
           //      address. It matches, and it must still not respond.
           pulse_stop();
           nC = 0;
           address_phase(7'b0000001, 1'b0);
           chk(addrC, 1'b0, 6);
           if (nC != 0) begin
               $display("FAIL: a device assigned the CBUS address responded to it"); errors = errors + 1; end

           // 7 -- a STOP releases the device.
           pulse_stop();
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 7);
           pulse_stop();
           repeat (2) @(negedge clk);
           chk(addrG, 1'b0, 7);

           // 8 -- Sr TO A DIFFERENT ADDRESS must release us. A device that stayed
           //      selected across a repeated START would answer somebody else's
           //      transfer -- and Module 5 established that Sr does not release the bus,
           //      so nothing else would tell it to let go.
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 8);
           address_phase(7'h49, 1'b0);              // Sr, then another device's address
           chk(addrG, 1'b0, 8);

           // 8b -- THE DEVICE MUST LET GO AT THE Sr ITSELF, not merely once the new
           //       address resolves eight clocks later. A device still reporting
           //       `addressed` in that window is claiming a transfer that may belong
           //       to somebody else.
           pulse_stop();
           address_phase(7'h48, 1'b0);
           chk(addrG, 1'b1, 82);
           pulse_start();                       // the Sr alone -- no address byte yet
           repeat (2) @(negedge clk);
           chk(addrG, 1'b0, 82);                // released already, mid-address-phase

           // 9 -- Sr back to US re-selects, with the new direction.
           address_phase(7'h48, 1'b1);
           chk(addrG, 1'b1, 9); chk(dirG, 1'b1, 9);

           if (errors == 0)
               $display("PASS: own address matched both directions, general call optional, prohibitions win, Sr releases");
           else $display("FAIL: %0d error(s)", errors);
           $finish;
       end
   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_address_decoder.vhd — SYNTHESIZABLE RTL. The same decoder in VHDL, instantiating both sub-entities.
   library ieee;
   use ieee.std_logic_1164.all;

   -- The slave-side address decoder: shift, classify, compare, decide, respond.
   -- ASSEMBLED from the two blocks built earlier in this module rather than
   -- reimplementing them -- i2c_address_byte_capture and i2c_address_classifier.
   entity i2c_address_decoder is
       generic (
           OWN_ADDR        : std_logic_vector(6 downto 0) := "1001000";   -- 0x48
           -- General call support is OPTIONAL in the specification (Table 2).
           ANSWER_GEN_CALL : boolean := true
       );
       port (
           clk             : in  std_logic;
           rst_n           : in  std_logic;
           scl_in          : in  std_logic;                    -- observed bus level
           sda_in          : in  std_logic;                    -- observed bus level
           frame_start     : in  std_logic;                    -- pulse: S or Sr
           frame_stop      : in  std_logic;                    -- pulse: P
           addressed       : out std_logic;                    -- this transfer is ours
           direction       : out std_logic;                    -- 1 = master READS
           gen_call_active : out std_logic;                    -- selected by general call
           ack_request     : out std_logic;                    -- pulse: ACK the address
           last_addr       : out std_logic_vector(6 downto 0)  -- for software and debug
       );
   end entity;

   architecture rtl of i2c_address_decoder is
       constant CLS_GENERAL_CALL : std_logic_vector(3 downto 0) := x"1";

       -- stage 1
       signal cap_addr  : std_logic_vector(6 downto 0);
       signal cap_rw    : std_logic;
       signal cap_valid : std_logic;
       signal capturing : std_logic;
       -- stage 2
       signal cls_reserved, cls_must_not_ack : std_logic;
       signal cls_class : std_logic_vector(3 downto 0);
       -- stage 3
       signal own_match, gen_call_match : std_logic;
   begin
       u_capture : entity work.i2c_address_byte_capture
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
                     frame_start => frame_start, frame_stop => frame_stop,
                     addr => cap_addr, rw => cap_rw, addr_valid => cap_valid,
                     capturing => capturing);

       u_classify : entity work.i2c_address_classifier
           port map (addr => cap_addr, rw => cap_rw, is_reserved => cls_reserved,
                     addr_class => cls_class, must_not_ack => cls_must_not_ack);

       own_match      <= '1' when cap_addr = OWN_ADDR else '0';
       gen_call_match <= '1' when (cls_class = CLS_GENERAL_CALL and ANSWER_GEN_CALL) else '0';

       process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '0' then
                   addressed       <= '0';
                   direction       <= '0';
                   gen_call_active <= '0';
                   ack_request     <= '0';
                   last_addr       <= (others => '0');
               else
                   ack_request <= '0';

                   if frame_stop = '1' then
                       addressed       <= '0';
                       gen_call_active <= '0';
                   elsif frame_start = '1' then
                       -- S or Sr: stop being addressed until the new address says so.
                       addressed       <= '0';
                       gen_call_active <= '0';
                   elsif cap_valid = '1' then
                       last_addr <= cap_addr;

                       -- The prohibition WINS over a match: the footnotes forbidding a
                       -- response make no exception for a device given that address.
                       if cls_must_not_ack = '1' then
                           addressed       <= '0';
                           gen_call_active <= '0';
                       elsif own_match = '1' then
                           addressed       <= '1';
                           direction       <= cap_rw;
                           gen_call_active <= '0';
                           ack_request     <= '1';
                       elsif gen_call_match = '1' then
                           -- The general call is a broadcast WRITE: there is no readable
                           -- direction, because every device would transmit at once.
                           addressed       <= '1';
                           direction       <= '0';
                           gen_call_active <= '1';
                           ack_request     <= '1';
                       else
                           addressed       <= '0';
                           gen_call_active <= '0';
                       end if;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_address_decoder_tb.vhd — SELF-CHECKING TESTBENCH, SIMULATION ONLY. The same cases with assert report severity.
   library ieee;
   use ieee.std_logic_1164.all;

   entity i2c_address_decoder_tb is
   end entity;

   architecture sim of i2c_address_decoder_tb is
       signal clk         : std_logic := '0';
       signal rst_n       : std_logic := '0';
       signal scl_in      : std_logic := '1';
       signal sda_in      : std_logic := '1';
       signal frame_start : std_logic := '0';
       signal frame_stop  : std_logic := '0';

       -- THREE devices, separating three different decisions:
       --   G -- an ordinary part at 0x48 that answers the general call
       --   N -- the same part with general call support NOT built
       --   C -- a part mis-assigned the CBUS address
       signal addrG, dirG, gcG, ackG : std_logic;
       signal addrN, dirN, gcN, ackN : std_logic;
       signal addrC, dirC, gcC, ackC : std_logic;
       signal laG, laN, laC : std_logic_vector(6 downto 0);

       signal nG, nN, nC : natural := 0;
       signal test_done  : std_logic := '0';
   begin
       dutG : entity work.i2c_address_decoder
           generic map (OWN_ADDR => "1001000", ANSWER_GEN_CALL => true)
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
                     frame_start => frame_start, frame_stop => frame_stop,
                     addressed => addrG, direction => dirG, gen_call_active => gcG,
                     ack_request => ackG, last_addr => laG);

       dutN : entity work.i2c_address_decoder
           generic map (OWN_ADDR => "1001000", ANSWER_GEN_CALL => false)
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
                     frame_start => frame_start, frame_stop => frame_stop,
                     addressed => addrN, direction => dirN, gen_call_active => gcN,
                     ack_request => ackN, last_addr => laN);

       dutC : entity work.i2c_address_decoder
           generic map (OWN_ADDR => "0000001", ANSWER_GEN_CALL => false)
           port map (clk => clk, rst_n => rst_n, scl_in => scl_in, sda_in => sda_in,
                     frame_start => frame_start, frame_stop => frame_stop,
                     addressed => addrC, direction => dirC, gen_call_active => gcC,
                     ack_request => ackC, last_addr => laC);

       clk <= not clk after 5 ns;

       watchdog : process
       begin
           wait for 80 us;
           if test_done = '0' then
               report "watchdog expired -- the design never reached the expected state"
                   severity failure;
           end if;
           wait;
       end process;

       count : process (clk)
       begin
           if rising_edge(clk) then
               if rst_n = '1' then
                   if ackG = '1' then nG <= nG + 1; end if;
                   if ackN = '1' then nN <= nN + 1; end if;
                   if ackC = '1' then nC <= nC + 1; end if;
               end if;
           end if;
       end process;

       stim : process
           variable errs : natural := 0;
           variable bG, bN, bC : natural := 0;

           procedure waitn (n : in positive) is
           begin
               for i in 1 to n loop wait until falling_edge(clk); end loop;
           end procedure;

           procedure send_bit (b : in std_logic) is
           begin
               scl_in <= '0';   waitn(2);
               sda_in <= b;     waitn(2);
               scl_in <= '1';   waitn(2);
               scl_in <= '0';   waitn(1);
           end procedure;

           procedure pulse_start is
           begin
               frame_start <= '1'; waitn(1); frame_start <= '0';
           end procedure;

           procedure pulse_stop is
           begin
               frame_stop <= '1'; waitn(1); frame_stop <= '0';
           end procedure;

           procedure address_phase (a : in std_logic_vector(6 downto 0); dir : in std_logic) is
           begin
               pulse_start;
               for k in 6 downto 0 loop send_bit(a(k)); end loop;
               send_bit(dir);
               waitn(2);
           end procedure;

           procedure chk (got, want : in std_logic; step : in natural) is
           begin
               if got /= want then
                   report "step " & integer'image(step) & ": signal mismatch" severity error;
                   errs := errs + 1;
               end if;
           end procedure;
       begin
           waitn(3);
           chk(addrG, '0', 0); chk(gcG, '0', 0);
           rst_n <= '1'; waitn(1);

           -- Our own address, WRITE.
           bG := nG;
           address_phase("1001000", '0');
           chk(addrG, '1', 1); chk(dirG, '0', 1); chk(gcG, '0', 1);
           if (nG - bG) /= 1 then
               report "expected exactly one ACK for our address" severity error; errs := errs + 1; end if;
           if laG /= "1001000" then
               report "last_addr wrong" severity error; errs := errs + 1; end if;

           -- Our own address, READ.
           pulse_stop; bG := nG;
           address_phase("1001000", '1');
           chk(addrG, '1', 2); chk(dirG, '1', 2);
           if (nG - bG) /= 1 then
               report "read address not acknowledged once" severity error; errs := errs + 1; end if;

           -- Somebody else's address.
           pulse_stop; bG := nG;
           address_phase("1001001", '0');
           chk(addrG, '0', 3);
           if (nG - bG) /= 0 then
               report "acknowledged another device's address" severity error; errs := errs + 1; end if;

           -- THE GENERAL CALL: optional, so both behaviours are compliant.
           pulse_stop; bG := nG; bN := nN;
           address_phase("0000000", '0');
           chk(addrG, '1', 4); chk(gcG, '1', 4); chk(dirG, '0', 4);
           chk(addrN, '0', 4); chk(gcN, '0', 4);
           if (nG - bG) /= 1 then
               report "general call not acknowledged by the supporting device" severity error;
               errs := errs + 1; end if;
           if (nN - bN) /= 0 then
               report "a device without general call support acknowledged it" severity error;
               errs := errs + 1; end if;

           -- THE START BYTE: same seven address bits, R/W = 1, must never be acked.
           pulse_stop; bG := nG; bN := nN;
           address_phase("0000000", '1');
           chk(addrG, '0', 5); chk(gcG, '0', 5);
           if (nG - bG) /= 0 or (nN - bN) /= 0 then
               report "the START byte was acknowledged" severity error; errs := errs + 1; end if;

           -- THE PROHIBITION WINS OVER A MATCH.
           pulse_stop; bC := nC;
           address_phase("0000001", '0');
           chk(addrC, '0', 6);
           if (nC - bC) /= 0 then
               report "a device assigned the CBUS address responded to it" severity error;
               errs := errs + 1; end if;

           -- A STOP releases the device.
           pulse_stop;
           address_phase("1001000", '0');
           chk(addrG, '1', 7);
           pulse_stop; waitn(2);
           chk(addrG, '0', 7);

           -- Sr TO A DIFFERENT ADDRESS must release us.
           address_phase("1001000", '0');
           chk(addrG, '1', 8);
           address_phase("1001001", '0');
           chk(addrG, '0', 8);

           -- THE DEVICE MUST LET GO AT THE Sr ITSELF, not merely once the new address
           -- resolves eight clocks later. A device still reporting `addressed` in that
           -- window is claiming a transfer that may belong to somebody else.
           pulse_stop;
           address_phase("1001000", '0');
           chk(addrG, '1', 82);
           pulse_start;                        -- the Sr alone -- no address byte yet
           waitn(2);
           chk(addrG, '0', 82);                -- released already, mid-address-phase

           -- Sr back to US re-selects, with the new direction.
           address_phase("1001000", '1');
           chk(addrG, '1', 9); chk(dirG, '1', 9);

           if errs = 0 then
               report "i2c_address_decoder self-check complete: own address matched both "
                    & "directions, general call optional, prohibitions win, Sr releases"
                    severity note;
           else
               report "i2c_address_decoder self-check FAILED" severity error;
           end if;
           test_done <= '1';
           wait;
       end process;
   end architecture;

5a. Verified Execution

Each language's testbench was compiled with its decoder and both sub-modules, and run:

languagefiles compiled togethersimulatorresultcompletes at
SystemVerilogdecoder + capture + classifier + TBIcarus Verilog, -g2012PASS6660 ns
Verilog-2005decoder + capture + classifier + TBIcarus Verilog, -g2005PASS6660 ns
VHDLall three analysed into the work library, then elaboratednvc 1.23.0PASS6660 ns

5b. Cross-Language Parity

SystemVerilogVerilog-2005VHDL
own addressparameter logic [6:0]parameter [6:0]std_logic_vector(6 downto 0) generic
general call optionparameter bitparameterboolean generic
instantiationnamed port connectionsnamed port connectionsentity work.<name> direct instantiation
option in a condition&& ANSWER_GEN_CALL&& ANSWER_GEN_CALLand ANSWER_GEN_CALL on a boolean

The VHDL version uses direct entity instantiation (u_capture : entity work.i2c_address_byte_capture) rather than a component declaration. Both are legal; direct instantiation avoids restating every port in a component declaration, which is one fewer place for a port list to drift out of step with the entity it describes.

i2c_address_decoder — its own address, write

10 cycles
Ten internal clock cycles. The observed SCL input rises and stays high for three cycles then falls, while the observed SDA input stays low. One cycle after the rising edge the internal address valid pulse occurs. The following cycle the addressed output rises and stays high, and the acknowledge request output pulses for exactly one cycle.eighth bit sampledeighth bit sampledbyte completebyte completematch: addressed, ACK requestedmatch: addressed, ACKrequestedclkscl_insda_inaddr_validaddressedack_requestt0t1t2t3t4t5t6t7t8t9
Figure 2 — a successful match, from the simulation, on the internal clock. The eighth address bit is sampled on SCL's rising edge; the capture stage publishes the complete byte one cycle later; the decision stage then asserts addressed and pulses ack_request. The address here is this device's own, with a write direction, so all three stages agree. SIMULATION-DERIVED figure.

i2c_address_decoder — the START byte, refused

10 cycles
Ten internal clock cycles. The observed SCL input rises and stays high for three cycles then falls, while the observed SDA input stays high, carrying the read direction bit. The internal address valid pulse occurs and the must not acknowledge signal rises and stays high, but the addressed and acknowledge request outputs both remain low for the whole trace.byte complete: 0x01 on the wirebyte complete: 0x01 on thewireprohibition wins — no ACKprohibition wins — no ACKclkscl_insda_inaddr_validmust_not_ackaddressedack_requestt0t1t2t3t4t5t6t7t8t9
Figure 3 — the prohibition winning, from the simulation. The byte on the wire is 0x01, which is address 0000000 with the read bit set: the START byte, which no device may acknowledge. The capture stage publishes it exactly as before and the classifier raises must_not_ack — so despite a complete, well-formed address byte, the device neither becomes addressed nor requests an acknowledge. SIMULATION-DERIVED figure.

6. What the Testbench Proves

The suite instantiates three decoders, because three different decisions need three different configurations:

instanceconfigurationwhat only it can show
G0x48, general call supportedthe ordinary match, both directions, and a general call answered
N0x48, general call not builtthat ignoring a general call is compliant, not broken
C0x01 (the CBUS address), general call offthat the prohibition beats a match
stepstimulusrequired result
1own address, writeaddressed, direction = 0, one ACK
2own address, readaddressed, direction = 1, one ACK
3another device's addressnot addressed, no ACK
4general callG answers, N stays silent — both compliant
5START byte (same seven bits, R/W = 1)neither answers
6the CBUS address, to device C which holds itC does not answer
7STOPreleased
8Sr, then another device's addressreleased
8bSr alone, checked before the new byte completesalready released
9Sr back to our address, readre-selected, new direction

Step 4 needs two instances. "General call support is optional" is a statement about two compliant behaviours, and one device cannot exhibit both. Instantiating G and N side by side turns it into a checked property rather than a sentence — the same topology argument Chapter 6.2 made for its two matchers.

Step 6 needs a deliberately misconfigured instance. Device C is given the CBUS address, which no sane board would do on purpose. That is the point: the prohibition has to hold for a device that does hold the address, and only an instance configured that way can test it.

Steps 5 and 6 together are the reason the classifier takes eight bits. Step 5 differs from step 4 in one bit of one byte and inverts the required behaviour.

7. An Assertion for the Ordering Rule

The priority of §2 is a natural assertion, because it is a statement about every address phase rather than about a particular one.

Azvya Education Pvt. Ltd.VLSI Mentor
SYSTEMVERILOG ASSERTION — VERIFICATION ONLY. The prohibition must always win.
   // The strongest single property in this chapter: whatever else is true, a byte
   // the specification forbids answering is never answered. Note it does NOT
   // mention own_match -- that is exactly the point. No matching condition, now or
   // added later, may create an exception.
   property prohibition_always_wins;
       @(posedge clk) disable iff (!rst_n)
       (addr_valid && must_not_ack) |=> !ack_request;
   endproperty
   assert property (prohibition_always_wins)
       else $error("acknowledged a byte the specification forbids answering");

   // And the pulse property, for the same reason it mattered in Chapter 6.1: a
   // consumer in the byte-transfer layer counts acknowledge requests.
   property ack_request_is_a_pulse;
       @(posedge clk) disable iff (!rst_n)
       ack_request |=> !ack_request;
   endproperty

   // Addressed must not survive a framing event -- section 4's window.
   property framing_releases_immediately;
       @(posedge clk) disable iff (!rst_n)
       (frame_start || frame_stop) |=> !addressed;
   endproperty

The first property is worth writing even though the procedural suite checks the same thing, and the reason is its shape. It says nothing about which addresses match, so it keeps holding when somebody later adds 10-bit matching, or a second own-address, or a general-call variant. A test checks the design as it is; this property constrains every future version of it.

The third property is the one that would have caught §8's surviving mutation immediately, without needing step 8b — an illustration of assertions catching windows that a check-afterwards test walks straight past.

8. Mutation Testing

Six faults injected into the verified RTL. Five caught immediately; one survived and was a real gap.

mutationwhat it breaksresult
an address match beats the prohibitionthe §2 ordering, reversedFAIL — step 6
general call answered regardless of the build optiona device answers broadcasts it does not implementFAIL — step 4
ack_request is a level, not a pulsethe byte-transfer layer double-countsFAIL — three ACKs seen
a STOP does not release the deviceselection leaks past the transferFAIL — step 7
a repeated START does not release the devicesee belowinitially PASSED
the general call latches the wire direction—survived, provably equivalent

The mutation that survived and mattered. Removing the addressed <= 1'b0 from the framing-START branch left the suite passing. It is not equivalent: the device stays addressed from the Sr until the new address byte completes, eight clock pulses later, and during that window it is claiming a transfer that may belong to another device.

The original test only checked addressed after the new address had resolved — by which point the capture's addr_valid had fired and the decision stage had cleared the flag for a different reason. The design was right and the test was checking the wrong instant.

The fix is step 8b: issue the Sr alone and check immediately, before any address bits follow. Two lines, and the mutation is now caught.

The mutation that survived and did not matter. Changing the general-call branch's direction <= 1'b0 to direction <= cap_rw is provably equivalent, and the proof is in the classifier: CLS_GENERAL_CALL is assigned only for the pattern 0000000 with R/W = 0. So whenever that branch executes, cap_rw is zero. The two assignments are identical under every reachable input.

The explicit 1'b0 is still the better code — it states that a general call has no meaningful direction because a broadcast read is nonsense, rather than leaving a reader to derive that from the classifier — but it is a readability choice, not a behavioural one, and no test can distinguish them.

Module 6 totals: 27 mutations injected, 24 caught, 3 proven behaviourally equivalent. The three equivalents are documented where they arise — this chapter's direction assignment, Chapter 6.1's shifter clear, and Chapter 6.3's shadowed case item — and each was proven rather than assumed, because "probably equivalent" and "provably equivalent" are different claims and only one of them justifies not writing a test.

9. Verification Connection — The Slave Agent's Address Phase

A slave agent's address phase is where a verification environment decides whether it is modelling a device or the protocol.

Azvya Education Pvt. Ltd.VLSI Mentor
UVM CONCEPT — VERIFICATION ONLY. A configurable slave-side address responder.
   // The agent's addressing behaviour is CONFIGURATION, not code, because the
   // whole point of a reusable slave agent is that one class models many parts.
   class i2c_slave_addr_cfg extends uvm_object;
       `uvm_object_utils(i2c_slave_addr_cfg)
       bit [6:0] own_addr        = 7'h48;
       bit       answer_gen_call = 1'b0;   // OPTIONAL per Table 2 -- default off
       bit       ten_bit_mode    = 1'b0;
       bit [9:0] own_addr_10     = 10'h000;
       // The escape hatch of Chapter 6.4, as an explicit opt-in rather than an
       // absent check -- so an agent at a reserved address is a deliberate test,
       // not an accident nobody notices.
       bit       allow_reserved  = 1'b0;
       function new(string name = "i2c_slave_addr_cfg"); super.new(name); endfunction
   endclass

   // The responder mirrors the RTL's ORDERING, deliberately. A model that checked
   // the match first would disagree with a correct DUT on exactly the case the
   // prohibition exists for -- and a disagreeing model reports a DUT bug.
   function bit i2c_slave_responder::should_ack(bit [6:0] a, bit rw);
       if (is_start_byte(a, rw) || is_cbus(a)) return 0;          // FIRST
       if (a == cfg.own_addr)                  return 1;
       if (cfg.answer_gen_call && a == 7'h00 && rw == 1'b0) return 1;
       return 0;
   endfunction

Two observations, and the second is the one that bites in practice.

Addressing behaviour is configuration. A slave agent whose address is a parameter models one part; one whose address, general-call support and 10-bit mode are configuration models a family. The three-instance testbench in §6 is the block-level version of the same idea.

The reference model must share the RTL's ordering, and for a non-obvious reason. If the model checks the match before the prohibition, then against a correct DUT the two disagree on the CBUS case — and the environment reports a DUT bug that does not exist. A reference model encodes the specification's priority, and if that priority was derived rather than stated, the derivation has to be written down somewhere both the model and the RTL point at. §2 is that place for this design.

10. FPGA and ASIC Implications

The whole decoder is tiny, and the hierarchy costs nothing. Eight flip-flops for the shifter, four for the counter, a handful for the decision state, and a combinational decode. Synthesis flattens the hierarchy; the module boundaries exist for verification and review, not for area.

Keep the hierarchy anyway. Each block is independently testable — Chapter 6.3's classifier gets an exhaustive 256-combination sweep precisely because it is a separate combinational block with no state. Folding it into the decoder would make that sweep impossible and the reserved-map decode unreviewable against the table.

All the reset state must share one domain. addressed, the capture's capturing and bit_count, and — in a 10-bit device — the matcher's was_selected must all be cleared by the same reset. Splitting them lets a peripheral reset leave a device addressed for a transfer that no longer exists, or remembering a selection from a previous transaction. Chapter 5.4 made the same point for its classifier and it is the same failure.

An always-on decoder is normal on an ASIC. A peripheral that can be woken by being addressed needs framing detection and address decoding running in an ungated domain, which makes these few dozen flip-flops part of the always-on partition and their reset a power-on reset. Chapter 5.2 §11 covered the consequence: such a block comes out of reset onto a bus that may have been live for hours, so its assumptions about initial state are load-bearing.

Expose last_addr. What the device observed is different information from what it was configured with — Chapter 6.4 exposed the latter — and having both readable turns "why is this device not responding" into two register reads instead of a capture session.

11. Debugging — The Device That Answered Two Transfers at Once

A slave that corrupted a transfer it was not part of

Pitfall — releasing the addressed flag when the new address resolves instead of at the repeated START
Buggy Code
// A decoder handles the release conditions, and handles them almost right. It
// clears 'addressed' on a STOP, and relies on the new address byte to clear it
// after a repeated START:
//
//   if (frame_stop) begin
//       addressed <= 1'b0;
//   end else if (cap_valid) begin
//       if (own_match) addressed <= 1'b1;
//       else           addressed <= 1'b0;      // <-- a foreign address clears it
//   end
//
// The reasoning is sound as far as it goes: after an Sr a new address arrives, and
// if it is not ours the else-branch releases us. Correct, eventually.
//
// It passes a test that issues an Sr, sends another device's address, and then
// checks that we let go -- which is the obvious test to write, and which this
// design passes, because by then the new address HAS resolved.
Symptom

Two devices on the bus, a sensor at 0x48 and an EEPROM at 0x50, and a controller that uses combined transfers -- write a register pointer to the sensor, repeated START, read it back; then a repeated START to address the EEPROM without releasing the bus.

The sensor works. The EEPROM works. Interleaved, the EEPROM read occasionally returns data with bits cleared -- and only when it directly follows a sensor transfer in the same transaction, never when the EEPROM is addressed after a STOP.

It is intermittent in a way that tracks bus speed: slower buses fail less. That points at timing, and the timing measures clean. It also tracks which register the sensor was addressed for, which makes no sense at all and sends the investigation into the sensor's datasheet errata.

Captures are clean. Framing is legal, both addresses are correct on the wire, and the EEPROM's data is well formed apart from being wrong.

Root Cause

The sensor stayed 'addressed' across the repeated START, and for eight clock pulses it and the EEPROM were both addressed at once.

Section 4 is the rule: a device must release at the framing event ITSELF, not once the new address resolves. Between the Sr and the completion of the new address byte there is a window -- eight SCL pulses -- in which this design still reported addressed = 1. During that window the EEPROM's address byte was on the wire, the EEPROM correctly became addressed too, and the sensor had not yet been told to let go.

Nothing bad happens from being addressed alone. What happens is that anything DOWNSTREAM of 'addressed' acts on it. In this device the byte-transfer layer used 'addressed' to decide whether to drive SDA during the acknowledge slot -- so the sensor acknowledged the EEPROM's address byte, and then, because the transfer was a read, began transmitting alongside the EEPROM.

Two transmitters on SDA resolve as the wired-AND (Chapter 6.4, section 7), so the controller read the bitwise AND of the EEPROM's data and whatever the sensor was putting out. That explains every confusing feature:

- bits CLEARED, never set -- AND can only clear - depends on the sensor's register pointer -- that determined what the sensor transmitted, which is one operand of the AND - worse at speed -- the window is a fixed number of SCL pulses, so it is a larger fraction of a fast transfer's timeline - never after a STOP -- a STOP released the sensor correctly; only the Sr path was broken - captures clean -- the bus carried exactly what both devices drove

The test that passed was checking the right signal at the wrong INSTANT. It looked after the new address had resolved, which is the one moment the buggy design is correct.

Fix
// Release at the framing event:
//
//   end else if (frame_start) begin
//       addressed       <= 1'b0;      // S or Sr: not the target until the new
//       gen_call_active <= 1'b0;      // address says so
//   end else if (cap_valid) begin
//       ...
//
// Section 8 confirms it: injecting this exact fault into the corrected design is
// caught by step 8b and by nothing else in the suite.
//
// The verification lessons, in order of how much they generalise.
//
//   1. CHECK THE WINDOW, NOT JUST THE OUTCOME. The original test asked "did we
//      let go?" and the answer was yes -- eventually. The right question is "when
//      did we let go?", and answering it requires a check between the two events,
//      which is step 8b: issue the Sr ALONE and check before any address bits
//      follow. Two lines.
//
//   2. THIS IS WHAT ASSERTIONS ARE FOR. Section 7's third property --
//      (frame_start || frame_stop) |=> !addressed -- catches it on the first
//      transfer of the first test, with no thought about instants at all, because
//      it constrains EVERY cycle rather than a chosen one. A procedural test has
//      to know where to look; an assertion does not.
//
//   3. SUSPECT "STALE CLAIM" WHEN BITS ARE CLEARED AND NEVER SET. The wired-AND
//      signature from Chapter 6.4 appears again here, and it means the same thing:
//      two devices transmitting. The new information is that they need not both be
//      misconfigured -- one of them can simply have failed to let go.
//
// The design habit: for any flag meaning "I am currently the target", write down
// the exact EVENT that clears it, not the condition that will eventually be
// false. "Cleared when a foreign address arrives" and "cleared at the framing
// event" differ by a window, and a window is where two devices overlap.

12. Common Misconceptions

"The decoder decides whether to acknowledge by comparing its address." Comparing is necessary and not sufficient. A prohibition from the reserved map beats a match, and a decoder that checks the match first is non-compliant in exactly the case the prohibition exists for.

"A device given the CBUS address should respond to it." It must not. The footnote forbids I²C-compatible devices from responding, with no exception for a device that holds the address.

"The general call address and the START byte are different addresses." They are the same seven address bits and differ only in the direction bit — which is why the classifier takes all eight and why §6's steps 4 and 5 differ by one bit and invert the required behaviour.

"A device that ignores a general call is broken." General call support is optional in every configuration. Ignoring it is a compliant choice, and §6 instantiates two devices specifically to make both behaviours checkable.

"A repeated START does not need to release the device, because the new address will." It will, eight clock pulses later. In between, the device is claiming a transfer that may belong to somebody else — and §11 is what downstream logic does with that stale claim.

"direction tells the device what to do." It tells the device what the master asked for. Acting on it is the byte-transfer layer's job, and separating the two is what lets the decoder be verified without modelling the ninth clock.

"ack_request should be a level while the device is addressed." It is an event: one pulse per byte to be acknowledged. A level makes one address byte indistinguishable from several, and §8's third mutation shows a consumer counting three where there was one.

"A surviving mutation means the test suite has a hole." Sometimes. Of this module's three survivors, all three were proven equivalent, and one further survivor in this chapter was a genuine gap that needed a new test. Telling them apart requires a proof, not an assumption.

13. Reason It Through

A device is strapped to 0000001. A transfer addresses 0000001. Its comparator matches. What must it do, and what does that imply about the RTL's structure?

It must not respond, because the CBUS footnote forbids it unconditionally. The structural implication is that the classification result has to be available and evaluated first when the match is acted on — so the classifier is a combinational stage feeding the decision, and the decision's first test is the prohibition. Checking the match first produces a device that is compliant except in the one case the rule was written for.

Why must a decoder take the direction bit into account when classifying, not just when latching direction?

Because 0000000 with R/W = 0 is a general call that a device may answer and 0000000 with R/W = 1 is the START byte that no device may answer. A classifier working on seven bits has merged two rows of Table 3 that mean opposite things, and it will acknowledge a byte the specification forbids.

Why must addressed be cleared at a repeated START, while Chapter 6.2's was_selected must survive one?

Because they mean different things. addressed is a claim about the present — I am the target of the transfer now — and after an Sr that is not yet known, so holding it asserts something false for eight clock pulses. was_selected is a record of the past — I matched all ten bits earlier in this transaction — which remains true across the Sr and is the only thing that makes the 10-bit read re-address unambiguous.

A test issues a repeated START, sends another device's address, and confirms this device released. What can that test not detect?

That the device released too late. By the time the new address has resolved, a design that releases on the foreign address and a design that releases at the framing event are indistinguishable — the flag is clear either way. Detecting the difference requires checking in the window between the two events, which means issuing the Sr alone.

Why does the reference model in a verification environment have to share the RTL's decision ordering?

Because if it checks the match before the prohibition, it disagrees with a correct DUT on the CBUS and START-byte cases — and an environment whose model disagrees with a correct design reports a bug that does not exist. Since the ordering is derived rather than stated in the specification, the derivation has to live somewhere both the model and the RTL refer to.

The decoder could be one module instead of three. What is lost?

Independent verification and reviewability. The classifier as a separate combinational block gets an exhaustive 256-combination sweep against an independently-written model; folded in, that is impossible because it would have state and framing dependencies. The reserved-map decode would also stop being a line-by-line transcription of the table, which is the property that makes it checkable by review at all. Synthesis flattens the hierarchy regardless, so the boundaries cost nothing and buy both.

14. Understanding Check

15. Summary

The decoder is assembled, not written. Framing from Module 5, capture from Chapter 6.1, classification from Chapter 6.3, and one new decision stage.

The ordering is the content: prohibition, then own match, then general call. That priority is nowhere stated in the specification and has to be derived from what the footnotes are for — a match is necessary and not sufficient.

Classification needs all eight bits, because two rows of the reserved table share an address and mean opposite things.

ack_request is a decision, not a driver. Whether to acknowledge belongs here; driving the ninth clock slot belongs to Module 7, and the split is what lets this block be verified without modelling the acknowledge cycle.

Release happens at the framing event, not when the next address resolves. The window between them is where two devices overlap, and §11 shows what downstream logic does with a stale claim.

addressed and a 10-bit was_selected have opposite rules across a repeated START, because one is a claim about the present and the other a record of the past.

Three instances were needed to verify three claims: an ordinary match, that ignoring a general call is compliant, and that a prohibition beats a match on a device deliberately given a forbidden address.

One survivor was a real gap and three were provably equivalent. Module 6 totals 27 mutations, 24 caught, 3 proven equivalent — and the proofs matter, because "probably equivalent" would not justify skipping a test.

16. What Comes Next

Module 6 is complete. A transfer can now be framed, addressed, and recognised by exactly the device it was meant for — and that device knows which direction the master asked for.

What happens next is the thing every chapter in this module has deferred to the same place: the acknowledge. Four chapters have produced an ack_request without once saying what an acknowledge is on the wire, which clock pulse carries it, who drives it, or what it does and does not prove.

Module 7 — Byte Transfer & the ACK/NACK Cycle supplies all of it: the byte as the atomic unit of nine clock pulses, the acknowledge slot as a handshake the receiver drives, why the direction of the byte rather than the role of the device decides who acknowledges, and why a master deliberately NACKs the last byte of a read.

Browse the full path on the I²C tutorials index. For the blocks this chapter assembles, see The Address Byte and Reserved Addresses; for the framing it depends on, The START Condition.

Continue learning