Skip to content
VLSI Mentor

I²C · Module 16

EEPROM Access — Word Address, Random Read and Sequential Read

The serial memory the specification names in a footnote, worked out in full. Explains why a current-address read and a random read are byte-for-byte identical on the wire, what happens when a master and a device disagree about how many word-address bytes there are, and why an address past the end of the array wraps silently instead of failing.

Chapter 16.1 established that the register-map pattern is one sentence of specification and six questions the datasheet has to answer. This chapter takes the device that sentence actually names — a serial memory — and works it out completely.

A serial EEPROM is the purest form of the pattern. There is no functional behaviour to speak of: it holds bytes, and every interesting thing about it is a convention concerning the pointer. Which makes it the ideal place to find out what those conventions cost when a master gets one wrong.

That is the mechanism the whole chapter uses, and the emphasised sentence is a requirement people routinely miss: the read phase of a combined transfer ends with the master declining the last byte, before the repeated START, not after it.

1. Two Reads That Are Identical on the Wire

An EEPROM offers two read transactions, and the distinction between them is not visible in any capture.

The current-address read. START, address with the read bit, one or more bytes, STOP. No pointer is sent. The device serves from wherever its internal counter already is.

The random read. START, address with the write bit, the word address bytes, repeated START, address with the read bit, one or more bytes, STOP. The write phase exists only for its effect on the pointer — it carries no data at all.

Now look at the second half of a random read in isolation: START, address with the read bit, bytes, STOP. That is a current-address read. Byte for byte.

The device cannot tell them apart either, except by having watched what came before. There is nothing in the read phase that says "a pointer was just written"; the only difference is history. This is why an EEPROM's behaviour depends on transaction sequence in a way that a stateless protocol view cannot capture.

2. How Wide Is the Word Address?

This is the question with the worst failure mode in the module, and it is not one of Chapter 16.1's six — it is prior to all of them.

A 256-byte part needs one address byte. A 32-kilobyte part needs two. And the master has to know which, because the device decides how many bytes it will consume as the pointer before it decides anything else.

Consider a master that believes a part takes one address byte, talking to a part that takes two. The master sends:

bytethe master meansthe device does
1address, writeacknowledges
2word address 0x10takes it as the high half of the pointer
3data 0xABtakes it as the low half — the pointer is now 0x10AB
4data 0xCDwrites 0xCD at 0x10AB
5data 0xEFwrites 0xEF at 0x10AC

Every byte is acknowledged. The frame is well formed. A scope shows a perfectly conforming transfer. And the master has written two bytes instead of three, at an address it never named, having silently donated its first data byte to the pointer.

The reverse mistake is just as bad and fails differently. A master that sends two address bytes to a one-byte part has its high byte taken as the pointer and its low byte taken as the first data byte — so the data lands at the address the high byte named, shifted by one position in the payload.

There is no acknowledge, no NACK, no status bit and no timing difference that distinguishes either case from correct operation. The only evidence is that the data is in the wrong place, and you have to read it back from the right place to find out.

3. An Address Past the End of the Array

A master that believes a part is larger than it is sends a pointer the device cannot hold. What happens then is worth being precise about, because the answer is neither "an error" nor "undefined".

The excess bits are not refused and not reported, because the wires for them do not exist. The device's counter is as wide as its array, so a pointer that overflows it simply wraps: on a 1024-word part, word address 0x0834 lands at 0x0034.

Azvya Education Pvt. Ltd.VLSI Mentor
the arithmetic, on a 1024-word part
   the master sends      0x08 0x34
   assembled pointer     0x0834      = 2100
   the device's counter  10 bits     = 0..1023
   what it holds         0x0834 AND 0x03FF = 0x0034 = 52

   so the access lands at 52, and 52 is a real location holding real data.

That is the mechanism behind "my write went to the wrong place". It is also why the counter must be masked to the array rather than merely assembled: a truncated word address — a master that sent the high byte and then stopped — otherwise leaves the counter outside the array, and the next read is an out-of-range access rather than a merely wrong one.

4. A Random Read, Drawn

A sequence diagram with two actors, master and EEPROM at address 0x50. The master sends a START, then the address byte 0xA0 meaning device 0x50 with write direction, and the EEPROM acknowledges. The master sends the high word-address byte 0x02 and the EEPROM acknowledges, then the low word-address byte 0x10 and the EEPROM acknowledges. No data byte follows. The master sends a repeated START, then the address byte 0xA1 with read direction, which the EEPROM acknowledges. The EEPROM then sends the byte at location 0x0210, which the master acknowledges, and the byte at 0x0211, which the master not-acknowledges. The master finally sends a STOP.Random read: set the pointer to 0x0210, then read two bytesMasterEEPROM 0x50S — START0xA0 — addr 0x50, WACK0x02 — word addr,highACK0x10 — word addr,lowACK — pointer is0x0210Sr — repeated START0xA1 — addr 0x50, RACK — still thereceiverdata at 0x0210ACK — send anotherdata at 0x0211NACK — that isenoughP — STOP
A random read on a two-address-byte part. The write phase carries no data — it exists only to set the pointer — and the repeated START is what keeps the two halves one transaction.

The write phase sends zero data bytes. That is the part people find surprising. It is a write transaction by direction bit and it writes nothing; its entire purpose is to leave the pointer somewhere. A device that required at least one data byte after the pointer could not support a random read.

The pointer survives the repeated START, and note 4 requires the bus logic not to. Exactly the distinction Chapter 16.1 §4 drew: the frame-level state machine resets, the device's counter does not.

The last acknowledge before the read is the EEPROM's. It is answering its own address, and at that instant it is still the receiver. Only the two data bytes' acknowledges belong to the master.

5. A Sequential Read, at Byte Resolution

Sequential read across the array boundary: 0xFE, 0xFF, 0x00, 0x01

9 cycles
A waveform with nine intervals at byte resolution. The first interval is a start condition, the second the address byte with read direction which the EEPROM acknowledges. The next four intervals each carry one data byte from the EEPROM, read from word addresses 0xFE, 0xFF, 0x00 and 0x01 in turn; the counter rolls over from 0xFF to 0x00 between the third and fourth of them. The master acknowledges the first three bytes and not-acknowledges the fourth. A rollover row shows the rollover flag rising at the point the counter wraps. The final interval is a stop condition.end of the arrayend of the arraybeginning of the arraybeginning of thearraythe counter rolled over, silentlythe counter rolled over,silentlymaster NACKs: this is the last bytemaster NACKs: this is thelast byteframeSaddr RdatadatadatadataPPPack0A tgtA mstA mstA mstN mstN mstN mstN mstaddr0FEFEFF0001020202byte00@FE@FF@00@01@01@01@01rollt0t1t2t3t4t5t6t7t8
A sequential read of four bytes from the end of a 256-byte array. The counter rolls over from 0xFF to 0x00 in the middle of the burst, silently, and the master's NACK on the fourth byte is what ends it.

Two properties of that figure matter for a driver.

The rollover is invisible to the master. Bytes three and four arrive exactly like bytes one and two, acknowledged the same way, at the same rate. A master reading four bytes from 0xFE on a 256-byte part gets two bytes from the end of the array and two from the beginning of it, and nothing in the transfer says so. The roll row in the figure is a device output added for the testbench — a real part does not have it.

The master decides where the burst ends, and it ends with a NACK. The device will serve bytes indefinitely; the only thing that stops it is the master declining one. Which is why format 3's emphasised sentence matters: if a repeated START is going to follow, that NACK comes before it.

6. The EEPROM Addressing Model in Three Languages

A word-address engine parameterised on the number of address bytes and the array size, its independent oracle, and both in all three languages. The bench instantiates three parts: a 256-byte part expecting two address bytes, a 256-byte part expecting one, and a 1024-word part expecting two — the last of which is the only one on which the high address byte can be shown to do anything.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_addressing.sv — word-address collection, masking and rollover, parameterised on the part
   // -----------------------------------------------------------------------------
   // i2c_eeprom_addressing.sv
   // EEPROM word-address handling: multi-byte pointers, the three read forms, and
   // the rollover at the end of the array (UM10204 3.1.10 notes 1 and 2).
   //
   // Chapter 16.1 established the register-pointer model with a ONE-byte pointer,
   // which is all an eight-register part needs. An EEPROM has more locations than a
   // byte can name, and the specification's note 1 says only that "the internal
   // memory location must be written during the first data byte" -- it does not say
   // how many bytes the location takes.
   //
   // So a 64 kbit EEPROM sends a SIXTEEN-bit word address as two bytes, high byte
   // first, and the master must know that from the datasheet. There is nothing on the
   // bus that distinguishes a two-byte word address followed by data from a one-byte
   // word address followed by data: both are "address byte, then some data bytes".
   // Getting the width wrong writes the low half of the pointer into location zero.
   //
   // Three read forms, and the difference between them is what the master omits:
   //
   //   CURRENT ADDRESS READ   S, addr+R, data...
   //       No word address at all. Serves from wherever the internal counter is,
   //       which is only meaningful because the counter persists -- Chapter 16.1's
   //       question 3. A read whose result depends on transaction history.
   //
   //   RANDOM READ            S, addr+W, hi, lo, Sr, addr+R, data...
   //       A dummy write that sets the counter, then a repeated START and a read.
   //       This is UM10204's combined format, and the write phase carries no data --
   //       its entire purpose is the side effect on the pointer.
   //
   //   SEQUENTIAL READ        either of the above, with the master ACKing to continue
   //       Auto-increment across the whole array, not just within a page. Reads do
   //       not have the page-boundary behaviour that writes do -- Chapter 16.3 is
   //       about why those two differ.
   //
   // The rollover is a device convention: at the top of the array the counter returns
   // to zero, so a sequential read that runs off the end silently starts again at the
   // beginning rather than failing.
   // -----------------------------------------------------------------------------

   module i2c_eeprom_addressing #(
      parameter int ADDR_BYTES = 2,       // word-address width in bytes
      parameter int N_WORDS    = 256,     // array size, for the rollover
      parameter int AW         = 16,      // word-address register width
      parameter [6:0]   MY_ADDR    = 7'h50,
      parameter int CNT_W      = 8
   ) (
      input  logic              clk,
      input  logic              rst_n,

      input  logic              start_seen,
      input  logic              stop_seen,
      input  logic              byte_valid,
      input  logic [7:0]        byte_in,
      input  logic              is_addr_byte,
      input  logic              read_byte_done,
      input  logic              master_acked,

      // A tiny backing array, written by the bench so reads have something to return.
      input  logic              load_en,
      input  logic [AW-1:0]     load_addr,
      input  logic [7:0]        load_data,

      output logic              ack,
      output logic [7:0]        tx_byte,
      output logic              tx_valid,
      output logic [AW-1:0]     word_addr,      // the internal address counter
      output logic [1:0]        addr_bytes_got, // how many pointer bytes have arrived
      output logic              rolled_over,    // the counter wrapped at the array end
      output logic              was_random_read,// this read was preceded by a pointer write
      output logic [2:0]        state,
      output logic [CNT_W-1:0]  bytes_read,
      output logic [CNT_W-1:0]  bytes_written
   );

      localparam [2:0] S_IDLE  = 3'd0,
                       S_ADDRH = 3'd1,  // collecting word-address bytes
                       S_DATA  = 3'd2,  // word address complete; data may follow
                       S_READ  = 3'd3;

      // Sized bounds. A part-select of a parameter reads as zero on some tools.
      localparam [AW-1:0] ADDR_TOP  = N_WORDS - 1;
      // The counter lives INSIDE the array. A 256-byte part has an 8-bit counter, so
      // a two-byte protocol pointer carries more bits than the device owns and the
      // excess wraps. Masking at assembly is what real hardware does -- the wires for
      // those bits are simply not there -- and it is also what stops an out-of-range
      // array index, which Verilog would read as X and VHDL would refuse outright.
      localparam [AW-1:0] ADDR_MASK = N_WORDS - 1;

      logic [7:0] mem [0:N_WORDS-1];
      integer   i;

      // Advance with the documented rollover: at the top of the array, back to zero.
      task advance_addr;
         begin
            if (word_addr == ADDR_TOP) begin
               word_addr   <= {AW{1'b0}};
               rolled_over <= 1'b1;
            end else begin
               word_addr <= word_addr + 1'b1;
            end
         end
      endtask

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            state           <= S_IDLE;
            ack             <= 1'b0;
            tx_byte         <= 8'h00;
            tx_valid        <= 1'b0;
            word_addr       <= {AW{1'b0}};
            addr_bytes_got  <= 2'd0;
            rolled_over     <= 1'b0;
            was_random_read <= 1'b0;
            bytes_read      <= {CNT_W{1'b0}};
            bytes_written   <= {CNT_W{1'b0}};
            for (i = 0; i < N_WORDS; i = i + 1) mem[i] <= 8'h00;
         end else begin
            ack <= 1'b0;

            // The bench's back door, so a read has something to return.
            if (load_en) mem[load_addr] <= load_data;

            if (start_seen) begin
               // Note 4 again: any START returns the device to expecting an address.
               // Crucially the word-address COUNTER survives -- that is what makes
               // the random read work, because the pointer set in the write phase
               // must still be there after the repeated START.
               state          <= S_IDLE;
               tx_valid       <= 1'b0;
               addr_bytes_got <= 2'd0;

            end else if (stop_seen) begin
               state          <= S_IDLE;
               tx_valid       <= 1'b0;
               addr_bytes_got <= 2'd0;

            end else if (byte_valid) begin
               case (state)

                  S_IDLE: begin
                     if (is_addr_byte && (byte_in[7:1] == MY_ADDR)) begin
                        ack <= 1'b1;
                        if (byte_in[0]) begin
                           // A read. Whether this is a CURRENT ADDRESS read or the
                           // second half of a RANDOM read depends only on whether a
                           // pointer write preceded it -- and the device cannot tell
                           // the difference except by having seen it.
                           tx_byte  <= mem[word_addr];
                           tx_valid <= 1'b1;
                           state    <= S_READ;
                        end else begin
                           // A write: the word address comes next, ADDR_BYTES of it.
                           addr_bytes_got  <= 2'd0;
                           was_random_read <= 1'b0;
                           state           <= S_ADDRH;
                        end
                     end
                  end

                  // Collect the word address, high byte first. This is the state a
                  // master with the wrong ADDR_BYTES gets wrong: it sends one byte
                  // and starts writing data, and the data's first byte is consumed
                  // as the low half of the pointer.
                  S_ADDRH: begin
                     ack <= 1'b1;
                     if (ADDR_BYTES == 1) begin
                        word_addr       <= {{(AW-8){1'b0}}, byte_in} & ADDR_MASK;
                        addr_bytes_got  <= 2'd1;
                        was_random_read <= 1'b1;
                        state           <= S_DATA;
                     end else begin
                        if (addr_bytes_got == 2'd0) begin
                           // High byte, shifted into place and masked to the array.
                           word_addr      <= {byte_in, {(AW-8){1'b0}}} & ADDR_MASK;
                           addr_bytes_got <= 2'd1;
                        end else begin
                           // Low byte completes the pointer, masked the same way.
                           word_addr       <= {word_addr[AW-1:8], byte_in} & ADDR_MASK;
                           addr_bytes_got  <= 2'd2;
                           was_random_read <= 1'b1;
                           state           <= S_DATA;
                        end
                     end
                  end

                  // Data bytes, if any. A random read sends none -- the write phase
                  // exists only for its effect on the pointer.
                  S_DATA: begin
                     ack           <= 1'b1;
                     mem[word_addr] <= byte_in;
                     bytes_written <= bytes_written + 1'b1;
                     advance_addr;
                  end

                  default: ;
               endcase

            end else if (read_byte_done && state == S_READ) begin
               bytes_read <= bytes_read + 1'b1;
               advance_addr;
               if (!master_acked) begin
                  tx_valid <= 1'b0;
                  state    <= S_IDLE;
               end else begin
                  // Sequential read: the next byte comes from the advanced counter,
                  // computed here so it is ready for the next transfer.
                  if (word_addr == ADDR_TOP) tx_byte <= mem[0];
                  else                       tx_byte <= mem[word_addr + 1'b1];
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_addressing_tb.sv — the independent oracle: fifteen scenarios across three differently-sized parts
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_eeprom_addressing_tb.sv
   // Independent oracle for i2c_eeprom_addressing.
   //
   // Two instances with DIFFERENT word-address widths see the same byte stream,
   // because the whole point of the chapter is that the bus cannot tell them apart.
   // Test 6 drives a two-byte sequence into both and shows the one-byte instance
   // consuming the data as the low half of its pointer -- a corruption with no
   // protocol error anywhere.
   //
   //   dut2 : ADDR_BYTES = 2   a 64 kbit-class part
   //   dut1 : ADDR_BYTES = 1   a 2 kbit-class part
   // -----------------------------------------------------------------------------
   module i2c_eeprom_addressing_tb;

      localparam [2:0] S_IDLE = 3'd0, S_ADDRH = 3'd1, S_DATA = 3'd2, S_READ = 3'd3;
      localparam [6:0] ADDR = 7'h50;
      localparam integer NW = 256;
      localparam integer AW = 16;
      // A two-byte word address on a 256-byte part is very nearly a contradiction:
      // every bit the high byte carries is masked away again, because the device only
      // owns eight address bits. So the bench also instantiates a part where the high
      // byte genuinely survives -- 1024 words, ten address bits -- which is the only
      // configuration in which the high half of the pointer can be shown to work.
      localparam integer NWB = 1024;

      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;
      logic        read_byte_done = 1'b0;
      logic        master_acked = 1'b0;
      logic        load_en = 1'b0;
      logic [AW-1:0] load_addr = 0;
      logic [7:0]  load_data = 8'h00;

      logic        a2_ack, a2_txv, a2_roll, a2_rand;
      logic [7:0]  a2_tx;
      logic [AW-1:0] a2_wa;
      logic [1:0]  a2_got;
      logic [2:0]  a2_state;
      logic [7:0]  a2_rd, a2_wr;

      logic        a1_ack, a1_txv, a1_roll, a1_rand;
      logic [7:0]  a1_tx;
      logic [AW-1:0] a1_wa;
      logic [1:0]  a1_got;
      logic [2:0]  a1_state;
      logic [7:0]  a1_rd, a1_wr;

      logic        ab_ack, ab_txv, ab_roll, ab_rand;
      logic [7:0]  ab_tx;
      logic [AW-1:0] ab_wa;
      logic [1:0]  ab_got;
      logic [2:0]  ab_state;
      logic [7:0]  ab_rd, ab_wr;

      integer errors = 0;
      integer n;

      i2c_eeprom_addressing #(.ADDR_BYTES(2), .N_WORDS(NW), .AW(AW),
                              .MY_ADDR(ADDR), .CNT_W(8)) dut2 (
         .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),
         .read_byte_done(read_byte_done), .master_acked(master_acked),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .ack(a2_ack), .tx_byte(a2_tx), .tx_valid(a2_txv), .word_addr(a2_wa),
         .addr_bytes_got(a2_got), .rolled_over(a2_roll),
         .was_random_read(a2_rand), .state(a2_state),
         .bytes_read(a2_rd), .bytes_written(a2_wr));

      i2c_eeprom_addressing #(.ADDR_BYTES(1), .N_WORDS(NW), .AW(AW),
                              .MY_ADDR(ADDR), .CNT_W(8)) dut1 (
         .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),
         .read_byte_done(read_byte_done), .master_acked(master_acked),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .ack(a1_ack), .tx_byte(a1_tx), .tx_valid(a1_txv), .word_addr(a1_wa),
         .addr_bytes_got(a1_got), .rolled_over(a1_roll),
         .was_random_read(a1_rand), .state(a1_state),
         .bytes_read(a1_rd), .bytes_written(a1_wr));

      // The 1024-word part: two address bytes, and ten address bits to put them in.
      i2c_eeprom_addressing #(.ADDR_BYTES(2), .N_WORDS(NWB), .AW(AW),
                              .MY_ADDR(ADDR), .CNT_W(8)) dutB (
         .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),
         .read_byte_done(read_byte_done), .master_acked(master_acked),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .ack(ab_ack), .tx_byte(ab_tx), .tx_valid(ab_txv), .word_addr(ab_wa),
         .addr_bytes_got(ab_got), .rolled_over(ab_roll),
         .was_random_read(ab_rand), .state(ab_state),
         .bytes_read(ab_rd), .bytes_written(ab_wr));

      always #5 clk = ~clk;

      task step; begin @(posedge clk); @(negedge clk); end endtask

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; start_seen = 1'b0; stop_seen = 1'b0; byte_valid = 1'b0;
            is_addr_byte = 1'b0; read_byte_done = 1'b0; master_acked = 1'b0;
            load_en = 1'b0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task ev_start; begin @(negedge clk); start_seen = 1'b1; @(posedge clk); @(negedge clk); start_seen = 1'b0; end endtask
      task ev_stop;  begin @(negedge clk); stop_seen  = 1'b1; @(posedge clk); @(negedge clk); stop_seen  = 1'b0; end endtask

      task send_addr (input rw);
         begin
            @(negedge clk); byte_in = {ADDR, rw}; 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 take (input do_ack);
         begin
            @(negedge clk); read_byte_done = 1'b1; master_acked = do_ack;
            @(posedge clk); @(negedge clk); read_byte_done = 1'b0;
         end
      endtask

      // Preload both instances' arrays through the back door.
      task preload (input integer a, input [7:0] d);
         begin
            @(negedge clk); load_en = 1'b1; load_addr = a[AW-1:0]; load_data = d;
            @(posedge clk); @(negedge clk); load_en = 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 (0x%0h) expected %0d (0x%0h)", what, got, got, exp, 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_eeprom_addressing: three read forms and a pointer the bus cannot size ===");

         // ----------------------------------------------------------------
         // T1. A two-byte word address is assembled high byte first.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         ck_int("T1 collecting the word address", a2_state, S_ADDRH);
         send_data(8'h00);                       // high byte
         ck_int("T1 one pointer byte so far", a2_got, 1);
         send_data(8'h2A);                       // low byte
         $display("T1  a two-byte word address, high byte first");
         ck_int("T1 pointer assembled", a2_wa, 16'h002A);
         ck_int("T1 two pointer bytes", a2_got, 2);
         ck_int("T1 now accepting data", a2_state, S_DATA);
         ev_stop;

         // ----------------------------------------------------------------
         // T2. A WRITE: pointer then data, with the counter advancing.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h10);     // word address 0x0010
         send_data(8'hA0); send_data(8'hA1); send_data(8'hA2);
         $display("T2  a write: pointer, then data, counter advancing");
         ck_int("T2 three bytes written", a2_wr, 3);
         ck_int("T2 counter advanced past them", a2_wa, 16'h0013);
         ev_stop;
         // read them back with a random read
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h10);
         ev_start;
         send_addr(1'b1);
         ck_int("T2 read back 0xA0", a2_tx, 8'hA0); take(1'b1);
         ck_int("T2 read back 0xA1", a2_tx, 8'hA1); take(1'b1);
         ck_int("T2 read back 0xA2", a2_tx, 8'hA2); take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T3. RANDOM READ. The write phase carries NO data -- its whole purpose is
         //     the side effect on the pointer, and then a repeated START turns the
         //     transfer around. This is UM10204's combined format.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0080, 8'h5A);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h80);     // pointer only, no data
         ck_int("T3 pointer set, no data written", a2_wr, 0);
         ck_bit("T3 marked as a pointer write", a2_rand, 1'b1);
         ev_start;                               // repeated START
         send_addr(1'b1);
         $display("T3  a random read: a dummy write for its side effect, then Sr");
         ck_bit("T3 serving", a2_txv, 1'b1);
         ck_int("T3 serves the addressed location", a2_tx, 8'h5A);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T4. CURRENT ADDRESS READ. No word address at all. It works only because
         //     the counter survived the STOP, so the result depends on history.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0040, 8'h11);
         preload(16'h0041, 8'h22);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h40);
         ev_stop;                                // pointer left at 0x0040
         ck_int("T4 counter survived the STOP", a2_wa, 16'h0040);
         ev_start;
         send_addr(1'b1);                        // a bare read, no pointer
         $display("T4  a current address read: no pointer, so history decides");
         ck_int("T4 serves 0x0040", a2_tx, 8'h11);
         take(1'b1);
         ck_int("T4 then 0x0041", a2_tx, 8'h22);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T5. SEQUENTIAL READ across many locations. Reads auto-increment through
         //     the whole array -- there is no page boundary on a read.
         // ----------------------------------------------------------------
         do_reset;
         for (n = 0; n < 20; n = n + 1) preload(16'h0000 + n[AW-1:0], 8'h70 + n[7:0]);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h00);
         ev_start;
         send_addr(1'b1);
         $display("T5  a sequential read runs across the array, not a page");
         for (n = 0; n < 20; n = n + 1) begin
            ck_int("T5 sequential byte", a2_tx, 8'h70 + n[7:0]);
            take(n < 19);
         end
         ck_int("T5 twenty bytes served", a2_rd, 20);
         ev_stop;

         // ----------------------------------------------------------------
         // T6. THE CHAPTER'S CENTRAL HAZARD. The same two-byte pointer sequence
         //     driven into a ONE-byte device. Nothing on the bus is wrong; every
         //     byte is acknowledged; and the second pointer byte is consumed as
         //     DATA at the location the first byte named.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         ck_bit("T6 both devices acknowledged", a2_ack, 1'b1);
         ck_bit("T6 both devices acknowledged (1-byte)", a1_ack, 1'b1);
         send_data(8'h01);                       // intended as the HIGH byte
         ck_int("T6 two-byte device: half a pointer", a2_got, 1);
         ck_int("T6 one-byte device: a COMPLETE pointer", a1_got, 1);
         ck_int("T6 one-byte device points at 0x0001", a1_wa, 16'h0001);
         ck_int("T6 one-byte device now expects data", a1_state, S_DATA);
         send_data(8'h20);                       // intended as the LOW byte
         $display("T6  a two-byte pointer sent to a one-byte device");
         // 0x0120 is beyond a 256-byte array, so the counter -- which is only as wide
         // as the array -- holds 0x20. A pointer wider than the device wraps into it.
         ck_int("T6 two-byte pointer 0x0120 wraps to 0x0020", a2_wa, 16'h0020);
         ck_bit("T6 one-byte device acknowledged it as data", a1_ack, 1'b1);
         ck_int("T6 one-byte device WROTE the low byte as data", a1_wr, 1);
         ev_stop;
         // and the corruption is at location 1, which nothing on the bus reported
         ev_start;
         send_addr(1'b0);
         send_data(8'h01);
         ev_start;
         send_addr(1'b1);
         ck_int("T6 location 0x0001 holds the stray pointer byte", a1_tx, 8'h20);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T7. THE ROLLOVER. A sequential read from the last location returns to
         //     zero rather than failing, so a read that runs off the end silently
         //     starts again.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h00FF, 8'hEE);
         preload(16'h0000, 8'h01);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'hFF);     // the last location
         ev_start;
         send_addr(1'b1);
         $display("T7  a sequential read rolls over instead of ending");
         ck_int("T7 the last location", a2_tx, 8'hEE);
         take(1'b1);
         ck_int("T7 back at location zero", a2_tx, 8'h01);
         ck_bit("T7 rollover reported", a2_roll, 1'b1);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T8. A write also rolls over at the array end. Chapter 16.3 shows that a
         //     write ALSO wraps at a page boundary, which is a different and much
         //     smaller modulus -- two wraps, two causes.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'hFE);
         send_data(8'hD0); send_data(8'hD1); send_data(8'hD2);
         $display("T8  a write rolls over at the end of the array too");
         ck_bit("T8 rollover reported", a2_roll, 1'b1);
         ck_int("T8 counter is back near zero", a2_wa, 16'h0001);
         ev_stop;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h00);
         ev_start;
         send_addr(1'b1);
         ck_int("T8 location zero got the third byte", a2_tx, 8'hD2);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T9. A repeated START abandons a partial word address. Half a pointer is
         //     not a pointer, and the counter must not be left half-updated for the
         //     read that follows.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0000, 8'h99);
         ev_start;
         send_addr(1'b0);
         send_data(8'h07);                       // high byte only
         ck_int("T9 half a pointer collected", a2_got, 1);
         ev_start;                               // abandon it
         $display("T9  a repeated START abandons a partial word address");
         ck_int("T9 pointer-byte count cleared", a2_got, 0);
         ck_int("T9 back to expecting an address", a2_state, S_IDLE);
         send_addr(1'b1);
         // the counter still holds whatever the partial write left, which is exactly
         // why a master must never rely on an abandoned pointer
         ck_bit("T9 serving something", a2_txv, 1'b1);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T10. A wrong device address is ignored by both instances.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); byte_in = {7'h51, 1'b0}; is_addr_byte = 1'b1; byte_valid = 1'b1;
         @(posedge clk); @(negedge clk); byte_valid = 1'b0; is_addr_byte = 1'b0;
         $display("T10 another device's address is ignored");
         ck_bit("T10 two-byte instance silent", a2_ack, 1'b0);
         ck_bit("T10 one-byte instance silent", a1_ack, 1'b0);
         ck_int("T10 both idle", a2_state, S_IDLE);

         // ----------------------------------------------------------------
         // T11. A random read followed by a sequential read with no new pointer:
         //      the second read continues from where the first stopped.
         // ----------------------------------------------------------------
         do_reset;
         for (n = 0; n < 8; n = n + 1) preload(16'h0030 + n[AW-1:0], 8'hB0 + n[7:0]);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h30);
         ev_start;
         send_addr(1'b1);
         ck_int("T11 first read", a2_tx, 8'hB0); take(1'b1);
         ck_int("T11 second read", a2_tx, 8'hB1); take(1'b0);
         ev_stop;
         $display("T11 a later bare read continues where the last one stopped");
         ev_start;
         send_addr(1'b1);                        // bare read, no pointer
         ck_int("T11 continues at 0x0032", a2_tx, 8'hB2); take(1'b1);
         ck_int("T11 and 0x0033", a2_tx, 8'hB3); take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T12. Counters. Reads and writes are counted separately, because a random
         //      read's write phase writes nothing and must not inflate the count.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h05);
         send_data(8'hC0); send_data(8'hC1);
         ev_stop;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h05);     // pointer only
         ev_start;
         send_addr(1'b1);
         take(1'b1); take(1'b0);
         ev_stop;
         $display("T12 a random read's write phase writes nothing");
         ck_int("T12 two bytes written in total", a2_wr, 2);
         ck_int("T12 two bytes read", a2_rd, 2);

         // ----------------------------------------------------------------
         // T13. THE HIGH ADDRESS BYTE, ON A PART THAT HAS SOMEWHERE TO PUT IT. On the
         //      256-byte part above, every bit the high byte carries is masked away
         //      again -- the device owns eight address bits and the protocol handed it
         //      sixteen. So nothing there can tell a device that assembles the pointer
         //      correctly from one that throws the high byte away.
         //
         //      On the 1024-word part it matters: word address 0x0210 has to land at
         //      0x0210, not at 0x0010.
         // ----------------------------------------------------------------
         do_reset;
         ev_start; send_addr(1'b0);
         send_data(8'h02);                       // high byte
         ck_int("T13 the high byte is in place and masked to the array", ab_wa, 16'h0200);
         send_data(8'h10);                       // low byte completes it
         $display("T13 the high address byte, on a part with somewhere to put it");
         ck_int("T13 the assembled pointer keeps both halves", ab_wa, 16'h0210);
         // The SAME two bytes, at the same instant, on the 256-byte part: the high
         // half is gone, because that part has no bits to hold it. One master, one
         // byte stream, two different locations -- decided entirely by the part.
         ck_int("T13 the 256-byte part holds only the low half", a2_wa, 16'h0010);
         send_data(8'h7E);                       // and a data byte lands there
         ev_stop;
         // Read it back with a random read, to prove the byte went where the pointer said.
         ev_start; send_addr(1'b0); send_data(8'h02); send_data(8'h10);
         ev_start; send_addr(1'b1);
         ck_int("T13 and the data byte went to that address", ab_tx, 8'h7E);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T14. AN ADDRESS BEYOND THE END OF THE ARRAY. A master that believes the
         //      part is larger than it is sends a pointer the device cannot hold. The
         //      excess bits are not refused and not reported -- the wires for them do
         //      not exist -- so the access silently WRAPS to somewhere inside the
         //      array. This is the mechanism behind "my write went to the wrong
         //      place", and it is also why the counter must be masked rather than
         //      merely assembled.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0000, 8'hA5);
         preload(16'h0034, 8'h5A);
         // 0x0834 on a 1024-word part: the top two bits are gone, leaving 0x0034.
         ev_start; send_addr(1'b0); send_data(8'h08); send_data(8'h34);
         $display("T14 an address past the end of the array wraps into it");
         ck_int("T14 0x0834 wrapped to 0x0034 on the 1024-word part", ab_wa, 16'h0034);
         ev_start; send_addr(1'b1);
         ck_int("T14 and reads the byte that lives there", ab_tx, 8'h5A);
         take(1'b0); ev_stop;
         // A truncated word address: the master sent the high byte and then stopped.
         // The counter must still hold a value INSIDE the array, or the next read is
         // an out-of-range access rather than a wrong one.
         do_reset;
         preload(16'h0000, 8'hA5);
         ev_start; send_addr(1'b0); send_data(8'h08); ev_stop;
         ck_int("T14 a truncated pointer is still inside the array", ab_wa, 16'h0000);
         ev_start; send_addr(1'b1);
         ck_int("T14 so the read that follows is wrong, not undefined", ab_tx, 8'hA5);
         take(1'b0); ev_stop;
         ck_int("T14 the 256-byte part did the same", a2_wa, 16'h0001);

         // ----------------------------------------------------------------
         // T15. WHAT was_random_read ACTUALLY MEANS. The device cannot tell a current
         //      address read from the read half of a random read by looking at the
         //      read -- they are byte-for-byte identical on the bus. The only
         //      difference is whether a pointer write came first, and the flag records
         //      exactly that and nothing more. A flag that were set on every read, or
         //      that survived into the next transaction, would say nothing.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0005, 8'h77);
         // A bare current-address read. No pointer write preceded it.
         ev_start; send_addr(1'b1);
         $display("T15 the flag records that a pointer write came first, nothing more");
         ck_bit("T15 a current-address read is not a random read", a2_rand, 1'b0);
         take(1'b0); ev_stop;
         // Now a real random read.
         ev_start; send_addr(1'b0); send_data(8'h00); send_data(8'h05);
         ck_bit("T15 a completed pointer write makes the next read random", a2_rand, 1'b1);
         ev_start; send_addr(1'b1);
         ck_int("T15 and it reads the addressed byte", a2_tx, 8'h77);
         take(1'b0); ev_stop;
         ck_bit("T15 the flag is still set after the read", a2_rand, 1'b1);
         // A new write phase must clear it BEFORE the new pointer is complete --
         // otherwise the flag is describing the previous transaction.
         ev_start; send_addr(1'b0);
         send_data(8'h00);                       // high byte only: pointer incomplete
         ck_bit("T15 a new write phase clears it at once", a2_rand, 1'b0);
         ck_int("T15 with the pointer still half-collected", a2_got, 2'd1);
         send_data(8'h05);                       // low byte completes it
         ck_bit("T15 and it is set again once the pointer is complete", a2_rand, 1'b1);
         ev_stop;

         if (errors == 0)
            $display("=== i2c_eeprom_addressing: ALL CHECKS PASSED ===");
         else
            $display("=== i2c_eeprom_addressing: %0d CHECK(S) FAILED ===", errors);
         $finish;
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_addressing.v — the same addressing engine in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_eeprom_addressing.sv
   // EEPROM word-address handling: multi-byte pointers, the three read forms, and
   // the rollover at the end of the array (UM10204 3.1.10 notes 1 and 2).
   //
   // Chapter 16.1 established the register-pointer model with a ONE-byte pointer,
   // which is all an eight-register part needs. An EEPROM has more locations than a
   // byte can name, and the specification's note 1 says only that "the internal
   // memory location must be written during the first data byte" -- it does not say
   // how many bytes the location takes.
   //
   // So a 64 kbit EEPROM sends a SIXTEEN-bit word address as two bytes, high byte
   // first, and the master must know that from the datasheet. There is nothing on the
   // bus that distinguishes a two-byte word address followed by data from a one-byte
   // word address followed by data: both are "address byte, then some data bytes".
   // Getting the width wrong writes the low half of the pointer into location zero.
   //
   // Three read forms, and the difference between them is what the master omits:
   //
   //   CURRENT ADDRESS READ   S, addr+R, data...
   //       No word address at all. Serves from wherever the internal counter is,
   //       which is only meaningful because the counter persists -- Chapter 16.1's
   //       question 3. A read whose result depends on transaction history.
   //
   //   RANDOM READ            S, addr+W, hi, lo, Sr, addr+R, data...
   //       A dummy write that sets the counter, then a repeated START and a read.
   //       This is UM10204's combined format, and the write phase carries no data --
   //       its entire purpose is the side effect on the pointer.
   //
   //   SEQUENTIAL READ        either of the above, with the master ACKing to continue
   //       Auto-increment across the whole array, not just within a page. Reads do
   //       not have the page-boundary behaviour that writes do -- Chapter 16.3 is
   //       about why those two differ.
   //
   // The rollover is a device convention: at the top of the array the counter returns
   // to zero, so a sequential read that runs off the end silently starts again at the
   // beginning rather than failing.
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_eeprom_addressing #(
      parameter ADDR_BYTES = 2,       // word-address width in bytes
      parameter N_WORDS    = 256,     // array size, for the rollover
      parameter AW         = 16,      // word-address register width
      parameter [6:0]   MY_ADDR    = 7'h50,
      parameter CNT_W      = 8
   ) (
      input  wire              clk,
      input  wire              rst_n,

      input  wire              start_seen,
      input  wire              stop_seen,
      input  wire              byte_valid,
      input  wire [7:0]        byte_in,
      input  wire              is_addr_byte,
      input  wire              read_byte_done,
      input  wire              master_acked,

      // A tiny backing array, written by the bench so reads have something to return.
      input  wire              load_en,
      input  wire [AW-1:0]     load_addr,
      input  wire [7:0]        load_data,

      output reg               ack,
      output reg  [7:0]        tx_byte,
      output reg               tx_valid,
      output reg  [AW-1:0]     word_addr,      // the internal address counter
      output reg  [1:0]        addr_bytes_got, // how many pointer bytes have arrived
      output reg               rolled_over,    // the counter wrapped at the array end
      output reg               was_random_read,// this read was preceded by a pointer write
      output reg  [2:0]        state,
      output reg  [CNT_W-1:0]  bytes_read,
      output reg  [CNT_W-1:0]  bytes_written
   );

      localparam [2:0] S_IDLE  = 3'd0,
                       S_ADDRH = 3'd1,  // collecting word-address bytes
                       S_DATA  = 3'd2,  // word address complete; data may follow
                       S_READ  = 3'd3;

      // Sized bounds. A part-select of a parameter reads as zero on some tools.
      localparam [AW-1:0] ADDR_TOP  = N_WORDS - 1;
      // The counter lives INSIDE the array. A 256-byte part has an 8-bit counter, so
      // a two-byte protocol pointer carries more bits than the device owns and the
      // excess wraps. Masking at assembly is what real hardware does -- the wires for
      // those bits are simply not there -- and it is also what stops an out-of-range
      // array index, which Verilog would read as X and VHDL would refuse outright.
      localparam [AW-1:0] ADDR_MASK = N_WORDS - 1;

      reg [7:0] mem [0:N_WORDS-1];
      integer   i;

      // Advance with the documented rollover: at the top of the array, back to zero.
      task advance_addr;
         begin
            if (word_addr == ADDR_TOP) begin
               word_addr   <= {AW{1'b0}};
               rolled_over <= 1'b1;
            end else begin
               word_addr <= word_addr + 1'b1;
            end
         end
      endtask

      always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
            state           <= S_IDLE;
            ack             <= 1'b0;
            tx_byte         <= 8'h00;
            tx_valid        <= 1'b0;
            word_addr       <= {AW{1'b0}};
            addr_bytes_got  <= 2'd0;
            rolled_over     <= 1'b0;
            was_random_read <= 1'b0;
            bytes_read      <= {CNT_W{1'b0}};
            bytes_written   <= {CNT_W{1'b0}};
            for (i = 0; i < N_WORDS; i = i + 1) mem[i] <= 8'h00;
         end else begin
            ack <= 1'b0;

            // The bench's back door, so a read has something to return.
            if (load_en) mem[load_addr] <= load_data;

            if (start_seen) begin
               // Note 4 again: any START returns the device to expecting an address.
               // Crucially the word-address COUNTER survives -- that is what makes
               // the random read work, because the pointer set in the write phase
               // must still be there after the repeated START.
               state          <= S_IDLE;
               tx_valid       <= 1'b0;
               addr_bytes_got <= 2'd0;

            end else if (stop_seen) begin
               state          <= S_IDLE;
               tx_valid       <= 1'b0;
               addr_bytes_got <= 2'd0;

            end else if (byte_valid) begin
               case (state)

                  S_IDLE: begin
                     if (is_addr_byte && (byte_in[7:1] == MY_ADDR)) begin
                        ack <= 1'b1;
                        if (byte_in[0]) begin
                           // A read. Whether this is a CURRENT ADDRESS read or the
                           // second half of a RANDOM read depends only on whether a
                           // pointer write preceded it -- and the device cannot tell
                           // the difference except by having seen it.
                           tx_byte  <= mem[word_addr];
                           tx_valid <= 1'b1;
                           state    <= S_READ;
                        end else begin
                           // A write: the word address comes next, ADDR_BYTES of it.
                           addr_bytes_got  <= 2'd0;
                           was_random_read <= 1'b0;
                           state           <= S_ADDRH;
                        end
                     end
                  end

                  // Collect the word address, high byte first. This is the state a
                  // master with the wrong ADDR_BYTES gets wrong: it sends one byte
                  // and starts writing data, and the data's first byte is consumed
                  // as the low half of the pointer.
                  S_ADDRH: begin
                     ack <= 1'b1;
                     if (ADDR_BYTES == 1) begin
                        word_addr       <= {{(AW-8){1'b0}}, byte_in} & ADDR_MASK;
                        addr_bytes_got  <= 2'd1;
                        was_random_read <= 1'b1;
                        state           <= S_DATA;
                     end else begin
                        if (addr_bytes_got == 2'd0) begin
                           // High byte, shifted into place and masked to the array.
                           word_addr      <= {byte_in, {(AW-8){1'b0}}} & ADDR_MASK;
                           addr_bytes_got <= 2'd1;
                        end else begin
                           // Low byte completes the pointer, masked the same way.
                           word_addr       <= {word_addr[AW-1:8], byte_in} & ADDR_MASK;
                           addr_bytes_got  <= 2'd2;
                           was_random_read <= 1'b1;
                           state           <= S_DATA;
                        end
                     end
                  end

                  // Data bytes, if any. A random read sends none -- the write phase
                  // exists only for its effect on the pointer.
                  S_DATA: begin
                     ack           <= 1'b1;
                     mem[word_addr] <= byte_in;
                     bytes_written <= bytes_written + 1'b1;
                     advance_addr;
                  end

                  default: ;
               endcase

            end else if (read_byte_done && state == S_READ) begin
               bytes_read <= bytes_read + 1'b1;
               advance_addr;
               if (!master_acked) begin
                  tx_valid <= 1'b0;
                  state    <= S_IDLE;
               end else begin
                  // Sequential read: the next byte comes from the advanced counter,
                  // computed here so it is ready for the next transfer.
                  if (word_addr == ADDR_TOP) tx_byte <= mem[0];
                  else                       tx_byte <= mem[word_addr + 1'b1];
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_addressing_tb.v — the same oracle in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_eeprom_addressing_tb.sv
   // Independent oracle for i2c_eeprom_addressing.
   //
   // Two instances with DIFFERENT word-address widths see the same byte stream,
   // because the whole point of the chapter is that the bus cannot tell them apart.
   // Test 6 drives a two-byte sequence into both and shows the one-byte instance
   // consuming the data as the low half of its pointer -- a corruption with no
   // protocol error anywhere.
   //
   //   dut2 : ADDR_BYTES = 2   a 64 kbit-class part
   //   dut1 : ADDR_BYTES = 1   a 2 kbit-class part
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_eeprom_addressing_tb;

      localparam [2:0] S_IDLE = 3'd0, S_ADDRH = 3'd1, S_DATA = 3'd2, S_READ = 3'd3;
      localparam [6:0] ADDR = 7'h50;
      localparam integer NW = 256;
      localparam integer AW = 16;
      // A two-byte word address on a 256-byte part is very nearly a contradiction:
      // every bit the high byte carries is masked away again, because the device only
      // owns eight address bits. So the bench also instantiates a part where the high
      // byte genuinely survives -- 1024 words, ten address bits -- which is the only
      // configuration in which the high half of the pointer can be shown to work.
      localparam integer NWB = 1024;

      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;
      reg        read_byte_done = 1'b0;
      reg        master_acked = 1'b0;
      reg        load_en = 1'b0;
      reg [AW-1:0] load_addr = 0;
      reg [7:0]  load_data = 8'h00;

      wire        a2_ack, a2_txv, a2_roll, a2_rand;
      wire [7:0]  a2_tx;
      wire [AW-1:0] a2_wa;
      wire [1:0]  a2_got;
      wire [2:0]  a2_state;
      wire [7:0]  a2_rd, a2_wr;

      wire        a1_ack, a1_txv, a1_roll, a1_rand;
      wire [7:0]  a1_tx;
      wire [AW-1:0] a1_wa;
      wire [1:0]  a1_got;
      wire [2:0]  a1_state;
      wire [7:0]  a1_rd, a1_wr;

      wire        ab_ack, ab_txv, ab_roll, ab_rand;
      wire [7:0]  ab_tx;
      wire [AW-1:0] ab_wa;
      wire [1:0]  ab_got;
      wire [2:0]  ab_state;
      wire [7:0]  ab_rd, ab_wr;

      integer errors = 0;
      integer n;

      i2c_eeprom_addressing #(.ADDR_BYTES(2), .N_WORDS(NW), .AW(AW),
                              .MY_ADDR(ADDR), .CNT_W(8)) dut2 (
         .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),
         .read_byte_done(read_byte_done), .master_acked(master_acked),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .ack(a2_ack), .tx_byte(a2_tx), .tx_valid(a2_txv), .word_addr(a2_wa),
         .addr_bytes_got(a2_got), .rolled_over(a2_roll),
         .was_random_read(a2_rand), .state(a2_state),
         .bytes_read(a2_rd), .bytes_written(a2_wr));

      i2c_eeprom_addressing #(.ADDR_BYTES(1), .N_WORDS(NW), .AW(AW),
                              .MY_ADDR(ADDR), .CNT_W(8)) dut1 (
         .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),
         .read_byte_done(read_byte_done), .master_acked(master_acked),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .ack(a1_ack), .tx_byte(a1_tx), .tx_valid(a1_txv), .word_addr(a1_wa),
         .addr_bytes_got(a1_got), .rolled_over(a1_roll),
         .was_random_read(a1_rand), .state(a1_state),
         .bytes_read(a1_rd), .bytes_written(a1_wr));

      // The 1024-word part: two address bytes, and ten address bits to put them in.
      i2c_eeprom_addressing #(.ADDR_BYTES(2), .N_WORDS(NWB), .AW(AW),
                              .MY_ADDR(ADDR), .CNT_W(8)) dutB (
         .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),
         .read_byte_done(read_byte_done), .master_acked(master_acked),
         .load_en(load_en), .load_addr(load_addr), .load_data(load_data),
         .ack(ab_ack), .tx_byte(ab_tx), .tx_valid(ab_txv), .word_addr(ab_wa),
         .addr_bytes_got(ab_got), .rolled_over(ab_roll),
         .was_random_read(ab_rand), .state(ab_state),
         .bytes_read(ab_rd), .bytes_written(ab_wr));

      always #5 clk = ~clk;

      task step; begin @(posedge clk); @(negedge clk); end endtask

      task do_reset;
         begin
            @(negedge clk);
            rst_n = 1'b0; start_seen = 1'b0; stop_seen = 1'b0; byte_valid = 1'b0;
            is_addr_byte = 1'b0; read_byte_done = 1'b0; master_acked = 1'b0;
            load_en = 1'b0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            step;
         end
      endtask

      task ev_start; begin @(negedge clk); start_seen = 1'b1; @(posedge clk); @(negedge clk); start_seen = 1'b0; end endtask
      task ev_stop;  begin @(negedge clk); stop_seen  = 1'b1; @(posedge clk); @(negedge clk); stop_seen  = 1'b0; end endtask

      task send_addr (input rw);
         begin
            @(negedge clk); byte_in = {ADDR, rw}; 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 take (input do_ack);
         begin
            @(negedge clk); read_byte_done = 1'b1; master_acked = do_ack;
            @(posedge clk); @(negedge clk); read_byte_done = 1'b0;
         end
      endtask

      // Preload both instances' arrays through the back door.
      task preload (input integer a, input [7:0] d);
         begin
            @(negedge clk); load_en = 1'b1; load_addr = a[AW-1:0]; load_data = d;
            @(posedge clk); @(negedge clk); load_en = 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 (0x%0h) expected %0d (0x%0h)", what, got, got, exp, 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_eeprom_addressing: three read forms and a pointer the bus cannot size ===");

         // ----------------------------------------------------------------
         // T1. A two-byte word address is assembled high byte first.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         ck_int("T1 collecting the word address", a2_state, S_ADDRH);
         send_data(8'h00);                       // high byte
         ck_int("T1 one pointer byte so far", a2_got, 1);
         send_data(8'h2A);                       // low byte
         $display("T1  a two-byte word address, high byte first");
         ck_int("T1 pointer assembled", a2_wa, 16'h002A);
         ck_int("T1 two pointer bytes", a2_got, 2);
         ck_int("T1 now accepting data", a2_state, S_DATA);
         ev_stop;

         // ----------------------------------------------------------------
         // T2. A WRITE: pointer then data, with the counter advancing.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h10);     // word address 0x0010
         send_data(8'hA0); send_data(8'hA1); send_data(8'hA2);
         $display("T2  a write: pointer, then data, counter advancing");
         ck_int("T2 three bytes written", a2_wr, 3);
         ck_int("T2 counter advanced past them", a2_wa, 16'h0013);
         ev_stop;
         // read them back with a random read
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h10);
         ev_start;
         send_addr(1'b1);
         ck_int("T2 read back 0xA0", a2_tx, 8'hA0); take(1'b1);
         ck_int("T2 read back 0xA1", a2_tx, 8'hA1); take(1'b1);
         ck_int("T2 read back 0xA2", a2_tx, 8'hA2); take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T3. RANDOM READ. The write phase carries NO data -- its whole purpose is
         //     the side effect on the pointer, and then a repeated START turns the
         //     transfer around. This is UM10204's combined format.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0080, 8'h5A);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h80);     // pointer only, no data
         ck_int("T3 pointer set, no data written", a2_wr, 0);
         ck_bit("T3 marked as a pointer write", a2_rand, 1'b1);
         ev_start;                               // repeated START
         send_addr(1'b1);
         $display("T3  a random read: a dummy write for its side effect, then Sr");
         ck_bit("T3 serving", a2_txv, 1'b1);
         ck_int("T3 serves the addressed location", a2_tx, 8'h5A);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T4. CURRENT ADDRESS READ. No word address at all. It works only because
         //     the counter survived the STOP, so the result depends on history.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0040, 8'h11);
         preload(16'h0041, 8'h22);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h40);
         ev_stop;                                // pointer left at 0x0040
         ck_int("T4 counter survived the STOP", a2_wa, 16'h0040);
         ev_start;
         send_addr(1'b1);                        // a bare read, no pointer
         $display("T4  a current address read: no pointer, so history decides");
         ck_int("T4 serves 0x0040", a2_tx, 8'h11);
         take(1'b1);
         ck_int("T4 then 0x0041", a2_tx, 8'h22);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T5. SEQUENTIAL READ across many locations. Reads auto-increment through
         //     the whole array -- there is no page boundary on a read.
         // ----------------------------------------------------------------
         do_reset;
         for (n = 0; n < 20; n = n + 1) preload(16'h0000 + n[AW-1:0], 8'h70 + n[7:0]);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h00);
         ev_start;
         send_addr(1'b1);
         $display("T5  a sequential read runs across the array, not a page");
         for (n = 0; n < 20; n = n + 1) begin
            ck_int("T5 sequential byte", a2_tx, 8'h70 + n[7:0]);
            take(n < 19);
         end
         ck_int("T5 twenty bytes served", a2_rd, 20);
         ev_stop;

         // ----------------------------------------------------------------
         // T6. THE CHAPTER'S CENTRAL HAZARD. The same two-byte pointer sequence
         //     driven into a ONE-byte device. Nothing on the bus is wrong; every
         //     byte is acknowledged; and the second pointer byte is consumed as
         //     DATA at the location the first byte named.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         ck_bit("T6 both devices acknowledged", a2_ack, 1'b1);
         ck_bit("T6 both devices acknowledged (1-byte)", a1_ack, 1'b1);
         send_data(8'h01);                       // intended as the HIGH byte
         ck_int("T6 two-byte device: half a pointer", a2_got, 1);
         ck_int("T6 one-byte device: a COMPLETE pointer", a1_got, 1);
         ck_int("T6 one-byte device points at 0x0001", a1_wa, 16'h0001);
         ck_int("T6 one-byte device now expects data", a1_state, S_DATA);
         send_data(8'h20);                       // intended as the LOW byte
         $display("T6  a two-byte pointer sent to a one-byte device");
         // 0x0120 is beyond a 256-byte array, so the counter -- which is only as wide
         // as the array -- holds 0x20. A pointer wider than the device wraps into it.
         ck_int("T6 two-byte pointer 0x0120 wraps to 0x0020", a2_wa, 16'h0020);
         ck_bit("T6 one-byte device acknowledged it as data", a1_ack, 1'b1);
         ck_int("T6 one-byte device WROTE the low byte as data", a1_wr, 1);
         ev_stop;
         // and the corruption is at location 1, which nothing on the bus reported
         ev_start;
         send_addr(1'b0);
         send_data(8'h01);
         ev_start;
         send_addr(1'b1);
         ck_int("T6 location 0x0001 holds the stray pointer byte", a1_tx, 8'h20);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T7. THE ROLLOVER. A sequential read from the last location returns to
         //     zero rather than failing, so a read that runs off the end silently
         //     starts again.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h00FF, 8'hEE);
         preload(16'h0000, 8'h01);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'hFF);     // the last location
         ev_start;
         send_addr(1'b1);
         $display("T7  a sequential read rolls over instead of ending");
         ck_int("T7 the last location", a2_tx, 8'hEE);
         take(1'b1);
         ck_int("T7 back at location zero", a2_tx, 8'h01);
         ck_bit("T7 rollover reported", a2_roll, 1'b1);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T8. A write also rolls over at the array end. Chapter 16.3 shows that a
         //     write ALSO wraps at a page boundary, which is a different and much
         //     smaller modulus -- two wraps, two causes.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'hFE);
         send_data(8'hD0); send_data(8'hD1); send_data(8'hD2);
         $display("T8  a write rolls over at the end of the array too");
         ck_bit("T8 rollover reported", a2_roll, 1'b1);
         ck_int("T8 counter is back near zero", a2_wa, 16'h0001);
         ev_stop;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h00);
         ev_start;
         send_addr(1'b1);
         ck_int("T8 location zero got the third byte", a2_tx, 8'hD2);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T9. A repeated START abandons a partial word address. Half a pointer is
         //     not a pointer, and the counter must not be left half-updated for the
         //     read that follows.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0000, 8'h99);
         ev_start;
         send_addr(1'b0);
         send_data(8'h07);                       // high byte only
         ck_int("T9 half a pointer collected", a2_got, 1);
         ev_start;                               // abandon it
         $display("T9  a repeated START abandons a partial word address");
         ck_int("T9 pointer-byte count cleared", a2_got, 0);
         ck_int("T9 back to expecting an address", a2_state, S_IDLE);
         send_addr(1'b1);
         // the counter still holds whatever the partial write left, which is exactly
         // why a master must never rely on an abandoned pointer
         ck_bit("T9 serving something", a2_txv, 1'b1);
         take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T10. A wrong device address is ignored by both instances.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); byte_in = {7'h51, 1'b0}; is_addr_byte = 1'b1; byte_valid = 1'b1;
         @(posedge clk); @(negedge clk); byte_valid = 1'b0; is_addr_byte = 1'b0;
         $display("T10 another device's address is ignored");
         ck_bit("T10 two-byte instance silent", a2_ack, 1'b0);
         ck_bit("T10 one-byte instance silent", a1_ack, 1'b0);
         ck_int("T10 both idle", a2_state, S_IDLE);

         // ----------------------------------------------------------------
         // T11. A random read followed by a sequential read with no new pointer:
         //      the second read continues from where the first stopped.
         // ----------------------------------------------------------------
         do_reset;
         for (n = 0; n < 8; n = n + 1) preload(16'h0030 + n[AW-1:0], 8'hB0 + n[7:0]);
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h30);
         ev_start;
         send_addr(1'b1);
         ck_int("T11 first read", a2_tx, 8'hB0); take(1'b1);
         ck_int("T11 second read", a2_tx, 8'hB1); take(1'b0);
         ev_stop;
         $display("T11 a later bare read continues where the last one stopped");
         ev_start;
         send_addr(1'b1);                        // bare read, no pointer
         ck_int("T11 continues at 0x0032", a2_tx, 8'hB2); take(1'b1);
         ck_int("T11 and 0x0033", a2_tx, 8'hB3); take(1'b0);
         ev_stop;

         // ----------------------------------------------------------------
         // T12. Counters. Reads and writes are counted separately, because a random
         //      read's write phase writes nothing and must not inflate the count.
         // ----------------------------------------------------------------
         do_reset;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h05);
         send_data(8'hC0); send_data(8'hC1);
         ev_stop;
         ev_start;
         send_addr(1'b0);
         send_data(8'h00); send_data(8'h05);     // pointer only
         ev_start;
         send_addr(1'b1);
         take(1'b1); take(1'b0);
         ev_stop;
         $display("T12 a random read's write phase writes nothing");
         ck_int("T12 two bytes written in total", a2_wr, 2);
         ck_int("T12 two bytes read", a2_rd, 2);

         // ----------------------------------------------------------------
         // T13. THE HIGH ADDRESS BYTE, ON A PART THAT HAS SOMEWHERE TO PUT IT. On the
         //      256-byte part above, every bit the high byte carries is masked away
         //      again -- the device owns eight address bits and the protocol handed it
         //      sixteen. So nothing there can tell a device that assembles the pointer
         //      correctly from one that throws the high byte away.
         //
         //      On the 1024-word part it matters: word address 0x0210 has to land at
         //      0x0210, not at 0x0010.
         // ----------------------------------------------------------------
         do_reset;
         ev_start; send_addr(1'b0);
         send_data(8'h02);                       // high byte
         ck_int("T13 the high byte is in place and masked to the array", ab_wa, 16'h0200);
         send_data(8'h10);                       // low byte completes it
         $display("T13 the high address byte, on a part with somewhere to put it");
         ck_int("T13 the assembled pointer keeps both halves", ab_wa, 16'h0210);
         // The SAME two bytes, at the same instant, on the 256-byte part: the high
         // half is gone, because that part has no bits to hold it. One master, one
         // byte stream, two different locations -- decided entirely by the part.
         ck_int("T13 the 256-byte part holds only the low half", a2_wa, 16'h0010);
         send_data(8'h7E);                       // and a data byte lands there
         ev_stop;
         // Read it back with a random read, to prove the byte went where the pointer said.
         ev_start; send_addr(1'b0); send_data(8'h02); send_data(8'h10);
         ev_start; send_addr(1'b1);
         ck_int("T13 and the data byte went to that address", ab_tx, 8'h7E);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T14. AN ADDRESS BEYOND THE END OF THE ARRAY. A master that believes the
         //      part is larger than it is sends a pointer the device cannot hold. The
         //      excess bits are not refused and not reported -- the wires for them do
         //      not exist -- so the access silently WRAPS to somewhere inside the
         //      array. This is the mechanism behind "my write went to the wrong
         //      place", and it is also why the counter must be masked rather than
         //      merely assembled.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0000, 8'hA5);
         preload(16'h0034, 8'h5A);
         // 0x0834 on a 1024-word part: the top two bits are gone, leaving 0x0034.
         ev_start; send_addr(1'b0); send_data(8'h08); send_data(8'h34);
         $display("T14 an address past the end of the array wraps into it");
         ck_int("T14 0x0834 wrapped to 0x0034 on the 1024-word part", ab_wa, 16'h0034);
         ev_start; send_addr(1'b1);
         ck_int("T14 and reads the byte that lives there", ab_tx, 8'h5A);
         take(1'b0); ev_stop;
         // A truncated word address: the master sent the high byte and then stopped.
         // The counter must still hold a value INSIDE the array, or the next read is
         // an out-of-range access rather than a wrong one.
         do_reset;
         preload(16'h0000, 8'hA5);
         ev_start; send_addr(1'b0); send_data(8'h08); ev_stop;
         ck_int("T14 a truncated pointer is still inside the array", ab_wa, 16'h0000);
         ev_start; send_addr(1'b1);
         ck_int("T14 so the read that follows is wrong, not undefined", ab_tx, 8'hA5);
         take(1'b0); ev_stop;
         ck_int("T14 the 256-byte part did the same", a2_wa, 16'h0001);

         // ----------------------------------------------------------------
         // T15. WHAT was_random_read ACTUALLY MEANS. The device cannot tell a current
         //      address read from the read half of a random read by looking at the
         //      read -- they are byte-for-byte identical on the bus. The only
         //      difference is whether a pointer write came first, and the flag records
         //      exactly that and nothing more. A flag that were set on every read, or
         //      that survived into the next transaction, would say nothing.
         // ----------------------------------------------------------------
         do_reset;
         preload(16'h0005, 8'h77);
         // A bare current-address read. No pointer write preceded it.
         ev_start; send_addr(1'b1);
         $display("T15 the flag records that a pointer write came first, nothing more");
         ck_bit("T15 a current-address read is not a random read", a2_rand, 1'b0);
         take(1'b0); ev_stop;
         // Now a real random read.
         ev_start; send_addr(1'b0); send_data(8'h00); send_data(8'h05);
         ck_bit("T15 a completed pointer write makes the next read random", a2_rand, 1'b1);
         ev_start; send_addr(1'b1);
         ck_int("T15 and it reads the addressed byte", a2_tx, 8'h77);
         take(1'b0); ev_stop;
         ck_bit("T15 the flag is still set after the read", a2_rand, 1'b1);
         // A new write phase must clear it BEFORE the new pointer is complete --
         // otherwise the flag is describing the previous transaction.
         ev_start; send_addr(1'b0);
         send_data(8'h00);                       // high byte only: pointer incomplete
         ck_bit("T15 a new write phase clears it at once", a2_rand, 1'b0);
         ck_int("T15 with the pointer still half-collected", a2_got, 2'd1);
         send_data(8'h05);                       // low byte completes it
         ck_bit("T15 and it is set again once the pointer is complete", a2_rand, 1'b1);
         ev_stop;

         if (errors == 0)
            $display("=== i2c_eeprom_addressing: ALL CHECKS PASSED ===");
         else
            $display("=== i2c_eeprom_addressing: %0d CHECK(S) FAILED ===", errors);
         $finish;
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_addressing.vhd — the same addressing engine in VHDL-2008
   -- ---------------------------------------------------------------------------
   -- i2c_eeprom_addressing.vhd
   -- EEPROM word-address handling: multi-byte pointers, the three read forms, and
   -- the rollover at the end of the array (UM10204 3.1.10 notes 1 and 2).
   -- Behavioural twin of i2c_eeprom_addressing.sv / .v.
   --
   -- Note 1 says only that "the internal memory location must be written during the
   -- first data byte" -- it does not say how many bytes the location takes. A 64 kbit
   -- EEPROM sends a SIXTEEN-bit word address as two bytes, high byte first, and the
   -- master must know that from the datasheet. Nothing on the bus distinguishes a
   -- two-byte word address followed by data from a one-byte one followed by data.
   --
   -- Three read forms, differing only in what the master omits:
   --   CURRENT ADDRESS READ   S, addr+R, data...          (no word address at all)
   --   RANDOM READ            S, addr+W, hi, lo, Sr, addr+R, data...
   --   SEQUENTIAL READ        either of the above, ACKing to continue
   --
   -- The rollover is a device convention: at the top of the array the counter returns
   -- to zero, so a sequential read that runs off the end starts again silently.
   -- ---------------------------------------------------------------------------

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_eeprom_addressing is
      generic (
         ADDR_BYTES : integer := 2;                                    -- pointer width
         N_WORDS    : integer := 256;
         AW         : integer := 16;
         MY_ADDR    : std_logic_vector(6 downto 0) := "1010000";       -- 0x50
         CNT_W      : integer := 8
      );
      port (
         clk   : in std_logic;
         rst_n : in std_logic;

         start_seen     : in std_logic;
         stop_seen      : in std_logic;
         byte_valid     : in std_logic;
         byte_in        : in std_logic_vector(7 downto 0);
         is_addr_byte   : in std_logic;
         read_byte_done : in std_logic;
         master_acked   : in std_logic;

         -- A back door so reads have something to return.
         load_en   : in std_logic;
         load_addr : in unsigned(AW-1 downto 0);
         load_data : in std_logic_vector(7 downto 0);

         ack             : out std_logic;
         tx_byte         : out std_logic_vector(7 downto 0);
         tx_valid        : out std_logic;
         word_addr       : out unsigned(AW-1 downto 0);
         addr_bytes_got  : out unsigned(1 downto 0);
         rolled_over     : out std_logic;
         was_random_read : out std_logic;
         state           : out unsigned(2 downto 0);
         bytes_read      : out unsigned(CNT_W-1 downto 0);
         bytes_written   : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_eeprom_addressing;

   architecture rtl of i2c_eeprom_addressing is

      constant ST_IDLE  : integer := 0;
      constant ST_ADDRH : integer := 1;   -- collecting word-address bytes
      constant ST_DATA  : integer := 2;   -- word address complete; data may follow
      constant ST_READ  : integer := 3;

      constant ADDR_TOP : integer := N_WORDS - 1;
      -- The counter lives INSIDE the array. A 256-byte part has an 8-bit counter, so
      -- a two-byte protocol pointer carries more bits than the device owns and the
      -- excess wraps. Masking at assembly is what real hardware does, and it is also
      -- what keeps the array index in range -- which VHDL refuses outright and
      -- Verilog would quietly read as X.

      type mem_arr is array (0 to N_WORDS-1) of std_logic_vector(7 downto 0);

      signal mem   : mem_arr := (others => (others => '0'));
      signal st    : integer := ST_IDLE;
      signal wa    : integer := 0;
      signal got   : integer := 0;
      signal n_rd  : integer := 0;
      signal n_wr  : integer := 0;

   begin

      state          <= to_unsigned(st, 3);
      word_addr      <= to_unsigned(wa, AW);
      addr_bytes_got <= to_unsigned(got, 2);
      bytes_read     <= to_unsigned(n_rd, CNT_W);
      bytes_written  <= to_unsigned(n_wr, CNT_W);

      process (clk, rst_n)
         variable nxt : integer;
      begin
         if rst_n = '0' then
            st              <= ST_IDLE;
            ack             <= '0';
            tx_byte         <= (others => '0');
            tx_valid        <= '0';
            wa              <= 0;
            got             <= 0;
            rolled_over     <= '0';
            was_random_read <= '0';
            n_rd            <= 0;
            n_wr            <= 0;
            mem             <= (others => (others => '0'));

         elsif rising_edge(clk) then
            ack <= '0';

            if load_en = '1' then
               mem(to_integer(load_addr)) <= load_data;
            end if;

            if start_seen = '1' then
               -- Note 4: any START returns the device to expecting an address. The
               -- word-address COUNTER survives, which is what makes a random read
               -- work across the repeated START.
               st       <= ST_IDLE;
               tx_valid <= '0';
               got      <= 0;

            elsif stop_seen = '1' then
               st       <= ST_IDLE;
               tx_valid <= '0';
               got      <= 0;

            elsif byte_valid = '1' then
               case st is

                  when ST_IDLE =>
                     if is_addr_byte = '1' and byte_in(7 downto 1) = MY_ADDR then
                        ack <= '1';
                        if byte_in(0) = '1' then
                           -- Whether this is a CURRENT ADDRESS read or the second
                           -- half of a RANDOM read depends only on what preceded it.
                           tx_byte  <= mem(wa);
                           tx_valid <= '1';
                           st       <= ST_READ;
                        else
                           got             <= 0;
                           was_random_read <= '0';
                           st              <= ST_ADDRH;
                        end if;
                     end if;

                  -- Collect the word address, high byte first. A master with the
                  -- wrong ADDR_BYTES gets this wrong: it sends one byte and starts
                  -- writing data, and the data's first byte becomes the low pointer.
                  when ST_ADDRH =>
                     ack <= '1';
                     if ADDR_BYTES = 1 then
                        wa              <= to_integer(unsigned(byte_in)) mod N_WORDS;
                        got             <= 1;
                        was_random_read <= '1';
                        st              <= ST_DATA;
                     else
                        if got = 0 then
                           wa  <= (to_integer(unsigned(byte_in)) * 256) mod N_WORDS;
                           got <= 1;
                        else
                           wa              <= ((wa / 256) * 256
                                               + to_integer(unsigned(byte_in))) mod N_WORDS;
                           got             <= 2;
                           was_random_read <= '1';
                           st              <= ST_DATA;
                        end if;
                     end if;

                  -- Data bytes, if any. A random read sends none.
                  when ST_DATA =>
                     ack    <= '1';
                     mem(wa) <= byte_in;
                     n_wr   <= n_wr + 1;
                     if wa = ADDR_TOP then
                        wa          <= 0;
                        rolled_over <= '1';
                     else
                        wa <= wa + 1;
                     end if;

                  when others =>
                     null;

               end case;

            elsif read_byte_done = '1' and st = ST_READ then
               n_rd <= n_rd + 1;
               if wa = ADDR_TOP then
                  wa          <= 0;
                  rolled_over <= '1';
                  nxt         := 0;
               else
                  wa  <= wa + 1;
                  nxt := wa + 1;
               end if;
               if master_acked = '0' then
                  tx_valid <= '0';
                  st       <= ST_IDLE;
               else
                  tx_byte <= mem(nxt);
               end if;
            end if;
         end if;
      end process;

   end architecture rtl;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_addressing_tb.vhd — the same oracle in VHDL-2008
   -- ---------------------------------------------------------------------------
   -- i2c_eeprom_addressing_tb.vhd
   -- Independent oracle for i2c_eeprom_addressing. Behavioural twin of the
   -- SystemVerilog and Verilog benches.
   --
   -- Two instances with DIFFERENT word-address widths see the same byte stream,
   -- because the whole point of the chapter is that the bus cannot tell them apart.
   -- Test 6 drives a two-byte sequence into both and shows the one-byte instance
   -- consuming the data as the low half of its pointer -- a corruption with no
   -- protocol error anywhere.
   --
   --   dut2 : ADDR_BYTES = 2   a 64 kbit-class part
   --   dut1 : ADDR_BYTES = 1   a 2 kbit-class part
   -- ---------------------------------------------------------------------------

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   entity i2c_eeprom_addressing_tb is
   end entity i2c_eeprom_addressing_tb;

   architecture sim of i2c_eeprom_addressing_tb is

      constant TCLK : time := 10 ns;
      constant ADDR : std_logic_vector(6 downto 0) := "1010000";   -- 0x50
      constant NW   : integer := 256;
      constant AW   : integer := 16;
      -- A two-byte word address on a 256-byte part is very nearly a contradiction:
      -- every bit the high byte carries is masked away again, because the device only
      -- owns eight address bits. So the bench also instantiates a part where the high
      -- byte genuinely survives -- 1024 words, ten address bits -- which is the only
      -- configuration in which the high half of the pointer can be shown to work.
      constant NWB  : integer := 1024;

      constant ST_IDLE  : integer := 0;
      constant ST_ADDRH : integer := 1;
      constant ST_DATA  : integer := 2;
      constant ST_READ  : integer := 3;

      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';
      signal read_byte_done : std_logic := '0';
      signal master_acked   : std_logic := '0';
      signal load_en        : std_logic := '0';
      signal load_addr      : unsigned(AW-1 downto 0) := (others => '0');
      signal load_data      : std_logic_vector(7 downto 0) := (others => '0');

      signal a2_ack, a2_txv, a2_roll, a2_rand : std_logic;
      signal a2_tx    : std_logic_vector(7 downto 0);
      signal a2_wa    : unsigned(AW-1 downto 0);
      signal a2_got   : unsigned(1 downto 0);
      signal a2_state : unsigned(2 downto 0);
      signal a2_rd, a2_wr : unsigned(7 downto 0);

      signal a1_ack, a1_txv, a1_roll, a1_rand : std_logic;
      signal a1_tx    : std_logic_vector(7 downto 0);
      signal a1_wa    : unsigned(AW-1 downto 0);
      signal a1_got   : unsigned(1 downto 0);
      signal a1_state : unsigned(2 downto 0);
      signal a1_rd, a1_wr : unsigned(7 downto 0);

      signal ab_ack, ab_txv, ab_roll, ab_rand : std_logic;
      signal ab_tx    : std_logic_vector(7 downto 0);
      signal ab_wa    : unsigned(AW-1 downto 0);
      signal ab_got   : unsigned(1 downto 0);
      signal ab_state : unsigned(2 downto 0);
      signal ab_rd, ab_wr : unsigned(7 downto 0);

      signal halt : boolean := false;

   begin

      dut2 : entity work.i2c_eeprom_addressing
         generic map (ADDR_BYTES => 2, N_WORDS => NW, AW => AW,
                      MY_ADDR => ADDR, CNT_W => 8)
         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, read_byte_done => read_byte_done,
            master_acked => master_acked, load_en => load_en,
            load_addr => load_addr, load_data => load_data,
            ack => a2_ack, tx_byte => a2_tx, tx_valid => a2_txv, word_addr => a2_wa,
            addr_bytes_got => a2_got, rolled_over => a2_roll,
            was_random_read => a2_rand, state => a2_state,
            bytes_read => a2_rd, bytes_written => a2_wr);

      dut1 : entity work.i2c_eeprom_addressing
         generic map (ADDR_BYTES => 1, N_WORDS => NW, AW => AW,
                      MY_ADDR => ADDR, CNT_W => 8)
         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, read_byte_done => read_byte_done,
            master_acked => master_acked, load_en => load_en,
            load_addr => load_addr, load_data => load_data,
            ack => a1_ack, tx_byte => a1_tx, tx_valid => a1_txv, word_addr => a1_wa,
            addr_bytes_got => a1_got, rolled_over => a1_roll,
            was_random_read => a1_rand, state => a1_state,
            bytes_read => a1_rd, bytes_written => a1_wr);

      -- The 1024-word part: two address bytes, and ten address bits to put them in.
      dutB : entity work.i2c_eeprom_addressing
         generic map (ADDR_BYTES => 2, N_WORDS => NWB, AW => AW,
                      MY_ADDR => ADDR, CNT_W => 8)
         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, read_byte_done => read_byte_done,
            master_acked => master_acked, load_en => load_en,
            load_addr => load_addr, load_data => load_data,
            ack => ab_ack, tx_byte => ab_tx, tx_valid => ab_txv, word_addr => ab_wa,
            addr_bytes_got => ab_got, rolled_over => ab_roll,
            was_random_read => ab_rand, state => ab_state,
            bytes_read => ab_rd, bytes_written => ab_wr);

      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 is
         begin
            wait until falling_edge(clk);
            rst_n <= '0'; start_seen <= '0'; stop_seen <= '0'; byte_valid <= '0';
            is_addr_byte <= '0'; read_byte_done <= '0'; master_acked <= '0';
            load_en <= '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 ev_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 ev_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 (rw : std_logic) is
         begin
            wait until falling_edge(clk);
            byte_in <= ADDR & rw; 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;

         procedure take (do_ack : std_logic) is
         begin
            wait until falling_edge(clk);
            read_byte_done <= '1'; master_acked <= do_ack;
            wait until rising_edge(clk); wait until falling_edge(clk);
            read_byte_done <= '0';
         end procedure;

         procedure preload (a : integer; d : integer) is
         begin
            wait until falling_edge(clk);
            load_en <= '1'; load_addr <= to_unsigned(a, AW);
            load_data <= std_logic_vector(to_unsigned(d, 8));
            wait until rising_edge(clk); wait until falling_edge(clk);
            load_en <= '0';
         end procedure;

      begin
         report "=== i2c_eeprom_addressing: three read forms and a pointer the bus cannot size ==="
                severity note;

         -- T1. A two-byte word address is assembled high byte first.
         do_reset;
         ev_start;
         send_addr('0');
         ck_int("T1 collecting the word address", to_integer(a2_state), ST_ADDRH);
         send_data(x"00");
         ck_int("T1 one pointer byte so far", to_integer(a2_got), 1);
         send_data(x"2A");
         report "T1  a two-byte word address, high byte first" severity note;
         ck_int("T1 pointer assembled", to_integer(a2_wa), 16#002A#);
         ck_int("T1 two pointer bytes", to_integer(a2_got), 2);
         ck_int("T1 now accepting data", to_integer(a2_state), ST_DATA);
         ev_stop;

         -- T2. A WRITE: pointer then data, with the counter advancing.
         do_reset;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"10");
         send_data(x"A0"); send_data(x"A1"); send_data(x"A2");
         report "T2  a write: pointer, then data, counter advancing" severity note;
         ck_int("T2 three bytes written", to_integer(a2_wr), 3);
         ck_int("T2 counter advanced past them", to_integer(a2_wa), 16#0013#);
         ev_stop;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"10");
         ev_start;
         send_addr('1');
         ck_int("T2 read back 0xA0", to_integer(unsigned(a2_tx)), 16#A0#); take('1');
         ck_int("T2 read back 0xA1", to_integer(unsigned(a2_tx)), 16#A1#); take('1');
         ck_int("T2 read back 0xA2", to_integer(unsigned(a2_tx)), 16#A2#); take('0');
         ev_stop;

         -- T3. RANDOM READ: the write phase carries NO data.
         do_reset;
         preload(16#0080#, 16#5A#);
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"80");
         ck_int("T3 pointer set, no data written", to_integer(a2_wr), 0);
         ck_bit("T3 marked as a pointer write", a2_rand, '1');
         ev_start;
         send_addr('1');
         report "T3  a random read: a dummy write for its side effect, then Sr"
                severity note;
         ck_bit("T3 serving", a2_txv, '1');
         ck_int("T3 serves the addressed location", to_integer(unsigned(a2_tx)), 16#5A#);
         take('0');
         ev_stop;

         -- T4. CURRENT ADDRESS READ: no word address, so history decides.
         do_reset;
         preload(16#0040#, 16#11#);
         preload(16#0041#, 16#22#);
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"40");
         ev_stop;
         ck_int("T4 counter survived the STOP", to_integer(a2_wa), 16#0040#);
         ev_start;
         send_addr('1');
         report "T4  a current address read: no pointer, so history decides"
                severity note;
         ck_int("T4 serves 0x0040", to_integer(unsigned(a2_tx)), 16#11#);
         take('1');
         ck_int("T4 then 0x0041", to_integer(unsigned(a2_tx)), 16#22#);
         take('0');
         ev_stop;

         -- T5. SEQUENTIAL READ across many locations.
         do_reset;
         for k in 0 to 19 loop preload(k, 16#70# + k); end loop;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"00");
         ev_start;
         send_addr('1');
         report "T5  a sequential read runs across the array, not a page" severity note;
         for k in 0 to 19 loop
            ck_int("T5 sequential byte", to_integer(unsigned(a2_tx)), 16#70# + k);
            if k < 19 then take('1'); else take('0'); end if;
         end loop;
         ck_int("T5 twenty bytes served", to_integer(a2_rd), 20);
         ev_stop;

         -- T6. THE CENTRAL HAZARD: a two-byte pointer into a one-byte device.
         do_reset;
         ev_start;
         send_addr('0');
         ck_bit("T6 both devices acknowledged", a2_ack, '1');
         ck_bit("T6 both devices acknowledged (1-byte)", a1_ack, '1');
         send_data(x"01");
         ck_int("T6 two-byte device: half a pointer", to_integer(a2_got), 1);
         ck_int("T6 one-byte device: a COMPLETE pointer", to_integer(a1_got), 1);
         ck_int("T6 one-byte device points at 0x0001", to_integer(a1_wa), 16#0001#);
         ck_int("T6 one-byte device now expects data", to_integer(a1_state), ST_DATA);
         send_data(x"20");
         report "T6  a two-byte pointer sent to a one-byte device" severity note;
         -- 0x0120 is beyond a 256-byte array, so the counter -- only as wide as the
         -- array -- holds 0x20. A pointer wider than the device wraps into it.
         ck_int("T6 two-byte pointer 0x0120 wraps to 0x0020",
                to_integer(a2_wa), 16#0020#);
         ck_bit("T6 one-byte device acknowledged it as data", a1_ack, '1');
         ck_int("T6 one-byte device WROTE the low byte as data", to_integer(a1_wr), 1);
         ev_stop;
         ev_start;
         send_addr('0');
         send_data(x"01");
         ev_start;
         send_addr('1');
         ck_int("T6 location 0x0001 holds the stray pointer byte",
                to_integer(unsigned(a1_tx)), 16#20#);
         take('0');
         ev_stop;

         -- T7. THE ROLLOVER on a read.
         do_reset;
         preload(16#00FF#, 16#EE#);
         preload(16#0000#, 16#01#);
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"FF");
         ev_start;
         send_addr('1');
         report "T7  a sequential read rolls over instead of ending" severity note;
         ck_int("T7 the last location", to_integer(unsigned(a2_tx)), 16#EE#);
         take('1');
         ck_int("T7 back at location zero", to_integer(unsigned(a2_tx)), 16#01#);
         ck_bit("T7 rollover reported", a2_roll, '1');
         take('0');
         ev_stop;

         -- T8. A write rolls over at the array end too.
         do_reset;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"FE");
         send_data(x"D0"); send_data(x"D1"); send_data(x"D2");
         report "T8  a write rolls over at the end of the array too" severity note;
         ck_bit("T8 rollover reported", a2_roll, '1');
         ck_int("T8 counter is back near zero", to_integer(a2_wa), 16#0001#);
         ev_stop;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"00");
         ev_start;
         send_addr('1');
         ck_int("T8 location zero got the third byte",
                to_integer(unsigned(a2_tx)), 16#D2#);
         take('0');
         ev_stop;

         -- T9. A repeated START abandons a partial word address.
         do_reset;
         preload(16#0000#, 16#99#);
         ev_start;
         send_addr('0');
         send_data(x"07");
         ck_int("T9 half a pointer collected", to_integer(a2_got), 1);
         ev_start;
         report "T9  a repeated START abandons a partial word address" severity note;
         ck_int("T9 pointer-byte count cleared", to_integer(a2_got), 0);
         ck_int("T9 back to expecting an address", to_integer(a2_state), ST_IDLE);
         send_addr('1');
         ck_bit("T9 serving something", a2_txv, '1');
         take('0');
         ev_stop;

         -- T10. A wrong device address is ignored by both instances.
         do_reset;
         wait until falling_edge(clk);
         byte_in <= "10100010"; is_addr_byte <= '1'; byte_valid <= '1';
         wait until rising_edge(clk); wait until falling_edge(clk);
         byte_valid <= '0'; is_addr_byte <= '0';
         report "T10 another device's address is ignored" severity note;
         ck_bit("T10 two-byte instance silent", a2_ack, '0');
         ck_bit("T10 one-byte instance silent", a1_ack, '0');
         ck_int("T10 both idle", to_integer(a2_state), ST_IDLE);

         -- T11. A later bare read continues where the last one stopped.
         do_reset;
         for k in 0 to 7 loop preload(16#0030# + k, 16#B0# + k); end loop;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"30");
         ev_start;
         send_addr('1');
         ck_int("T11 first read", to_integer(unsigned(a2_tx)), 16#B0#); take('1');
         ck_int("T11 second read", to_integer(unsigned(a2_tx)), 16#B1#); take('0');
         ev_stop;
         report "T11 a later bare read continues where the last one stopped"
                severity note;
         ev_start;
         send_addr('1');
         ck_int("T11 continues at 0x0032", to_integer(unsigned(a2_tx)), 16#B2#); take('1');
         ck_int("T11 and 0x0033", to_integer(unsigned(a2_tx)), 16#B3#); take('0');
         ev_stop;

         -- T12. A random read's write phase writes nothing.
         do_reset;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"05");
         send_data(x"C0"); send_data(x"C1");
         ev_stop;
         ev_start;
         send_addr('0');
         send_data(x"00"); send_data(x"05");
         ev_start;
         send_addr('1');
         take('1'); take('0');
         ev_stop;
         report "T12 a random read's write phase writes nothing" severity note;
         ck_int("T12 two bytes written in total", to_integer(a2_wr), 2);
         ck_int("T12 two bytes read", to_integer(a2_rd), 2);

         -- T13. THE HIGH ADDRESS BYTE, ON A PART THAT HAS SOMEWHERE TO PUT IT. On the
         --      256-byte part above, every bit the high byte carries is masked away
         --      again -- the device owns eight address bits and the protocol handed it
         --      sixteen. So nothing there can tell a device that assembles the pointer
         --      correctly from one that throws the high byte away.
         --
         --      On the 1024-word part it matters: word address 0x0210 has to land at
         --      0x0210, not at 0x0010.
         do_reset;
         ev_start; send_addr('0');
         send_data(x"02");                        -- high byte
         ck_int("T13 the high byte is in place and masked to the array",
                to_integer(ab_wa), 16#0200#);
         send_data(x"10");                        -- low byte completes it
         report "T13 the high address byte, on a part with somewhere to put it"
                severity note;
         ck_int("T13 the assembled pointer keeps both halves",
                to_integer(ab_wa), 16#0210#);
         -- The SAME two bytes, at the same instant, on the 256-byte part: the high half
         -- is gone, because that part has no bits to hold it. One master, one byte
         -- stream, two different locations -- decided entirely by the part.
         ck_int("T13 the 256-byte part holds only the low half",
                to_integer(a2_wa), 16#0010#);
         send_data(x"7E");                        -- and a data byte lands there
         ev_stop;
         -- Read it back with a random read, to prove the byte went where the pointer said.
         ev_start; send_addr('0'); send_data(x"02"); send_data(x"10");
         ev_start; send_addr('1');
         ck_int("T13 and the data byte went to that address",
                to_integer(unsigned(ab_tx)), 16#7E#);
         take('0'); ev_stop;

         -- T14. AN ADDRESS BEYOND THE END OF THE ARRAY. A master that believes the part
         --      is larger than it is sends a pointer the device cannot hold. The excess
         --      bits are not refused and not reported -- the wires for them do not
         --      exist -- so the access silently WRAPS to somewhere inside the array.
         --      This is the mechanism behind "my write went to the wrong place", and it
         --      is also why the counter must be masked rather than merely assembled.
         do_reset;
         preload(16#0000#, 16#A5#);
         preload(16#0034#, 16#5A#);
         -- 0x0834 on a 1024-word part: the top two bits are gone, leaving 0x0034.
         ev_start; send_addr('0'); send_data(x"08"); send_data(x"34");
         report "T14 an address past the end of the array wraps into it" severity note;
         ck_int("T14 0x0834 wrapped to 0x0034 on the 1024-word part",
                to_integer(ab_wa), 16#0034#);
         ev_start; send_addr('1');
         ck_int("T14 and reads the byte that lives there",
                to_integer(unsigned(ab_tx)), 16#5A#);
         take('0'); ev_stop;
         -- A truncated word address: the master sent the high byte and then stopped.
         -- The counter must still hold a value INSIDE the array, or the next read is an
         -- out-of-range access rather than a wrong one.
         do_reset;
         preload(16#0000#, 16#A5#);
         ev_start; send_addr('0'); send_data(x"08"); ev_stop;
         ck_int("T14 a truncated pointer is still inside the array",
                to_integer(ab_wa), 16#0000#);
         ev_start; send_addr('1');
         ck_int("T14 so the read that follows is wrong, not undefined",
                to_integer(unsigned(ab_tx)), 16#A5#);
         take('0'); ev_stop;
         ck_int("T14 the 256-byte part did the same", to_integer(a2_wa), 16#0001#);

         -- T15. WHAT was_random_read ACTUALLY MEANS. The device cannot tell a current
         --      address read from the read half of a random read by looking at the read
         --      -- they are byte-for-byte identical on the bus. The only difference is
         --      whether a pointer write came first, and the flag records exactly that
         --      and nothing more. A flag that were set on every read, or that survived
         --      into the next transaction, would say nothing.
         do_reset;
         preload(16#0005#, 16#77#);
         -- A bare current-address read. No pointer write preceded it.
         ev_start; send_addr('1');
         report "T15 the flag records that a pointer write came first, nothing more"
                severity note;
         ck_bit("T15 a current-address read is not a random read", a2_rand, '0');
         take('0'); ev_stop;
         -- Now a real random read.
         ev_start; send_addr('0'); send_data(x"00"); send_data(x"05");
         ck_bit("T15 a completed pointer write makes the next read random",
                a2_rand, '1');
         ev_start; send_addr('1');
         ck_int("T15 and it reads the addressed byte",
                to_integer(unsigned(a2_tx)), 16#77#);
         take('0'); ev_stop;
         ck_bit("T15 the flag is still set after the read", a2_rand, '1');
         -- A new write phase must clear it BEFORE the new pointer is complete --
         -- otherwise the flag is describing the previous transaction.
         ev_start; send_addr('0');
         send_data(x"00");                        -- high byte only: pointer incomplete
         ck_bit("T15 a new write phase clears it at once", a2_rand, '0');
         ck_int("T15 with the pointer still half-collected", to_integer(a2_got), 1);
         send_data(x"05");                        -- low byte completes it
         ck_bit("T15 and it is set again once the pointer is complete", a2_rand, '1');
         ev_stop;

         if err = 0 then
            report "=== i2c_eeprom_addressing: ALL CHECKS PASSED ===" severity note;
         else
            report "=== i2c_eeprom_addressing: " & integer'image(err)
                   & " CHECK(S) FAILED ===" severity note;
         end if;
         halt <= true;
         wait;
      end process;

   end architecture sim;

6a. Decisions Worth Defending

The counter is masked to the array at assembly, not merely assembled. ADDR_MASK is N_WORDS - 1, applied at every point the pointer is written. This is what real hardware does — the wires for the excess bits are not there — and it is also what stops an out-of-range array index. §3 and the callout there; mutation B2-1 removes the mask and three checks fail.

The low address byte completes the pointer rather than replacing it. {word_addr[AW-1:8], byte_in} keeps the high half that arrived in the previous byte. Mutation B2-2 replaces the whole pointer with the low byte, and it is provably unobservable on a 256-byte part — the final mask discards the high half anyway — which is precisely why the 1024-word instance exists. §7.

The word-address counter survives a START; the frame state does not. The START branch resets state and addr_bytes_got and deliberately leaves word_addr alone, because the random read depends on the pointer set in the write phase still being there after the turnaround. Mutation B2-4 clears it and seventeen checks fail.

addr_bytes_got is an output, so a bench can see a half-collected pointer. It is how test 15 catches a flag that describes the previous transaction rather than this one, and it is how a debug capture distinguishes "the master sent one byte" from "the device wanted one byte".

was_random_read records history and nothing else. §1's whole point is that the device cannot tell the two reads apart by looking at the read, so the flag is set when a pointer write completes and cleared when a new write phase begins. Two mutations attack it from opposite sides — B2-7 sets it on a bare read, B2-8 leaves it set from a previous transaction — and each is killed by one check.

The rollover is reported as well as performed. On a power-of-two array the counter would wrap on its own; rolled_over exists so a bench can prove the array boundary was reached rather than inferring it. Mutation B2-5 removes the wrap branch entirely and four checks fail; B2-6 wraps silently and two fail.

The next sequential byte is prefetched from the advanced counter, honouring the rollover. if (word_addr == ADDR_TOP) tx_byte <= mem[0] rather than indexing word_addr + 1, which on a full array would be an out-of-range read. Mutation B2-10 prefetches from the un-advanced counter and twenty-five checks fail.

The counter advances on every byte read, including the one the master NACKs. That is Chapter 16.1's question 5, answered the same way here for the same reason: it decides what the next current-address read returns. Mutation B2-11 skips the final advance and twenty-three checks fail.

6b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, fifteen scenarios, one finish time
   $ iverilog -g2012 -o d i2c_eeprom_addressing.sv i2c_eeprom_addressing_tb.sv && ./d
   === i2c_eeprom_addressing: three read forms and a pointer the bus cannot size ===
   T1  a two-byte word address, high byte first
   T2  a write: pointer, then data, counter advancing
   T3  a random read: a dummy write for its side effect, then Sr
   T4  a current address read: no pointer, so history decides
   T5  a sequential read runs across the array, not a page
   T6  a two-byte pointer sent to a one-byte device
   T7  a sequential read rolls over instead of ending
   T8  a write rolls over at the end of the array too
   T9  a repeated START abandons a partial word address
   T10 another device's address is ignored
   T11 a later bare read continues where the last one stopped
   T12 a random read's write phase writes nothing
   T13 the high address byte, on a part with somewhere to put it
   T14 an address past the end of the array wraps into it
   T15 the flag records that a pointer write came first, nothing more
   === i2c_eeprom_addressing: ALL CHECKS PASSED ===

   $ iverilog -g2005 -o v i2c_eeprom_addressing.v i2c_eeprom_addressing_tb.v && ./v
   === i2c_eeprom_addressing: three read forms and a pointer the bus cannot size ===
   T1  a two-byte word address, high byte first
   T2  a write: pointer, then data, counter advancing
   T3  a random read: a dummy write for its side effect, then Sr
   T4  a current address read: no pointer, so history decides
   T5  a sequential read runs across the array, not a page
   T6  a two-byte pointer sent to a one-byte device
   T7  a sequential read rolls over instead of ending
   T8  a write rolls over at the end of the array too
   T9  a repeated START abandons a partial word address
   T10 another device's address is ignored
   T11 a later bare read continues where the last one stopped
   T12 a random read's write phase writes nothing
   T13 the high address byte, on a part with somewhere to put it
   T14 an address past the end of the array wraps into it
   T15 the flag records that a pointer write came first, nothing more
   === i2c_eeprom_addressing: ALL CHECKS PASSED ===

   $ nvc --std=2008 -a i2c_eeprom_addressing.vhd i2c_eeprom_addressing_tb.vhd
   $ nvc --std=2008 -e i2c_eeprom_addressing_tb && nvc --std=2008 -r i2c_eeprom_addressing_tb --stop-time=300us
   === i2c_eeprom_addressing: three read forms and a pointer the bus cannot size ===
   T1  a two-byte word address, high byte first
   T2  a write: pointer, then data, counter advancing
   T3  a random read: a dummy write for its side effect, then Sr
   T4  a current address read: no pointer, so history decides
   T5  a sequential read runs across the array, not a page
   T6  a two-byte pointer sent to a one-byte device
   T7  a sequential read rolls over instead of ending
   T8  a write rolls over at the end of the array too
   T9  a repeated START abandons a partial word address
   T10 another device's address is ignored
   T11 a later bare read continues where the last one stopped
   T12 a random read's write phase writes nothing
   T13 the high address byte, on a part with somewhere to put it
   T14 an address past the end of the array wraps into it
   T15 the flag records that a pointer write came first, nothing more
   === i2c_eeprom_addressing: ALL CHECKS PASSED ===

6c. What The Testbench Proves

#scenariowhat it establishes
1a two-byte word addressassembled high byte first
2a write: pointer then datathe counter advances once per byte written
3a random readthe write phase carries no data; the repeated START turns it around
4a current-address read, no word address at allworks only because the counter survived the STOP
5a sequential read across many locationsreads auto-increment through the whole array — no page boundary
6the same two-byte pointer sequence into a one-byte deviceevery byte acknowledged; the second pointer byte is consumed as data
7a sequential read from the last locationrollover to zero rather than a failure, and it is reported
8a write at the array endwrites roll over too — a different modulus from Chapter 16.3's
9a repeated START mid-word-addresshalf a pointer is not a pointer; the counter is not left half-updated
10a wrong device addressignored by both instances
11a random read then a sequential read with no new pointerthe second read continues where the first stopped
12read and write byte countscounted separately — a random read's write phase writes nothing
13a two-byte pointer on the 1024-word partthe high byte survives and matters
14an address past the end of the arraywraps into it, silently; a truncated pointer stays in range
15was_random_read across four transitionsthe flag records history, and only history

Test 6 is §2's failure mode, run. The two-byte pointer sequence driven into a one-byte device: every byte is acknowledged, nothing on the bus is wrong, and the second pointer byte is written as data at the location the first byte named. It is the test that turns a paragraph of warning into an executable fact. The mirror-image case — a one-byte master against a two-byte device, §2's table — is the one §10 debugs; the bench drives the direction above because it is the direction in which the surplus byte's destination is assertable.

Test 13 is the only test in the module that can see the high address byte at all. On both 256-byte instances every bit it carries is masked away, so two distinct defects are invisible there. §7 records both.

Test 14 tests the truncated pointer as well as the overflowing one, because they fail differently: an overflowing pointer wraps to a real location, while an unmasked truncated pointer leaves the counter outside the array and the next read is out of range rather than merely wrong.

Test 15 checks was_random_read at four points in one sequence — after a bare read, after a completed pointer write, after the read that followed it, and in the middle of the next write phase. A flag like this is only meaningful if it is wrong at none of those points, and two mutations attack two of them.

7. Mutation Testing

Twelve defects injected into the SystemVerilog addressing engine.

#injected defectoutcome
B2-1the assembled pointer not masked to the arraykilled — 3 checks
B2-2the low address byte overwrites the high halfkilled — test 13
B2-3the first address byte taken as the low halfkilled — 54 checks
B2-4the word-address counter cleared on a repeated STARTkilled — 17 checks
B2-5no rollover at the top of the arraykilled — 4 checks
B2-6rollover performed but never reportedkilled — 2 checks
B2-7a current-address read reported as a random readkilled — test 15
B2-8was_random_read left set from a previous transactionkilled — test 15
B2-9the counter does not advance after a written bytekilled — 7 checks
B2-10the next sequential byte prefetched from the un-advanced counterkilled — 25 checks
B2-11no advance on the final, NACKed byte of a readkilled — 23 checks
B2-12the address-byte count carried across a repeated STARTkilled — 1 check

Twelve of twelve — after a bench change that is the most instructive result in the chapter.

B2-1 and B2-2 both survived the original suite, and neither was a bench hole in the ordinary sense. They were unobservable by construction on the parts the bench instantiated. Work through B2-2 on a 256-byte part: the correct expression is (high << 8 | low) & 0x00FF, and the mutation is low & 0x00FF. Those are equal for every input, because the mask discards the high half in both cases. No test could distinguish them, however the bench was written.

The fix was not a new assertion. It was a new device: a 1024-word part, on which ADDR_MASK is 0x03FF and the high byte contributes two real bits. Both mutations die immediately there.

That is a distinction worth holding onto. A surviving mutant is usually a missing test; occasionally it is a missing configuration, and no amount of assertion-writing against the existing configuration will fix it.

And it carries a design lesson about the part, not the model. A two-byte word address on a 256-byte part is a protocol handing a device more address bits than it owns. That is the same phenomenon as §3's out-of-range access, seen from the other end, and it is why the high byte on a small part is genuinely uninformative rather than merely redundant.

B2-3's fifty-four checks are the signature of a shifted frame. Taking the first address byte as the low half puts every subsequent byte in the wrong slot for the rest of the run. Compare B2-12's single check: carrying addr_bytes_got across a repeated START matters in exactly one place, and the bench looks at it in exactly one place.

B2-6 kills with two checks and is the one a real suite is likeliest to miss. The array size is a power of two, so the counter wraps correctly whether or not the wrap branch reports it — the only evidence is the rolled_over output itself. A bench that checked the returned bytes and not the flag would score zero on B2-6 while proving the data path correct.

8. Verification Connection — Layered Sequences and the Reference Model

An EEPROM's difficulty is not the individual transfer; it is that the meaning of a transfer depends on what preceded it. That is a sequence-layering problem, and it is where a UVM environment earns its cost.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_eeprom_uvm.sv — layered sequences, and a reference model that tracks the pointer
   // The item a driver consumes: one complete I2C transfer, framing included. Note
   // that a random read is ONE item, not two -- the repeated START is internal to it.
   // Modelling it as two items would let the sequencer interleave another master's
   // transfer between the halves, which is exactly the hazard of section 1's callout
   // and not something a bench should be able to do by accident.
   class eeprom_xfer extends uvm_sequence_item;
      `uvm_object_utils(eeprom_xfer)
      rand bit [6:0]  dev_addr;
      rand bit [15:0] word_addr;
      rand int        n_addr_bytes;   // 1 or 2 -- section 2's question
      rand bit        send_pointer;   // 0 = current-address read, 1 = random read
      rand bit        is_read;
      rand byte       payload[];
           byte       rdata[];
           bit        nacked;

      // A current-address read does not send a pointer, and a write always does.
      constraint c_form {
         if (!is_read) send_pointer == 1;
         n_addr_bytes inside {1, 2};
         payload.size() inside {[1:16]};
      }
   endclass

   // The reference model. Its whole job is to hold the ONE piece of state the
   // protocol does not carry: the device's internal counter. Without it, a
   // current-address read cannot be predicted at all -- and a scoreboard that cannot
   // predict a current-address read will either skip those transfers or, worse,
   // check them against a pointer it assumed.
   class eeprom_model extends uvm_component;
      `uvm_component_utils(eeprom_model)

      protected byte     mem[int];
      protected int      ptr;          // the device's counter, mirrored
      protected int      n_words;
      protected int      addr_mask;

      function new(string name, uvm_component parent);
         super.new(name, parent);
      endfunction

      function void configure(int words);
         n_words   = words;
         addr_mask = words - 1;        // section 3: the counter is masked, not clipped
      endfunction

      // Assemble a pointer exactly as the device does, masking included. Getting
      // this wrong in the model produces a scoreboard that disagrees with a correct
      // device, which is the most expensive kind of bug in a verification
      // environment: it looks like an RTL failure.
      protected function int assemble(bit [15:0] wa, int nbytes);
         int v = (nbytes == 1) ? (wa & 16'h00FF) : wa;
         return v & addr_mask;
      endfunction

      function void predict(eeprom_xfer t, output byte exp[]);
         if (t.send_pointer)
            ptr = assemble(t.word_addr, t.n_addr_bytes);
         if (t.is_read) begin
            exp = new[t.payload.size()];
            foreach (exp[i]) begin
               exp[i] = mem.exists(ptr) ? mem[ptr] : 8'h00;
               ptr    = (ptr == n_words - 1) ? 0 : ptr + 1;   // the rollover
            end
         end else begin
            foreach (t.payload[i]) begin
               mem[ptr] = t.payload[i];
               ptr      = (ptr == n_words - 1) ? 0 : ptr + 1;
            end
         end
      endfunction
   endclass

   // A layered sequence: the API a test wants, expressed in terms of transfers.
   // "Read sixteen bytes from 0x0210" is one call here and one transfer on the bus.
   class eeprom_read_seq extends uvm_sequence #(eeprom_xfer);
      `uvm_object_utils(eeprom_read_seq)
      rand bit [15:0] start_addr;
      rand int        n_bytes;
      rand int        n_addr_bytes;

      function new(string name = "eeprom_read_seq"); super.new(name); endfunction

      virtual task body();
         eeprom_xfer t = eeprom_xfer::type_id::create("t");
         start_item(t);
         if (!t.randomize() with {
                dev_addr     == 7'h50;
                is_read      == 1;
                send_pointer == 1;                 // a random read
                word_addr    == local::start_addr;
                n_addr_bytes == local::n_addr_bytes;
                payload.size() == local::n_bytes;  // sized, contents unused on a read
             })
            `uvm_error("EEPROM", "read transfer randomization failed")
         finish_item(t);
      endtask
   endclass

Three things this structure buys, and one trap it does not remove.

A reference model that mirrors the pointer is not optional here. A scoreboard for a stateless device can predict a transfer from the transfer alone. This one cannot: §1's current-address read carries no address, so its expected data is a function of every transfer since the last pointer write. A model without ptr has to skip those transfers, which means the most history-dependent transaction on the bus is the one that goes unchecked.

n_addr_bytes belongs in the item, and randomizing it is the point. §2's failure modes are a disagreement between master and device, so a bench that hard-codes the right number can never produce them. Constraining it to the device's real width for most tests and deliberately violating it in a directed test is how tests 9 and 10 of §6c become part of a regression rather than one-off experiments.

One transfer, not two, for a random read. Modelling the write phase and the read phase as separate items lets the sequencer put another master's transfer between them — which is legal on the bus and catastrophic for the read, and is a bug the bench introduced. Keeping the repeated START inside the item makes atomicity structural.

9. FPGA and ASIC Implications

Derive the counter width from the array size, and mask every write to it. An address wider than the array is not a theoretical problem: it arrives whenever a master is configured for the wrong part number, and an unmasked counter turns that into an out-of-range memory access. In an FPGA a BRAM will return whatever the address bits it does have select; in an ASIC the memory compiler's behaviour is unspecified.

The pointer needs a reset value and it should be zero. A current-address read is legal as the very first transaction after power-on, and it will serve from wherever the counter starts. Leaving that undefined makes the device's first transfer unpredictable in a way no driver can compensate for.

Prefetch costs one read port and removes a stretch. Serving a sequential read from a registered copy, fetched when the counter advanced, means the next byte is ready before the master clocks it. Fetching on demand instead means either a combinational read from the array — which constrains the memory — or clock stretching on every byte of every burst. The second is legal and multiplies a burst's duration.

Two address bytes are not free: the pointer's collection is a state. S_ADDRH has to know whether it is expecting the high or the low half, which is one bit of state and one output worth exposing. On a part that supports both widths by strap or by part number, that state machine is the only place the width appears, and it is worth parameterising rather than branching.

Report the rollover if you can afford a flag. A system that regularly rolls over is a system whose driver is reading past the end of the array, and that is a driver bug being masked by a device convention. One bit and one status register location turn an invisible failure into a logged one.

The write-protect pin interacts with all of this and none of it is on the bus. A part with WP asserted will typically acknowledge the address and the word address and then NACK or silently discard the data. Which of those it does is Chapter 16.1's question 4 again, and the pin's state is invisible to the master either way.

10. Debugging — The Firmware Update That Corrupted the First Byte

Symptom

A product stores a 16-byte configuration blob in an external EEPROM. A hardware revision moves to a larger second-source EEPROM, pin-compatible and at the same device address. After the change, writes appear to succeed -- every byte acknowledged -- but reading the blob back with the product's own firmware returns data that does not match what was written. A single-byte write followed by a single-byte read still passes the factory self-test.

Root Cause

Section 2's failure mode. The new part expects two word-address bytes and the driver sends one, so the device consumed the blob's first data byte as the low half of the pointer. Fifteen of the sixteen bytes were written, at an address the master never named -- and because 0x4000 exceeds a 4096-byte array, the masking of section 3 put them at 0x0000 rather than anywhere near the intended 0x0040. Nothing violates the specification: the device consumed the number of address bytes its datasheet specifies and acknowledged every byte it received. The self-test passed by coincidence, having written no data at all.

Fix
Send two word-address bytes for the new part, and make the address width a property of the part number in the driver rather than a constant. For the regression: write a known pattern and verify it with an INDEPENDENTLY computed address -- reading back through the same driver repeats whatever mistake the write made, which is why the self-test was useless. And make the self-test write at least two data bytes, so a driver that donates its first byte to the pointer cannot pass it.

Three things generalise.

The passing test was passing for the wrong reason. The single-byte write wrote no data at all — its one data byte became half the pointer — and the single-byte read returned a coincidence. A self-test whose payload is one byte cannot detect a driver that donates its first byte to the pointer, because there is nothing left to detect it with.

The address was wrong twice over. First by construction, because the first data byte became the pointer's low half; then again by masking, because the resulting 0x4000 is outside a 4096-byte array and section 3's wrap put the data at 0x0000. The blob was neither where the driver asked nor anywhere near it.

Every byte was acknowledged, and the acknowledge was honest. The device received eighteen bytes and acknowledged eighteen bytes. The protocol has no way to express "I expected a different number of address bytes", because the number is not part of the protocol.

"Pin-compatible, same address" again. As in Chapter 16.1 §10, the parts agree on everything a pinout or an address table would record, and differ on a convention that only a transaction longer than the pointer can reveal.

11. Common Misconceptions

"A current-address read and a random read are different transactions." The read halves are byte-for-byte identical. The difference is entirely whether a pointer write preceded it, and the device knows only because it watched. §1.

"The write phase of a random read carries data." It carries the word address and nothing else. It is a write by direction bit that writes nothing. §4.

"The number of word-address bytes is implied by the device address." It is a property of the part, stated in its datasheet and invisible on the bus. A master that guesses wrong is acknowledged all the way through. §2.

"A wrong number of address bytes produces a NACK." It produces a perfectly conforming transfer that writes the wrong number of bytes to an address the master never named. §2 and §10.

"An address past the end of the array is an error." It wraps into the array, silently, and lands on real data. §3.

"A 32 kbit part and a 2 kbit part with the same address are interchangeable." They differ in the number of address bytes, which changes the meaning of every byte after the first. §10.

"Rollover in a sequential read is visible to the master." Nothing in the transfer marks it. The bytes after the wrap arrive exactly like the bytes before it. §5.

"A repeated START in a random read could be a STOP and a START." On a single-master bus, usually. On a shared bus, another master can set its own pointer in the gap and the read serves from that. §1.

"A mutation that survives means the testbench needs another assertion." Sometimes it means the testbench needs another device. Two mutations here are unobservable on a 256-byte part for arithmetic reasons, whatever assertions are written. §7.

"A reference model can predict a read from the read." Not a current-address read, whose expected data depends on every transfer since the last pointer write. The model has to mirror the pointer. §8.

12. Reason It Through

A capture shows: START, 0xA1, three bytes, STOP. What address were those bytes read from?

Unknowable from the capture. It is a current-address read, so the address is wherever the device's counter happened to be — which depends on every transfer since the last pointer write, including transfers from other masters. You would need the preceding history, or a device that reports its counter. §1.

A master sends one word-address byte to a part that expects two, then three data bytes. How many bytes are written, and where?

Two, at the address formed by taking the word-address byte as the high half of the pointer and the master's first data byte as the low half. The first data byte is consumed by the pointer and never stored. §2.

Why does a read-back with the same driver not detect that?

Because the read makes the identical mistake: its single address byte becomes the high half and its first "data" position becomes the low half, so it reads from the same wrong address the write used. Two identical errors cancel, and the corruption presents as a one-byte shift rather than as a wrong location. §10.

Why must the pointer be masked to the array rather than range-checked and refused?

Because that is what the hardware does — the wires for the excess bits do not exist — and because a refusal has nowhere to go. There is no status code in a word-address write. Masking also keeps a truncated pointer inside the array, so a master that sends the high byte and then stops leaves the device at a real location rather than outside its memory. §3.

On a 256-byte part expecting two address bytes, name a defect no testbench can detect.

Discarding the high address byte entirely. The final pointer is masked to eight bits, so the high byte's contribution is zero either way — the correct expression and the defective one are arithmetically equal for every input. It takes a part with more than 256 words to observe. §7.

Why is rolled_over worth a flip-flop on a real part?

Because the rollover is otherwise invisible, and a system that rolls over routinely has a driver reading past the end of the array. One bit turns a silent driver bug into a logged event. §5 and §9.

Why should a random read be one sequence item rather than two?

Because two items can be interleaved by the sequencer, and a transfer from another master between the pointer write and the read makes the read serve from that master's address. The repeated START's atomicity is a protocol property, and a bench that can violate it will. §8.

13. Understanding Check

14. Summary

A serial memory is the register-map pattern with nothing else in it, which makes it the clearest place to see what the pointer conventions cost.

A current-address read and the read half of a random read are byte-for-byte identical. The only difference is whether a pointer write preceded it, and the device knows only because it watched. There is nothing in the read that encodes it.

The write phase of a random read carries no data. It is a write by direction bit whose entire purpose is to move the pointer.

How many word-address bytes a part takes is in its datasheet and nowhere on the bus. A master that gets it wrong is acknowledged on every byte, and its first data byte becomes half the pointer.

The reverse mistake fails differently, with the data landing one position shifted in the payload — and a read-back that repeats the same error cannot detect either.

An address past the end of the array wraps into it, because the counter is only as wide as the array and the excess bits have no wires. 0x0834 on a 1024-word part is 0x0034, a real location with real data.

So the counter must be masked at assembly, not merely assembled, which also keeps a truncated pointer inside the array. VHDL's bounds check found this where Verilog returned X.

A sequential read rolls over silently at the end of the array. The bytes after the wrap arrive exactly like the bytes before it, and a rolled_over flag is one bit that turns a driver bug into a logged event.

Two mutations here were unobservable by construction rather than untested. On a 256-byte part the final mask makes the correct and defective expressions arithmetically equal. The fix was a new device in the bench — a 1024-word part — not a new assertion.

And a reference model has to mirror the pointer, or the most history-dependent transaction on the bus is the one it cannot check.

15. What Comes Next

Reads have no structure: the counter walks the whole array and wraps at the end of it. Writes do not work that way at all.

Chapter 16.3 introduces the page buffer, and with it the most surprising asymmetry in the whole device. A write burst does not walk the array — it walks a page, and the high bits of the address are frozen the moment the word address arrives. A burst that runs off the end of a page does not continue into the next one; it wraps back to the start of the same page and begins overwriting the bytes it just sent.

Which means a sixteen-byte write to a sixteen-byte page can be perfect, and the same sixteen bytes starting one location later can destroy fifteen of themselves — with every byte acknowledged, and no indication anywhere that anything went wrong.

Continue learning