Skip to content
VLSI Mentor

I²C · Module 16

Real Device Access Patterns — Sensors, RTCs and PMICs

A measurement wider than a byte takes two transfers, and the device keeps measuring in between. Works out how a master assembles a value neither sample ever held, why the error is largest exactly where it matters, and why the remedy is a device convention that only a datasheet can tell you about.

Every device convention so far has concerned an address: where the pointer is, how wide it is, which page it lives in, when the device will answer. This chapter is about the devices where the data itself is the problem.

The setup is the most ordinary thing on the bus. A sensor measures something and reports it as a 16-bit number. The bus carries eight bits at a time. So a master reads two bytes, and assembles them.

And the sensor keeps measuring while it does.

Every byte was acknowledged. The transfer was well formed. The value is in range and looks plausible. And it is wrong by the full weight of the byte that changed.

1. Why the Error Is Largest Where It Matters

The example above is not a worst case chosen for drama. It is the characteristic case, and the reason is arithmetic.

A torn read goes wrong only when the two bytes come from samples whose high bytes differ. That happens exactly at a carry boundary — the moment the low byte rolls over and the high byte increments. And at a carry boundary the low byte's value jumps from near-maximum to near-zero, so combining the old high byte with the new low byte produces an error of roughly one full high-byte step.

Azvya Education Pvt. Ltd.VLSI Mentor
the error, as a function of where the tear happens
   both samples in the same high-byte range   ->  error at most 255 counts, often 1
   the samples straddle a carry boundary      ->  error of about 256 counts
   read LSB first instead of MSB              ->  same magnitude, opposite sign

So the error is small or zero almost everywhere, and large precisely at the value transitions a system is most likely to be watching for. A temperature crossing a threshold, a counter rolling over, an angle passing zero — these are the moments a carry happens, and they are the moments the tear is worst.

Which is why the bug survives testing. A sensor sitting at a steady value produces identical bytes every time, and a tear is invisible. The failure needs the measurement to be changing across a carry, which is exactly what a bench with a static model never does.

2. Reading LSB First Does Not Help

A tempting response is to reverse the order: read the low byte first, then the high byte. It does not help, and understanding why rules out a whole family of proposed fixes.

With LSB first, the master reads the low byte of sample N and the high byte of sample N+1. At the carry boundary that gives low byte 0xFF from sample 255 and high byte 0x01 from sample 256 — assembling to 0x01FF, which is 511. The error is 256 counts in the other direction.

The problem is not the order. It is that two transfers straddle an update. Any scheme that reads the bytes at two different times has the same exposure, and no ordering of the bytes changes that.

Nor does re-reading help on its own. Read twice and compare, and you have two chances to tear rather than one; if they agree you have probably got a coherent sample, and "probably" is doing a lot of work — two consecutive tears at the same boundary agree with each other.

3. The Remedy Is a Device Convention

The fix has to be on the device side, and it is: latch the whole measurement when the burst begins, and serve every byte of that burst from the latched copy.

A device that does this guarantees a burst read returns one coherent sample. The internal core keeps measuring — it must, because a sensor that stopped measuring while being read would be a worse sensor — but the bytes the master receives all come from the same instant.

A repeated START ends the burst, and that is correct. It is a new burst, so it re-latches — which means a master that turns the transfer around in the middle of a measurement loses coherency. The device is behaving correctly and the master has defeated the protection.

And whether the device does this at all is not on the bus. There is no capability bit, no status flag, nothing to interrogate. A latching device and a non-latching device are byte-for-byte identical on every transfer that does not straddle an update — which is almost all of them. The only way to know is the datasheet. §6c's test 2 is that case, run on both kinds of device, producing identical results.

4. The Two Devices, Side by Side

A sequence diagram with three actors: the master, a latching sensor, and a non-latching sensor. The master sends a start and an address byte with read direction. Both sensors acknowledge. The latching sensor freezes the current sample of 0x00FF at this point; the non-latching one does not. Each sensor sends its most significant byte, which is 0x00 in both cases, and the master acknowledges. The sensor core then updates to 0x0100 in both devices. Each sensor sends its least significant byte: the latching one sends 0xFF from its frozen copy, and the non-latching one sends 0x00 from the new live sample. The master not-acknowledges and sends a stop. The master has assembled 0x00FF from the latching device and 0x0000 from the non-latching one.An update between the MSB and LSB reads, at a carry boundaryMasterLatchingLiveS, addr RACK — latches 0x00FFACK — latchesnothingMSB 0x00MSB 0x00ACK — send the nextcore updates to0x0100LSB 0xFF — frozencopyLSB 0x00 — livesampleNACK, then P
The same byte stream and the same mid-burst update, through a device that latches and one that does not. The transfers are indistinguishable; only the assembled value differs.

Everything on the bus is identical between the two devices except the value of one byte, and that byte is in range and plausible in both cases. There is no framing difference, no timing difference, and no error indication.

5. The Tear, at Byte Resolution

One update, two devices: 0x00FF from the latch and 0x0000 from the live core

8 cycles
A waveform with eight intervals at byte resolution. The first interval is a start condition and the second an address byte with read direction, at which point the latching device freezes the sample 0x00FF. The third interval carries the most significant byte, 0x00, from both devices. Between the third and fifth intervals the sensor core updates from 0x00FF to 0x0100. The fifth interval carries the least significant byte, which differs: the latching device sends 0xFF and the live device sends 0x00. A torn risk row rises when the update lands inside the burst. The final intervals show the master not-acknowledging and sending a stop, and a row gives the assembled value for each device.sample Nsample Nsample N+1 livesample N+1 livethe burst begins: the latch is takenthe burst begins: the latchis takenthe core updates inside the burstthe core updates inside theburstlatch gives 0x00FF, live gives 0x0000latch gives 0x00FF, livegives 0x0000frameSaddr RMSBMSBLSBPPPcore00FF00FF00FF01000100010001000100latched000FF00FF00FF00FF00FF00FF00FFtx lat000000FFFFFFFFtx live00000000000000tornt0t1t2t3t4t5t6t7
Six intervals covering one burst read. The update lands between the two byte reads. The latching device serves 0xFF from its frozen copy; the live device serves 0x00 from the new sample, and the master assembles a number 255 counts below both samples.

The torn row is a device output that exists for the testbench. A real sensor does not have it. It is in the design because a property you cannot observe is a property you cannot test, and §6a explains what it must and must not flag.

The two tx rows differ in exactly one interval. That single byte is the whole failure, and there is no other difference anywhere in the transfer.

6. Status-First, and the Other Two Devices

The tear is the sharpest instance of a general pattern, and two more devices are worth naming because they are where most engineers meet it.

A real-time clock is a counter with the same problem and a nastier boundary. Reading seconds, then minutes, then hours across a minute rollover gives 10:59:59 read as 10:59 with seconds 00 — an hour and a minute that belong to the old sample and seconds that belong to the new one. Across an hour rollover the error is an hour. RTC datasheets therefore specify a burst read of the whole time register block, and the reason is exactly §3's: the device latches, and reading the fields separately defeats it.

A PMIC's status bits are read-to-clear, which makes the read itself destructive. A fault register whose bits clear on read cannot be polled twice: the second read returns zeros, and a master that reads it for logging and then again for decision-making has lost the fault. This is Chapter 16.1's question 4 in a sharper form — the register's read has a side effect, and nothing on the bus indicates it.

And a data-ready bit separates a fresh sample from a re-read. A sensor whose conversion takes longer than the master's polling interval will return the same measurement twice, and the master cannot tell without help. One bit, set when a new sample lands and cleared when it is read out, turns an ambiguous re-read into a defined one. The design below implements it and clears it on the LSB read — after the sample has been fully consumed, not on the first byte.

7. The Sensor Shadow Register in Three Languages

A sensor model with a configurable shadow register, a live-updating core, a data-ready bit and torn-read reporting — its independent oracle, and both in all three languages. The bench instantiates two devices differing only in whether they latch, drives both from the same byte stream, and makes the difference an assertion.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_shadow.sv — a configurable shadow register, a live core, and torn-read reporting
   // -----------------------------------------------------------------------------
   // i2c_sensor_shadow.sv
   // Sample coherency: why a multi-byte measurement must be read in one burst.
   //
   // A sensor whose measurement is wider than a byte updates every byte of it at once
   // internally. A master reads them one at a time. If an update lands between two
   // byte reads, the master assembles a value from TWO DIFFERENT SAMPLES -- a torn
   // read -- and the result is not merely stale, it can be a value neither sample
   // ever held.
   //
   //   sample N   = 0x00FF        sample N+1 = 0x0100
   //   master reads the MSB of N (0x00), the sensor updates, master reads the LSB of
   //   N+1 (0x00), and assembles 0x0000 -- 255 counts BELOW both samples.
   //
   // The error is worst exactly where the data is most interesting: at a carry
   // boundary, which is where the measurement is changing fastest.
   //
   // THE DEVICE-SIDE REMEDY. Latch the whole measurement on the first byte read of a
   // burst and serve every subsequent byte from that frozen copy, releasing it at the
   // STOP. A burst then always returns one coherent sample, and the master's only
   // obligation is to read the bytes in one transaction rather than several.
   //
   // This block implements both behaviours, selected by SHADOW_ENABLE, because the
   // difference is the entire point: a sensor without the latch is not broken, it
   // simply pushes the coherency problem onto the master, and a master that reads
   // byte-by-byte from a latching sensor is safe while the same code on a
   // non-latching one is not.
   //
   // Nothing here is in UM10204. Note 2 delegates "all decisions on auto-increment"
   // to the device designer, and a shadow register is one of those decisions. That is
   // why a datasheet specifying a burst read is specifying a CORRECTNESS requirement
   // and not an optimisation -- and why a driver that reads a 16-bit sensor with two
   // single-byte transactions can be wrong on hardware where it appears to work.
   //
   // The block also carries the two other patterns of the chapter:
   //   CONFIGURE-THEN-READ  a config register that must be written before the
   //                        measurement means anything, with a flag if it was not
   //   STATUS-FIRST         a data-ready bit, so a master can tell a fresh sample
   //                        from a re-read of the same one
   // -----------------------------------------------------------------------------

   module i2c_sensor_shadow #(
      parameter bit     SHADOW_ENABLE = 1'b1,  // latch the sample on the first read
      parameter [6:0]   MY_ADDR       = 7'h68,
      parameter [7:0]   REG_STATUS    = 8'h00,
      parameter [7:0]   REG_MSB       = 8'h01,
      parameter [7:0]   REG_LSB       = 8'h02,
      parameter [7:0]   REG_CONFIG    = 8'h03,
      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,

      // ---- the sensor core, driven by the bench -------------------------------
      input  logic              sample_update,   // pulse: a new measurement is ready
      input  logic [15:0]       sample_in,

      output logic              ack,
      output logic [7:0]        tx_byte,
      output logic              tx_valid,
      output logic [7:0]        ptr,
      output logic [15:0]       live_sample,     // the sensor core's current value
      output logic [15:0]       shadow,          // the frozen copy a burst serves from
      output logic              shadow_held,     // a burst is in progress
      output logic              configured,      // the config register has been written
      output logic              read_unconfigured, // a measurement was read before that
      output logic              data_ready,      // a fresh sample is waiting
      output logic              torn_risk,       // an update landed mid-burst
      output logic [2:0]        state,
      output logic [CNT_W-1:0]  updates_seen,
      output logic [CNT_W-1:0]  bytes_served
   );

      localparam [2:0] S_IDLE = 3'd0, S_PTR = 3'd1, S_WRITE = 3'd2, S_READ = 3'd3;

      logic [7:0] config_reg;

      // What a read of the current pointer returns. With the shadow enabled a burst
      // serves the frozen copy; without it, every byte comes from the live core.
      function [7:0] read_reg (input [7:0] p, input [15:0] src, input [7:0] cfg,
                               input rdy, input cfgd);
         begin
            if      (p == REG_STATUS) read_reg = {6'b0, cfgd, rdy};
            else if (p == REG_MSB)    read_reg = src[15:8];
            else if (p == REG_LSB)    read_reg = src[7:0];
            else if (p == REG_CONFIG) read_reg = cfg;
            else                      read_reg = 8'h00;
         end
      endfunction

      // Where a read in progress takes its measurement bytes from. Note this is
      // NOT used for the FIRST byte of a burst: at the address byte the shadow is
      // only being captured, so shadow_held is still low and the byte must come
      // from the live core. It is the SECOND and later bytes that must come from
      // the frozen copy, and that is the one place this is read.
      wire [15:0] serving = (SHADOW_ENABLE && shadow_held) ? shadow : live_sample;

      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;
            ptr               <= 8'h00;
            live_sample       <= 16'h0000;
            shadow            <= 16'h0000;
            shadow_held       <= 1'b0;
            configured        <= 1'b0;
            read_unconfigured <= 1'b0;
            data_ready        <= 1'b0;
            torn_risk         <= 1'b0;
            config_reg        <= 8'h00;
            updates_seen      <= {CNT_W{1'b0}};
            bytes_served      <= {CNT_W{1'b0}};
         end else begin
            ack <= 1'b0;

            // -----------------------------------------------------------------
            // The sensor core updates whenever it likes -- including in the middle
            // of a burst, which is the whole hazard. The core does NOT wait for the
            // bus, because a sensor that stopped measuring while being read would be
            // a worse device.
            // -----------------------------------------------------------------
            if (sample_update) begin
               live_sample  <= sample_in;
               data_ready   <= 1'b1;
               updates_seen <= updates_seen + 1'b1;
               // An update during a burst is exactly the torn-read window. With the
               // shadow enabled the burst is unaffected and this is merely recorded;
               // without it, the master is now assembling two samples.
               if (shadow_held) torn_risk <= 1'b1;
            end

            if (start_seen) begin
               state    <= S_IDLE;
               tx_valid <= 1'b0;
               // A repeated START ends the burst, so a master that turns the transfer
               // around gets a FRESH latch -- which is correct: it is a new burst.
               shadow_held <= 1'b0;

            end else if (stop_seen) begin
               state       <= S_IDLE;
               tx_valid    <= 1'b0;
               shadow_held <= 1'b0;        // release the frozen copy

            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 begins. THIS is where the sample is latched, if
                           // the device latches at all.
                           if (SHADOW_ENABLE && !shadow_held) begin
                              shadow      <= live_sample;
                              shadow_held <= 1'b1;
                              tx_byte     <= read_reg(ptr, live_sample, config_reg,
                                                      data_ready, configured);
                           end else begin
                              tx_byte <= read_reg(ptr, live_sample, config_reg,
                                                  data_ready, configured);
                           end
                           // Reading a measurement before configuring the device is a
                           // real and common mistake, and the value returned is
                           // meaningless rather than wrong.
                           if ((ptr == REG_MSB || ptr == REG_LSB) && !configured)
                              read_unconfigured <= 1'b1;
                           tx_valid <= 1'b1;
                           state    <= S_READ;
                        end else begin
                           state <= S_PTR;
                        end
                     end
                  end

                  S_PTR: begin
                     ack   <= 1'b1;
                     ptr   <= byte_in;
                     state <= S_WRITE;
                  end

                  S_WRITE: begin
                     ack <= 1'b1;
                     if (ptr == REG_CONFIG) begin
                        config_reg <= byte_in;
                        configured <= 1'b1;
                     end
                     // Status and the measurement registers are read-only; a write is
                     // accepted and discarded, which is this device's convention.
                     ptr <= ptr + 1'b1;
                  end

                  default: ;
               endcase

            end else if (read_byte_done && state == S_READ) begin
               bytes_served <= bytes_served + 1'b1;
               // Reading the LSB clears data_ready: the sample has been consumed, so
               // a master can tell a fresh measurement from a re-read of the same one.
               if (ptr == REG_LSB) data_ready <= 1'b0;
               if (!master_acked) begin
                  tx_valid <= 1'b0;
                  state    <= S_IDLE;
                  ptr      <= ptr + 1'b1;
               end else begin
                  ptr     <= ptr + 1'b1;
                  // The next byte comes from the frozen copy if one is held.
                  tx_byte <= read_reg(ptr + 1'b1, serving, config_reg,
                                      data_ready, configured);
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_shadow_tb.sv — the independent oracle: two devices, one byte stream, thirteen scenarios
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_sensor_shadow_tb.sv
   // Independent oracle for i2c_sensor_shadow.
   //
   // Two instances, one latching and one not, see the same byte stream and the same
   // sensor updates. Test 4 is the chapter's worked example: an update injected
   // between the MSB and LSB reads at a carry boundary. The latching instance returns
   // a coherent 0x00FF; the non-latching one returns 0x0000, which is 255 counts below
   // BOTH samples and a value neither ever held.
   //
   //   dut_s : SHADOW_ENABLE = 1   latches the sample on the first read of a burst
   //   dut_n : SHADOW_ENABLE = 0   serves every byte from the live core
   // -----------------------------------------------------------------------------
   module i2c_sensor_shadow_tb;

      localparam [2:0] S_IDLE = 3'd0, S_PTR = 3'd1, S_WRITE = 3'd2, S_READ = 3'd3;
      localparam [6:0] ADDR = 7'h68;
      localparam [7:0] R_STATUS = 8'h00, R_MSB = 8'h01, R_LSB = 8'h02, R_CONFIG = 8'h03;

      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        sample_update = 1'b0;
      logic [15:0] sample_in = 16'h0000;

      logic        s_ack, s_txv, s_held, s_cfgd, s_ruc, s_rdy, s_torn;
      logic [7:0]  s_tx, s_ptr;
      logic [15:0] s_live, s_shadow;
      logic [2:0]  s_state;
      logic [7:0]  s_upd, s_srv;

      logic        n_ack, n_txv, n_held, n_cfgd, n_ruc, n_rdy, n_torn;
      logic [7:0]  n_tx, n_ptr;
      logic [15:0] n_live, n_shadow;
      logic [2:0]  n_state;
      logic [7:0]  n_upd, n_srv;

      integer errors = 0;
      integer k;
      logic [7:0] msb_got, lsb_got;
      logic [15:0] assembled_s, assembled_n;

      i2c_sensor_shadow #(.SHADOW_ENABLE(1'b1), .MY_ADDR(ADDR), .CNT_W(8)) dut_s (
         .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),
         .sample_update(sample_update), .sample_in(sample_in),
         .ack(s_ack), .tx_byte(s_tx), .tx_valid(s_txv), .ptr(s_ptr),
         .live_sample(s_live), .shadow(s_shadow), .shadow_held(s_held),
         .configured(s_cfgd), .read_unconfigured(s_ruc), .data_ready(s_rdy),
         .torn_risk(s_torn), .state(s_state),
         .updates_seen(s_upd), .bytes_served(s_srv));

      i2c_sensor_shadow #(.SHADOW_ENABLE(1'b0), .MY_ADDR(ADDR), .CNT_W(8)) dut_n (
         .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),
         .sample_update(sample_update), .sample_in(sample_in),
         .ack(n_ack), .tx_byte(n_tx), .tx_valid(n_txv), .ptr(n_ptr),
         .live_sample(n_live), .shadow(n_shadow), .shadow_held(n_held),
         .configured(n_cfgd), .read_unconfigured(n_ruc), .data_ready(n_rdy),
         .torn_risk(n_torn), .state(n_state),
         .updates_seen(n_upd), .bytes_served(n_srv));

      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;
            sample_update = 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

      task update (input [15:0] v);
         begin
            @(negedge clk); sample_in = v; sample_update = 1'b1;
            @(posedge clk); @(negedge clk); sample_update = 1'b0;
         end
      endtask

      task configure (input [7:0] v);
         begin
            ev_start; send_addr(1'b0); send_data(R_CONFIG); send_data(v); ev_stop;
         end
      endtask

      task set_ptr (input [7:0] p);
         begin
            ev_start; send_addr(1'b0); send_data(p); ev_stop;
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d (0x%0h) expected %0d (0x%0h)", what, g, g, e, e);
               errors = errors + 1;
            end
         end
      endtask

      task ck_bit (input [200*8:1] what, input g, input e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0b expected %0b", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      initial begin
         $display("=== i2c_sensor_shadow: one sample, two bytes, and the gap between them ===");

         // ----------------------------------------------------------------
         // T1. CONFIGURE-THEN-READ. A measurement read before the config register is
         //     written is meaningless, and the device says so.
         // ----------------------------------------------------------------
         do_reset;
         update(16'h1234);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         $display("T1  reading a measurement before configuring is flagged");
         ck_bit("T1 not configured", s_cfgd, 1'b0);
         ck_bit("T1 flagged as read-unconfigured", s_ruc, 1'b1);
         take(1'b0); ev_stop;
         configure(8'h81);
         ck_bit("T1 now configured", s_cfgd, 1'b1);

         // ----------------------------------------------------------------
         // T2. A clean burst read with no update in the middle. Both instances agree,
         //     which is the case that makes the hazard invisible in testing.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'hABCD);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = s_tx; take(1'b1);
         lsb_got = s_tx; take(1'b0);
         ev_stop;
         assembled_s = {msb_got, lsb_got};
         $display("T2  an undisturbed burst: both instances agree");
         ck_int("T2 latching instance assembled 0xABCD", assembled_s, 16'hABCD);
         // the same stream through the non-latching instance
         do_reset;
         configure(8'h81);
         update(16'hABCD);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = n_tx; take(1'b1);
         lsb_got = n_tx; take(1'b0);
         ev_stop;
         assembled_n = {msb_got, lsb_got};
         ck_int("T2 non-latching instance also 0xABCD", assembled_n, 16'hABCD);

         // ----------------------------------------------------------------
         // T3. The latch is taken on the FIRST read of a burst, and held.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h5566);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         $display("T3  the sample is latched on the first read of a burst");
         ck_bit("T3 the latch is held", s_held,   1'b1);
         ck_int("T3 and holds the sample", s_shadow, 16'h5566);
         ck_bit("T3 the non-latching instance holds nothing", n_held, 1'b0);
         take(1'b0); ev_stop;
         ck_bit("T3 released at the STOP", s_held, 1'b0);

         // ----------------------------------------------------------------
         // T4. THE CHAPTER'S WORKED EXAMPLE. A carry boundary crossed between the
         //     two byte reads. 0x00FF becomes 0x0100 mid-burst.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h00FF);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = s_tx;                          // MSB of sample N = 0x00
         update(16'h0100);                        // the sensor updates mid-burst
         take(1'b1);
         lsb_got = s_tx;                          // LSB -- from where?
         take(1'b0);
         ev_stop;
         assembled_s = {msb_got, lsb_got};
         $display("T4  an update between the two byte reads, at a carry boundary");
         ck_bit("T4 the latching instance saw the update", s_torn, 1'b1);
         ck_int("T4 but still returned a COHERENT 0x00FF", assembled_s, 16'h00FF);
         // now the same thing without the latch
         do_reset;
         configure(8'h81);
         update(16'h00FF);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = n_tx;
         update(16'h0100);
         take(1'b1);
         lsb_got = n_tx;
         take(1'b0);
         ev_stop;
         assembled_n = {msb_got, lsb_got};
         ck_int("T4 the non-latching instance returned 0x0000", assembled_n, 16'h0000);
         // and the damage, stated numerically
         $display("T4  0x0000 is %0d counts below sample N and %0d below sample N+1",
                  16'h00FF - assembled_n, 16'h0100 - assembled_n);
         ck_int("T4 255 counts below sample N", 16'h00FF - assembled_n, 255);
         if (!(assembled_n < 16'h00FF && assembled_n < 16'h0100)) begin
            $display("  FAIL T4 the torn value should be below BOTH samples");
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T5. The torn value is a value NEITHER sample ever held. That is what makes
         //     it worse than staleness: a stale reading is a real measurement from
         //     the past, and this is not a measurement at all.
         // ----------------------------------------------------------------
         $display("T5  the torn value was never a real sample");
         if (assembled_n == 16'h00FF || assembled_n == 16'h0100) begin
            $display("  FAIL T5 the torn value coincided with a real sample");
            errors = errors + 1;
         end
         ck_int("T5 it is 0x0000, which is neither", assembled_n, 16'h0000);

         // ----------------------------------------------------------------
         // T6. A repeated START ends the burst, so the master gets a FRESH latch.
         //     That is correct -- it is a new burst -- and a master that turns the
         //     transfer around mid-measurement therefore loses coherency.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h7788);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         ck_int("T6 latched 0x7788", s_shadow, 16'h7788);
         take(1'b1);
         update(16'h99AA);                        // a new sample arrives
         ev_start;                                // repeated START: a NEW burst
         $display("T6  a repeated START ends the burst and re-latches");
         ck_bit("T6 the old latch was released", s_held, 1'b0);
         send_addr(1'b1);
         ck_int("T6 the new burst latched the NEW sample", s_shadow, 16'h99AA);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T7. STATUS-FIRST. data_ready distinguishes a fresh sample from a re-read
         //     of one already consumed.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         set_ptr(R_STATUS);
         ev_start; send_addr(1'b1);
         ck_int("T7 no data yet: ready bit clear", s_tx & 8'h01, 8'h00);
         take(1'b0); ev_stop;
         update(16'h4321);
         set_ptr(R_STATUS);
         ev_start; send_addr(1'b1);
         $display("T7  a data-ready bit separates a fresh sample from a re-read");
         ck_int("T7 a sample arrived: ready bit set", s_tx & 8'h01, 8'h01);
         take(1'b0); ev_stop;
         // consume it, and the flag clears
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1); take(1'b1); take(1'b0); ev_stop;
         set_ptr(R_STATUS);
         ev_start; send_addr(1'b1);
         ck_int("T7 consumed: ready bit clear again", s_tx & 8'h01, 8'h00);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T8. The sensor core keeps measuring during a burst. A device that stopped
         //     sampling while being read would be a worse device, so the updates
         //     must continue -- and be counted.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         for (k = 0; k < 4; k = k + 1) begin
            update(16'h0100 + k[15:0]);
            take(1'b1);
         end
         take(1'b0); ev_stop;
         $display("T8  the core keeps measuring while a burst is served");
         ck_int("T8 four updates during the burst", s_upd, 4);
         ck_bit("T8 the torn window was recorded", s_torn, 1'b1);

         // ----------------------------------------------------------------
         // T9. A burst that reads MORE than the measurement walks into the config
         //     register, and those bytes are NOT from the shadow -- only the
         //     measurement registers are latched.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h5A);
         update(16'hBEEF);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         ck_int("T9 MSB", s_tx, 8'hBE); take(1'b1);
         ck_int("T9 LSB", s_tx, 8'hEF); take(1'b1);
         $display("T9  a burst reading past the measurement reaches the config byte");
         ck_int("T9 the config register", s_tx, 8'h5A);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T10. Writes to read-only registers are accepted and discarded by this
         //      device -- Chapter 16.1's question 4, answered the other way.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h1111);
         ev_start; send_addr(1'b0); send_data(R_MSB); send_data(8'hFF);
         $display("T10 a write to the measurement is accepted and discarded");
         ck_bit("T10 accepted", s_ack, 1'b1);
         ev_stop;
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         ck_int("T10 the measurement is unchanged", s_tx, 8'h11);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T11. A wrong address is ignored by both instances.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); byte_in = {7'h69, 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("T11 another device's address is ignored");
         ck_bit("T11 latching instance silent",     s_ack,   1'b0);
         ck_bit("T11 non-latching instance silent", n_ack,   1'b0);
         ck_int("T11 both idle",                    s_state, S_IDLE);

         // ----------------------------------------------------------------
         // T12. The served-byte count, so a bench can prove a burst was actually a
         //      burst rather than several transactions.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h2468);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         take(1'b1); take(1'b1); take(1'b0);
         ev_stop;
         $display("T12 the served-byte count proves a burst was one transaction");
         ck_int("T12 three bytes in one burst", s_srv, 3);

         // ----------------------------------------------------------------
         // T13. THE FLAG MUST MEAN SOMETHING. torn_risk records an update that landed
         //      INSIDE a burst. An update between transactions is the normal case --
         //      it is what the sensor is for -- and flagging those too would make the
         //      signal useless: it would be high on every working device.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h0111);                        // no burst is open
         update(16'h0222);
         $display("T13 an update between transactions is not a torn-read window");
         ck_bit("T13 no burst was open, so no torn risk", s_torn, 1'b0);
         ck_int("T13 the updates were still counted", s_upd, 2);
         // A complete burst with no update inside it: still clean.
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1); take(1'b1); take(1'b0); ev_stop;
         ck_bit("T13 a burst with no update inside it is clean", s_torn, 1'b0);
         // And now one update inside a burst, which must flag.
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         update(16'h0333);
         take(1'b1); take(1'b0); ev_stop;
         ck_bit("T13 an update inside a burst does flag", s_torn, 1'b1);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_shadow.v — the same sensor model in Verilog-2001
   // -----------------------------------------------------------------------------
   // i2c_sensor_shadow.sv
   // Sample coherency: why a multi-byte measurement must be read in one burst.
   //
   // A sensor whose measurement is wider than a byte updates every byte of it at once
   // internally. A master reads them one at a time. If an update lands between two
   // byte reads, the master assembles a value from TWO DIFFERENT SAMPLES -- a torn
   // read -- and the result is not merely stale, it can be a value neither sample
   // ever held.
   //
   //   sample N   = 0x00FF        sample N+1 = 0x0100
   //   master reads the MSB of N (0x00), the sensor updates, master reads the LSB of
   //   N+1 (0x00), and assembles 0x0000 -- 255 counts BELOW both samples.
   //
   // The error is worst exactly where the data is most interesting: at a carry
   // boundary, which is where the measurement is changing fastest.
   //
   // THE DEVICE-SIDE REMEDY. Latch the whole measurement on the first byte read of a
   // burst and serve every subsequent byte from that frozen copy, releasing it at the
   // STOP. A burst then always returns one coherent sample, and the master's only
   // obligation is to read the bytes in one transaction rather than several.
   //
   // This block implements both behaviours, selected by SHADOW_ENABLE, because the
   // difference is the entire point: a sensor without the latch is not broken, it
   // simply pushes the coherency problem onto the master, and a master that reads
   // byte-by-byte from a latching sensor is safe while the same code on a
   // non-latching one is not.
   //
   // Nothing here is in UM10204. Note 2 delegates "all decisions on auto-increment"
   // to the device designer, and a shadow register is one of those decisions. That is
   // why a datasheet specifying a burst read is specifying a CORRECTNESS requirement
   // and not an optimisation -- and why a driver that reads a 16-bit sensor with two
   // single-byte transactions can be wrong on hardware where it appears to work.
   //
   // The block also carries the two other patterns of the chapter:
   //   CONFIGURE-THEN-READ  a config register that must be written before the
   //                        measurement means anything, with a flag if it was not
   //   STATUS-FIRST         a data-ready bit, so a master can tell a fresh sample
   //                        from a re-read of the same one
   // -----------------------------------------------------------------------------

   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_sensor_shadow #(
      parameter         SHADOW_ENABLE = 1'b1,  // latch the sample on the first read
      parameter [6:0]   MY_ADDR       = 7'h68,
      parameter [7:0]   REG_STATUS    = 8'h00,
      parameter [7:0]   REG_MSB       = 8'h01,
      parameter [7:0]   REG_LSB       = 8'h02,
      parameter [7:0]   REG_CONFIG    = 8'h03,
      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,

      // ---- the sensor core, driven by the bench -------------------------------
      input  wire              sample_update,   // pulse: a new measurement is ready
      input  wire [15:0]       sample_in,

      output reg               ack,
      output reg  [7:0]        tx_byte,
      output reg               tx_valid,
      output reg  [7:0]        ptr,
      output reg  [15:0]       live_sample,     // the sensor core's current value
      output reg  [15:0]       shadow,          // the frozen copy a burst serves from
      output reg               shadow_held,     // a burst is in progress
      output reg               configured,      // the config register has been written
      output reg               read_unconfigured, // a measurement was read before that
      output reg               data_ready,      // a fresh sample is waiting
      output reg               torn_risk,       // an update landed mid-burst
      output reg  [2:0]        state,
      output reg  [CNT_W-1:0]  updates_seen,
      output reg  [CNT_W-1:0]  bytes_served
   );

      localparam [2:0] S_IDLE = 3'd0, S_PTR = 3'd1, S_WRITE = 3'd2, S_READ = 3'd3;

      reg [7:0] config_reg;

      // What a read of the current pointer returns. With the shadow enabled a burst
      // serves the frozen copy; without it, every byte comes from the live core.
      function [7:0] read_reg (input [7:0] p, input [15:0] src, input [7:0] cfg,
                               input rdy, input cfgd);
         begin
            if      (p == REG_STATUS) read_reg = {6'b0, cfgd, rdy};
            else if (p == REG_MSB)    read_reg = src[15:8];
            else if (p == REG_LSB)    read_reg = src[7:0];
            else if (p == REG_CONFIG) read_reg = cfg;
            else                      read_reg = 8'h00;
         end
      endfunction

      // Where a read in progress takes its measurement bytes from. Note this is
      // NOT used for the FIRST byte of a burst: at the address byte the shadow is
      // only being captured, so shadow_held is still low and the byte must come
      // from the live core. It is the SECOND and later bytes that must come from
      // the frozen copy, and that is the one place this is read.
      wire [15:0] serving = (SHADOW_ENABLE && shadow_held) ? shadow : live_sample;

      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;
            ptr               <= 8'h00;
            live_sample       <= 16'h0000;
            shadow            <= 16'h0000;
            shadow_held       <= 1'b0;
            configured        <= 1'b0;
            read_unconfigured <= 1'b0;
            data_ready        <= 1'b0;
            torn_risk         <= 1'b0;
            config_reg        <= 8'h00;
            updates_seen      <= {CNT_W{1'b0}};
            bytes_served      <= {CNT_W{1'b0}};
         end else begin
            ack <= 1'b0;

            // -----------------------------------------------------------------
            // The sensor core updates whenever it likes -- including in the middle
            // of a burst, which is the whole hazard. The core does NOT wait for the
            // bus, because a sensor that stopped measuring while being read would be
            // a worse device.
            // -----------------------------------------------------------------
            if (sample_update) begin
               live_sample  <= sample_in;
               data_ready   <= 1'b1;
               updates_seen <= updates_seen + 1'b1;
               // An update during a burst is exactly the torn-read window. With the
               // shadow enabled the burst is unaffected and this is merely recorded;
               // without it, the master is now assembling two samples.
               if (shadow_held) torn_risk <= 1'b1;
            end

            if (start_seen) begin
               state    <= S_IDLE;
               tx_valid <= 1'b0;
               // A repeated START ends the burst, so a master that turns the transfer
               // around gets a FRESH latch -- which is correct: it is a new burst.
               shadow_held <= 1'b0;

            end else if (stop_seen) begin
               state       <= S_IDLE;
               tx_valid    <= 1'b0;
               shadow_held <= 1'b0;        // release the frozen copy

            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 begins. THIS is where the sample is latched, if
                           // the device latches at all.
                           if (SHADOW_ENABLE && !shadow_held) begin
                              shadow      <= live_sample;
                              shadow_held <= 1'b1;
                              tx_byte     <= read_reg(ptr, live_sample, config_reg,
                                                      data_ready, configured);
                           end else begin
                              tx_byte <= read_reg(ptr, live_sample, config_reg,
                                                  data_ready, configured);
                           end
                           // Reading a measurement before configuring the device is a
                           // real and common mistake, and the value returned is
                           // meaningless rather than wrong.
                           if ((ptr == REG_MSB || ptr == REG_LSB) && !configured)
                              read_unconfigured <= 1'b1;
                           tx_valid <= 1'b1;
                           state    <= S_READ;
                        end else begin
                           state <= S_PTR;
                        end
                     end
                  end

                  S_PTR: begin
                     ack   <= 1'b1;
                     ptr   <= byte_in;
                     state <= S_WRITE;
                  end

                  S_WRITE: begin
                     ack <= 1'b1;
                     if (ptr == REG_CONFIG) begin
                        config_reg <= byte_in;
                        configured <= 1'b1;
                     end
                     // Status and the measurement registers are read-only; a write is
                     // accepted and discarded, which is this device's convention.
                     ptr <= ptr + 1'b1;
                  end

                  default: ;
               endcase

            end else if (read_byte_done && state == S_READ) begin
               bytes_served <= bytes_served + 1'b1;
               // Reading the LSB clears data_ready: the sample has been consumed, so
               // a master can tell a fresh measurement from a re-read of the same one.
               if (ptr == REG_LSB) data_ready <= 1'b0;
               if (!master_acked) begin
                  tx_valid <= 1'b0;
                  state    <= S_IDLE;
                  ptr      <= ptr + 1'b1;
               end else begin
                  ptr     <= ptr + 1'b1;
                  // The next byte comes from the frozen copy if one is held.
                  tx_byte <= read_reg(ptr + 1'b1, serving, config_reg,
                                      data_ready, configured);
               end
            end
         end
      end

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_shadow_tb.v — the same oracle in Verilog-2001
   `timescale 1ns/1ps
   // -----------------------------------------------------------------------------
   // i2c_sensor_shadow_tb.sv
   // Independent oracle for i2c_sensor_shadow.
   //
   // Two instances, one latching and one not, see the same byte stream and the same
   // sensor updates. Test 4 is the chapter's worked example: an update injected
   // between the MSB and LSB reads at a carry boundary. The latching instance returns
   // a coherent 0x00FF; the non-latching one returns 0x0000, which is 255 counts below
   // BOTH samples and a value neither ever held.
   //
   //   dut_s : SHADOW_ENABLE = 1   latches the sample on the first read of a burst
   //   dut_n : SHADOW_ENABLE = 0   serves every byte from the live core
   // -----------------------------------------------------------------------------
   // (Verilog-2001 -- structurally identical to the SystemVerilog above.)
   module i2c_sensor_shadow_tb;

      localparam [2:0] S_IDLE = 3'd0, S_PTR = 3'd1, S_WRITE = 3'd2, S_READ = 3'd3;
      localparam [6:0] ADDR = 7'h68;
      localparam [7:0] R_STATUS = 8'h00, R_MSB = 8'h01, R_LSB = 8'h02, R_CONFIG = 8'h03;

      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        sample_update = 1'b0;
      reg [15:0] sample_in = 16'h0000;

      wire        s_ack, s_txv, s_held, s_cfgd, s_ruc, s_rdy, s_torn;
      wire [7:0]  s_tx, s_ptr;
      wire [15:0] s_live, s_shadow;
      wire [2:0]  s_state;
      wire [7:0]  s_upd, s_srv;

      wire        n_ack, n_txv, n_held, n_cfgd, n_ruc, n_rdy, n_torn;
      wire [7:0]  n_tx, n_ptr;
      wire [15:0] n_live, n_shadow;
      wire [2:0]  n_state;
      wire [7:0]  n_upd, n_srv;

      integer errors = 0;
      integer k;
      reg [7:0] msb_got, lsb_got;
      reg [15:0] assembled_s, assembled_n;

      i2c_sensor_shadow #(.SHADOW_ENABLE(1'b1), .MY_ADDR(ADDR), .CNT_W(8)) dut_s (
         .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),
         .sample_update(sample_update), .sample_in(sample_in),
         .ack(s_ack), .tx_byte(s_tx), .tx_valid(s_txv), .ptr(s_ptr),
         .live_sample(s_live), .shadow(s_shadow), .shadow_held(s_held),
         .configured(s_cfgd), .read_unconfigured(s_ruc), .data_ready(s_rdy),
         .torn_risk(s_torn), .state(s_state),
         .updates_seen(s_upd), .bytes_served(s_srv));

      i2c_sensor_shadow #(.SHADOW_ENABLE(1'b0), .MY_ADDR(ADDR), .CNT_W(8)) dut_n (
         .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),
         .sample_update(sample_update), .sample_in(sample_in),
         .ack(n_ack), .tx_byte(n_tx), .tx_valid(n_txv), .ptr(n_ptr),
         .live_sample(n_live), .shadow(n_shadow), .shadow_held(n_held),
         .configured(n_cfgd), .read_unconfigured(n_ruc), .data_ready(n_rdy),
         .torn_risk(n_torn), .state(n_state),
         .updates_seen(n_upd), .bytes_served(n_srv));

      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;
            sample_update = 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

      task update (input [15:0] v);
         begin
            @(negedge clk); sample_in = v; sample_update = 1'b1;
            @(posedge clk); @(negedge clk); sample_update = 1'b0;
         end
      endtask

      task configure (input [7:0] v);
         begin
            ev_start; send_addr(1'b0); send_data(R_CONFIG); send_data(v); ev_stop;
         end
      endtask

      task set_ptr (input [7:0] p);
         begin
            ev_start; send_addr(1'b0); send_data(p); ev_stop;
         end
      endtask

      task ck_int (input [200*8:1] what, input integer g, input integer e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0d (0x%0h) expected %0d (0x%0h)", what, g, g, e, e);
               errors = errors + 1;
            end
         end
      endtask

      task ck_bit (input [200*8:1] what, input g, input e);
         begin
            if (g !== e) begin
               $display("  FAIL %0s: got %0b expected %0b", what, g, e);
               errors = errors + 1;
            end
         end
      endtask

      initial begin
         $display("=== i2c_sensor_shadow: one sample, two bytes, and the gap between them ===");

         // ----------------------------------------------------------------
         // T1. CONFIGURE-THEN-READ. A measurement read before the config register is
         //     written is meaningless, and the device says so.
         // ----------------------------------------------------------------
         do_reset;
         update(16'h1234);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         $display("T1  reading a measurement before configuring is flagged");
         ck_bit("T1 not configured", s_cfgd, 1'b0);
         ck_bit("T1 flagged as read-unconfigured", s_ruc, 1'b1);
         take(1'b0); ev_stop;
         configure(8'h81);
         ck_bit("T1 now configured", s_cfgd, 1'b1);

         // ----------------------------------------------------------------
         // T2. A clean burst read with no update in the middle. Both instances agree,
         //     which is the case that makes the hazard invisible in testing.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'hABCD);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = s_tx; take(1'b1);
         lsb_got = s_tx; take(1'b0);
         ev_stop;
         assembled_s = {msb_got, lsb_got};
         $display("T2  an undisturbed burst: both instances agree");
         ck_int("T2 latching instance assembled 0xABCD", assembled_s, 16'hABCD);
         // the same stream through the non-latching instance
         do_reset;
         configure(8'h81);
         update(16'hABCD);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = n_tx; take(1'b1);
         lsb_got = n_tx; take(1'b0);
         ev_stop;
         assembled_n = {msb_got, lsb_got};
         ck_int("T2 non-latching instance also 0xABCD", assembled_n, 16'hABCD);

         // ----------------------------------------------------------------
         // T3. The latch is taken on the FIRST read of a burst, and held.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h5566);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         $display("T3  the sample is latched on the first read of a burst");
         ck_bit("T3 the latch is held", s_held,   1'b1);
         ck_int("T3 and holds the sample", s_shadow, 16'h5566);
         ck_bit("T3 the non-latching instance holds nothing", n_held, 1'b0);
         take(1'b0); ev_stop;
         ck_bit("T3 released at the STOP", s_held, 1'b0);

         // ----------------------------------------------------------------
         // T4. THE CHAPTER'S WORKED EXAMPLE. A carry boundary crossed between the
         //     two byte reads. 0x00FF becomes 0x0100 mid-burst.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h00FF);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = s_tx;                          // MSB of sample N = 0x00
         update(16'h0100);                        // the sensor updates mid-burst
         take(1'b1);
         lsb_got = s_tx;                          // LSB -- from where?
         take(1'b0);
         ev_stop;
         assembled_s = {msb_got, lsb_got};
         $display("T4  an update between the two byte reads, at a carry boundary");
         ck_bit("T4 the latching instance saw the update", s_torn, 1'b1);
         ck_int("T4 but still returned a COHERENT 0x00FF", assembled_s, 16'h00FF);
         // now the same thing without the latch
         do_reset;
         configure(8'h81);
         update(16'h00FF);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         msb_got = n_tx;
         update(16'h0100);
         take(1'b1);
         lsb_got = n_tx;
         take(1'b0);
         ev_stop;
         assembled_n = {msb_got, lsb_got};
         ck_int("T4 the non-latching instance returned 0x0000", assembled_n, 16'h0000);
         // and the damage, stated numerically
         $display("T4  0x0000 is %0d counts below sample N and %0d below sample N+1",
                  16'h00FF - assembled_n, 16'h0100 - assembled_n);
         ck_int("T4 255 counts below sample N", 16'h00FF - assembled_n, 255);
         if (!(assembled_n < 16'h00FF && assembled_n < 16'h0100)) begin
            $display("  FAIL T4 the torn value should be below BOTH samples");
            errors = errors + 1;
         end

         // ----------------------------------------------------------------
         // T5. The torn value is a value NEITHER sample ever held. That is what makes
         //     it worse than staleness: a stale reading is a real measurement from
         //     the past, and this is not a measurement at all.
         // ----------------------------------------------------------------
         $display("T5  the torn value was never a real sample");
         if (assembled_n == 16'h00FF || assembled_n == 16'h0100) begin
            $display("  FAIL T5 the torn value coincided with a real sample");
            errors = errors + 1;
         end
         ck_int("T5 it is 0x0000, which is neither", assembled_n, 16'h0000);

         // ----------------------------------------------------------------
         // T6. A repeated START ends the burst, so the master gets a FRESH latch.
         //     That is correct -- it is a new burst -- and a master that turns the
         //     transfer around mid-measurement therefore loses coherency.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h7788);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         ck_int("T6 latched 0x7788", s_shadow, 16'h7788);
         take(1'b1);
         update(16'h99AA);                        // a new sample arrives
         ev_start;                                // repeated START: a NEW burst
         $display("T6  a repeated START ends the burst and re-latches");
         ck_bit("T6 the old latch was released", s_held, 1'b0);
         send_addr(1'b1);
         ck_int("T6 the new burst latched the NEW sample", s_shadow, 16'h99AA);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T7. STATUS-FIRST. data_ready distinguishes a fresh sample from a re-read
         //     of one already consumed.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         set_ptr(R_STATUS);
         ev_start; send_addr(1'b1);
         ck_int("T7 no data yet: ready bit clear", s_tx & 8'h01, 8'h00);
         take(1'b0); ev_stop;
         update(16'h4321);
         set_ptr(R_STATUS);
         ev_start; send_addr(1'b1);
         $display("T7  a data-ready bit separates a fresh sample from a re-read");
         ck_int("T7 a sample arrived: ready bit set", s_tx & 8'h01, 8'h01);
         take(1'b0); ev_stop;
         // consume it, and the flag clears
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1); take(1'b1); take(1'b0); ev_stop;
         set_ptr(R_STATUS);
         ev_start; send_addr(1'b1);
         ck_int("T7 consumed: ready bit clear again", s_tx & 8'h01, 8'h00);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T8. The sensor core keeps measuring during a burst. A device that stopped
         //     sampling while being read would be a worse device, so the updates
         //     must continue -- and be counted.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         for (k = 0; k < 4; k = k + 1) begin
            update(16'h0100 + k[15:0]);
            take(1'b1);
         end
         take(1'b0); ev_stop;
         $display("T8  the core keeps measuring while a burst is served");
         ck_int("T8 four updates during the burst", s_upd, 4);
         ck_bit("T8 the torn window was recorded", s_torn, 1'b1);

         // ----------------------------------------------------------------
         // T9. A burst that reads MORE than the measurement walks into the config
         //     register, and those bytes are NOT from the shadow -- only the
         //     measurement registers are latched.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h5A);
         update(16'hBEEF);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         ck_int("T9 MSB", s_tx, 8'hBE); take(1'b1);
         ck_int("T9 LSB", s_tx, 8'hEF); take(1'b1);
         $display("T9  a burst reading past the measurement reaches the config byte");
         ck_int("T9 the config register", s_tx, 8'h5A);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T10. Writes to read-only registers are accepted and discarded by this
         //      device -- Chapter 16.1's question 4, answered the other way.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h1111);
         ev_start; send_addr(1'b0); send_data(R_MSB); send_data(8'hFF);
         $display("T10 a write to the measurement is accepted and discarded");
         ck_bit("T10 accepted", s_ack, 1'b1);
         ev_stop;
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         ck_int("T10 the measurement is unchanged", s_tx, 8'h11);
         take(1'b0); ev_stop;

         // ----------------------------------------------------------------
         // T11. A wrong address is ignored by both instances.
         // ----------------------------------------------------------------
         do_reset;
         @(negedge clk); byte_in = {7'h69, 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("T11 another device's address is ignored");
         ck_bit("T11 latching instance silent",     s_ack,   1'b0);
         ck_bit("T11 non-latching instance silent", n_ack,   1'b0);
         ck_int("T11 both idle",                    s_state, S_IDLE);

         // ----------------------------------------------------------------
         // T12. The served-byte count, so a bench can prove a burst was actually a
         //      burst rather than several transactions.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h2468);
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         take(1'b1); take(1'b1); take(1'b0);
         ev_stop;
         $display("T12 the served-byte count proves a burst was one transaction");
         ck_int("T12 three bytes in one burst", s_srv, 3);

         // ----------------------------------------------------------------
         // T13. THE FLAG MUST MEAN SOMETHING. torn_risk records an update that landed
         //      INSIDE a burst. An update between transactions is the normal case --
         //      it is what the sensor is for -- and flagging those too would make the
         //      signal useless: it would be high on every working device.
         // ----------------------------------------------------------------
         do_reset;
         configure(8'h81);
         update(16'h0111);                        // no burst is open
         update(16'h0222);
         $display("T13 an update between transactions is not a torn-read window");
         ck_bit("T13 no burst was open, so no torn risk", s_torn, 1'b0);
         ck_int("T13 the updates were still counted", s_upd, 2);
         // A complete burst with no update inside it: still clean.
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1); take(1'b1); take(1'b0); ev_stop;
         ck_bit("T13 a burst with no update inside it is clean", s_torn, 1'b0);
         // And now one update inside a burst, which must flag.
         set_ptr(R_MSB);
         ev_start; send_addr(1'b1);
         update(16'h0333);
         take(1'b1); take(1'b0); ev_stop;
         ck_bit("T13 an update inside a burst does flag", s_torn, 1'b1);

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

   endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_shadow.vhd — the same sensor model in VHDL-2008
   -- ---------------------------------------------------------------------------
   -- i2c_sensor_shadow.vhd
   -- Sample coherency: why a multi-byte measurement must be read in one burst.
   -- Behavioural twin of i2c_sensor_shadow.sv / .v.
   --
   -- A sensor whose measurement is wider than a byte updates every byte of it at once
   -- internally. A master reads them one at a time. If an update lands between two
   -- byte reads, the master assembles a value from TWO DIFFERENT SAMPLES -- a torn
   -- read -- and the result can be a value neither sample ever held.
   --
   --   sample N   = 0x00FF        sample N+1 = 0x0100
   --   read the MSB of N (0x00), the sensor updates, read the LSB of N+1 (0x00),
   --   assemble 0x0000 -- 255 counts BELOW both samples.
   --
   -- The error is worst exactly where the data is most interesting: at a carry
   -- boundary, which is where the measurement is changing fastest.
   --
   -- THE DEVICE-SIDE REMEDY. Latch the whole measurement on the first byte read of a
   -- burst, serve every subsequent byte from that frozen copy, and release it at the
   -- STOP. A burst then always returns one coherent sample.
   --
   -- Both behaviours are implemented, selected by SHADOW_ENABLE, because the
   -- difference is the point: a sensor without the latch is not broken, it pushes the
   -- coherency problem onto the master.
   --
   -- Nothing here is in UM10204. Note 2 delegates "all decisions on auto-increment"
   -- to the device designer, and a shadow register is one of those decisions -- which
   -- is why a datasheet specifying a burst read is specifying CORRECTNESS.
   -- ---------------------------------------------------------------------------

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

   entity i2c_sensor_shadow is
      generic (
         SHADOW_ENABLE : std_logic := '1';   -- latch the sample on the first read
         MY_ADDR       : std_logic_vector(6 downto 0) := "1101000";   -- 0x68
         REG_STATUS    : std_logic_vector(7 downto 0) := x"00";
         REG_MSB       : std_logic_vector(7 downto 0) := x"01";
         REG_LSB       : std_logic_vector(7 downto 0) := x"02";
         REG_CONFIG    : std_logic_vector(7 downto 0) := x"03";
         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;

         -- the sensor core, driven by the bench
         sample_update : in std_logic;
         sample_in     : in std_logic_vector(15 downto 0);

         ack               : out std_logic;
         tx_byte           : out std_logic_vector(7 downto 0);
         tx_valid          : out std_logic;
         ptr               : out std_logic_vector(7 downto 0);
         live_sample       : out std_logic_vector(15 downto 0);
         shadow            : out std_logic_vector(15 downto 0);
         shadow_held       : out std_logic;
         configured        : out std_logic;
         read_unconfigured : out std_logic;
         data_ready        : out std_logic;
         torn_risk         : out std_logic;
         state             : out unsigned(2 downto 0);
         updates_seen      : out unsigned(CNT_W-1 downto 0);
         bytes_served      : out unsigned(CNT_W-1 downto 0)
      );
   end entity i2c_sensor_shadow;

   architecture rtl of i2c_sensor_shadow is

      constant ST_IDLE  : integer := 0;
      constant ST_PTR   : integer := 1;
      constant ST_WRITE : integer := 2;
      constant ST_READ  : integer := 3;

      signal st      : integer := ST_IDLE;
      signal p       : std_logic_vector(7 downto 0) := (others => '0');
      signal live    : std_logic_vector(15 downto 0) := (others => '0');
      signal shad    : std_logic_vector(15 downto 0) := (others => '0');
      signal held    : std_logic := '0';
      signal cfg     : std_logic_vector(7 downto 0) := (others => '0');
      signal cfgd    : std_logic := '0';
      signal rdy     : std_logic := '0';
      signal n_upd   : integer := 0;
      signal n_srv   : integer := 0;

      -- What a read of a given pointer returns from a given source.
      function read_reg (pp  : std_logic_vector(7 downto 0);
                         src : std_logic_vector(15 downto 0);
                         c   : std_logic_vector(7 downto 0);
                         r   : std_logic;
                         d   : std_logic) return std_logic_vector is
      begin
         if    pp = REG_STATUS then return "000000" & d & r;
         elsif pp = REG_MSB    then return src(15 downto 8);
         elsif pp = REG_LSB    then return src(7 downto 0);
         elsif pp = REG_CONFIG then return c;
         else                       return x"00";
         end if;
      end function;

   begin

      state        <= to_unsigned(st, 3);
      ptr          <= p;
      live_sample  <= live;
      shadow       <= shad;
      shadow_held  <= held;
      configured   <= cfgd;
      data_ready   <= rdy;
      updates_seen <= to_unsigned(n_upd, CNT_W);
      bytes_served <= to_unsigned(n_srv, CNT_W);

      process (clk, rst_n)
         variable src : std_logic_vector(15 downto 0);
         variable nxt : std_logic_vector(7 downto 0);
      begin
         if rst_n = '0' then
            st                <= ST_IDLE;
            ack               <= '0';
            tx_byte           <= (others => '0');
            tx_valid          <= '0';
            p                 <= (others => '0');
            live              <= (others => '0');
            shad              <= (others => '0');
            held              <= '0';
            cfg               <= (others => '0');
            cfgd              <= '0';
            read_unconfigured <= '0';
            rdy               <= '0';
            torn_risk         <= '0';
            n_upd             <= 0;
            n_srv             <= 0;

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

            -- The sensor core updates whenever it likes, including mid-burst, which
            -- is the whole hazard. A sensor that stopped measuring while being read
            -- would be a worse device.
            if sample_update = '1' then
               live  <= sample_in;
               rdy   <= '1';
               n_upd <= n_upd + 1;
               if held = '1' then
                  torn_risk <= '1';
               end if;
            end if;

            if start_seen = '1' then
               st       <= ST_IDLE;
               tx_valid <= '0';
               -- A repeated START ends the burst, so a master that turns the transfer
               -- around gets a FRESH latch -- correct, because it is a new burst.
               held <= '0';

            elsif stop_seen = '1' then
               st       <= ST_IDLE;
               tx_valid <= '0';
               held     <= '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
                           -- A read begins. THIS is where the sample is latched, if
                           -- the device latches at all.
                           if SHADOW_ENABLE = '1' and held = '0' then
                              shad <= live;
                              held <= '1';
                           end if;
                           tx_byte <= read_reg(p, live, cfg, rdy, cfgd);
                           -- Reading a measurement before configuring is a real and
                           -- common mistake; the value is meaningless, not wrong.
                           if (p = REG_MSB or p = REG_LSB) and cfgd = '0' then
                              read_unconfigured <= '1';
                           end if;
                           tx_valid <= '1';
                           st       <= ST_READ;
                        else
                           st <= ST_PTR;
                        end if;
                     end if;

                  when ST_PTR =>
                     ack <= '1';
                     p   <= byte_in;
                     st  <= ST_WRITE;

                  when ST_WRITE =>
                     ack <= '1';
                     if p = REG_CONFIG then
                        cfg  <= byte_in;
                        cfgd <= '1';
                     end if;
                     -- Status and the measurement registers are read-only; a write is
                     -- accepted and discarded, which is this device's convention.
                     p <= std_logic_vector(unsigned(p) + 1);

                  when others =>
                     null;

               end case;

            elsif read_byte_done = '1' and st = ST_READ then
               n_srv <= n_srv + 1;
               -- Reading the LSB clears data_ready: the sample has been consumed.
               if p = REG_LSB then
                  rdy <= '0';
               end if;
               if master_acked = '0' then
                  tx_valid <= '0';
                  st       <= ST_IDLE;
                  p        <= std_logic_vector(unsigned(p) + 1);
               else
                  nxt := std_logic_vector(unsigned(p) + 1);
                  p   <= nxt;
                  if SHADOW_ENABLE = '1' and held = '1' then
                     src := shad;
                  else
                     src := live;
                  end if;
                  tx_byte <= read_reg(nxt, src, cfg, rdy, cfgd);
               end if;
            end if;
         end if;
      end process;

   end architecture rtl;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_shadow_tb.vhd — the same oracle in VHDL-2008
   -- ---------------------------------------------------------------------------
   -- i2c_sensor_shadow_tb.vhd
   -- Independent oracle for i2c_sensor_shadow. Behavioural twin of the SystemVerilog
   -- and Verilog benches.
   --
   -- Two instances, one latching and one not, see the same byte stream and the same
   -- sensor updates. Test 4 is the chapter's worked example: an update injected between
   -- the MSB and LSB reads at a carry boundary. The latching instance returns a
   -- coherent 0x00FF; the non-latching one returns 0x0000, which is 255 counts below
   -- BOTH samples and a value neither ever held.
   --
   --   dut_s : SHADOW_ENABLE = '1'   latches the sample on the first read of a burst
   --   dut_n : SHADOW_ENABLE = '0'   serves every byte from the live core
   -- ---------------------------------------------------------------------------

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

   entity i2c_sensor_shadow_tb is
   end entity i2c_sensor_shadow_tb;

   architecture sim of i2c_sensor_shadow_tb is

      constant TCLK : time := 10 ns;

      constant ST_IDLE : integer := 0;

      constant ADDR     : std_logic_vector(6 downto 0) := "1101000";   -- 0x68
      constant R_STATUS : std_logic_vector(7 downto 0) := x"00";
      constant R_MSB    : std_logic_vector(7 downto 0) := x"01";
      constant R_LSB    : std_logic_vector(7 downto 0) := x"02";
      constant R_CONFIG  : std_logic_vector(7 downto 0) := x"03";

      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) := x"00";
      signal is_addr_byte   : std_logic := '0';
      signal read_byte_done : std_logic := '0';
      signal master_acked   : std_logic := '0';
      signal sample_update  : std_logic := '0';
      signal sample_in      : std_logic_vector(15 downto 0) := x"0000";

      signal s_ack, s_txv, s_held, s_cfgd, s_ruc, s_rdy, s_torn : std_logic;
      signal s_tx, s_ptr        : std_logic_vector(7 downto 0);
      signal s_live, s_shadow   : std_logic_vector(15 downto 0);
      signal s_state            : unsigned(2 downto 0);
      signal s_upd, s_srv       : unsigned(7 downto 0);

      signal n_ack, n_txv, n_held, n_cfgd, n_ruc, n_rdy, n_torn : std_logic;
      signal n_tx, n_ptr        : std_logic_vector(7 downto 0);
      signal n_live, n_shadow   : std_logic_vector(15 downto 0);
      signal n_state            : unsigned(2 downto 0);
      signal n_upd, n_srv       : unsigned(7 downto 0);

      signal halt : boolean := false;

   begin

      dut_s : entity work.i2c_sensor_shadow
         generic map (SHADOW_ENABLE => '1', 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,
            sample_update => sample_update, sample_in => sample_in,
            ack => s_ack, tx_byte => s_tx, tx_valid => s_txv, ptr => s_ptr,
            live_sample => s_live, shadow => s_shadow, shadow_held => s_held,
            configured => s_cfgd, read_unconfigured => s_ruc, data_ready => s_rdy,
            torn_risk => s_torn, state => s_state,
            updates_seen => s_upd, bytes_served => s_srv);

      dut_n : entity work.i2c_sensor_shadow
         generic map (SHADOW_ENABLE => '0', 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,
            sample_update => sample_update, sample_in => sample_in,
            ack => n_ack, tx_byte => n_tx, tx_valid => n_txv, ptr => n_ptr,
            live_sample => n_live, shadow => n_shadow, shadow_held => n_held,
            configured => n_cfgd, read_unconfigured => n_ruc, data_ready => n_rdy,
            torn_risk => n_torn, state => n_state,
            updates_seen => n_upd, bytes_served => n_srv);

      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;
         variable msb_got, lsb_got : std_logic_vector(7 downto 0);
         variable asm_s, asm_n     : std_logic_vector(15 downto 0);

         procedure ck_int (what : string; g : integer; e : integer) is
         begin
            if g /= e then
               report "  FAIL " & what & ": got " & integer'image(g)
                      & " expected " & integer'image(e) severity note;
               err := err + 1;
            end if;
         end procedure;

         procedure ck_bit (what : string; g : std_logic; e : std_logic) is
         begin
            if g /= e then
               report "  FAIL " & what & ": got " & std_logic'image(g)
                      & " expected " & std_logic'image(e) severity note;
               err := err + 1;
            end if;
         end procedure;

         procedure step is
         begin
            wait until rising_edge(clk); wait until falling_edge(clk);
         end procedure;

         procedure do_reset 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';
            sample_update <= '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 update (v : std_logic_vector(15 downto 0)) is
         begin
            wait until falling_edge(clk);
            sample_in <= v; sample_update <= '1';
            wait until rising_edge(clk); wait until falling_edge(clk);
            sample_update <= '0';
         end procedure;

         procedure configure (v : std_logic_vector(7 downto 0)) is
         begin
            ev_start; send_addr('0'); send_data(R_CONFIG); send_data(v); ev_stop;
         end procedure;

         procedure set_ptr (p : std_logic_vector(7 downto 0)) is
         begin
            ev_start; send_addr('0'); send_data(p); ev_stop;
         end procedure;

      begin
         report "=== i2c_sensor_shadow: one sample, two bytes, and the gap between them ==="
                severity note;

         -- T1. A measurement read before the config register is written is
         --     meaningless, and the device says so.
         do_reset;
         update(x"1234");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         report "T1  reading a measurement before configuring is flagged" severity note;
         ck_bit("T1 not configured", s_cfgd, '0');
         ck_bit("T1 flagged as read-unconfigured", s_ruc, '1');
         take('0'); ev_stop;
         configure(x"81");
         ck_bit("T1 now configured", s_cfgd, '1');

         -- T2. A clean burst read with no update in the middle. Both instances agree,
         --     which is the case that makes the hazard invisible in testing.
         do_reset;
         configure(x"81");
         update(x"ABCD");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         msb_got := s_tx; take('1');
         lsb_got := s_tx; take('0');
         ev_stop;
         asm_s := msb_got & lsb_got;
         report "T2  an undisturbed burst: both instances agree" severity note;
         ck_int("T2 latching instance assembled 0xABCD",
                to_integer(unsigned(asm_s)), 16#ABCD#);
         do_reset;
         configure(x"81");
         update(x"ABCD");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         msb_got := n_tx; take('1');
         lsb_got := n_tx; take('0');
         ev_stop;
         asm_n := msb_got & lsb_got;
         ck_int("T2 non-latching instance also 0xABCD",
                to_integer(unsigned(asm_n)), 16#ABCD#);

         -- T3. The latch is taken on the FIRST read of a burst, and held.
         do_reset;
         configure(x"81");
         update(x"5566");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         report "T3  the sample is latched on the first read of a burst" severity note;
         ck_bit("T3 the latch is held", s_held, '1');
         ck_int("T3 and holds the sample", to_integer(unsigned(s_shadow)), 16#5566#);
         ck_bit("T3 the non-latching instance holds nothing", n_held, '0');
         take('0'); ev_stop;
         ck_bit("T3 released at the STOP", s_held, '0');

         -- T4. THE CHAPTER'S WORKED EXAMPLE. A carry boundary crossed between the two
         --     byte reads. 0x00FF becomes 0x0100 mid-burst.
         do_reset;
         configure(x"81");
         update(x"00FF");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         msb_got := s_tx;                       -- MSB of sample N = 0x00
         update(x"0100");                       -- the sensor updates mid-burst
         take('1');
         lsb_got := s_tx;                       -- LSB -- from where?
         take('0');
         ev_stop;
         asm_s := msb_got & lsb_got;
         report "T4  an update between the two byte reads, at a carry boundary"
                severity note;
         ck_bit("T4 the latching instance saw the update", s_torn, '1');
         ck_int("T4 but still returned a COHERENT 0x00FF",
                to_integer(unsigned(asm_s)), 16#00FF#);
         do_reset;
         configure(x"81");
         update(x"00FF");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         msb_got := n_tx;
         update(x"0100");
         take('1');
         lsb_got := n_tx;
         take('0');
         ev_stop;
         asm_n := msb_got & lsb_got;
         ck_int("T4 the non-latching instance returned 0x0000",
                to_integer(unsigned(asm_n)), 0);
         report "T4  0x0000 is "
                & integer'image(16#00FF# - to_integer(unsigned(asm_n)))
                & " counts below sample N and "
                & integer'image(16#0100# - to_integer(unsigned(asm_n)))
                & " below sample N+1" severity note;
         ck_int("T4 255 counts below sample N",
                16#00FF# - to_integer(unsigned(asm_n)), 255);
         if not (to_integer(unsigned(asm_n)) < 16#00FF#
                 and to_integer(unsigned(asm_n)) < 16#0100#) then
            report "  FAIL T4 the torn value should be below BOTH samples" severity note;
            err := err + 1;
         end if;

         -- T5. The torn value is a value NEITHER sample ever held.
         report "T5  the torn value was never a real sample" severity note;
         if to_integer(unsigned(asm_n)) = 16#00FF#
            or to_integer(unsigned(asm_n)) = 16#0100# then
            report "  FAIL T5 the torn value coincided with a real sample" severity note;
            err := err + 1;
         end if;
         ck_int("T5 it is 0x0000, which is neither", to_integer(unsigned(asm_n)), 0);

         -- T6. A repeated START ends the burst, so the master gets a FRESH latch.
         do_reset;
         configure(x"81");
         update(x"7788");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         ck_int("T6 latched 0x7788", to_integer(unsigned(s_shadow)), 16#7788#);
         take('1');
         update(x"99AA");                       -- a new sample arrives
         ev_start;                              -- repeated START: a NEW burst
         report "T6  a repeated START ends the burst and re-latches" severity note;
         ck_bit("T6 the old latch was released", s_held, '0');
         send_addr('1');
         ck_int("T6 the new burst latched the NEW sample",
                to_integer(unsigned(s_shadow)), 16#99AA#);
         take('0'); ev_stop;

         -- T7. data_ready distinguishes a fresh sample from a re-read of one already
         --     consumed.
         do_reset;
         configure(x"81");
         set_ptr(R_STATUS);
         ev_start; send_addr('1');
         ck_int("T7 no data yet: ready bit clear",
                to_integer(unsigned(s_tx and x"01")), 0);
         take('0'); ev_stop;
         update(x"4321");
         set_ptr(R_STATUS);
         ev_start; send_addr('1');
         report "T7  a data-ready bit separates a fresh sample from a re-read"
                severity note;
         ck_int("T7 a sample arrived: ready bit set",
                to_integer(unsigned(s_tx and x"01")), 1);
         take('0'); ev_stop;
         set_ptr(R_MSB);
         ev_start; send_addr('1'); take('1'); take('0'); ev_stop;
         set_ptr(R_STATUS);
         ev_start; send_addr('1');
         ck_int("T7 consumed: ready bit clear again",
                to_integer(unsigned(s_tx and x"01")), 0);
         take('0'); ev_stop;

         -- T8. The sensor core keeps measuring during a burst, and the updates are
         --     counted.
         do_reset;
         configure(x"81");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         for k in 0 to 3 loop
            update(std_logic_vector(to_unsigned(16#0100# + k, 16)));
            take('1');
         end loop;
         take('0'); ev_stop;
         report "T8  the core keeps measuring while a burst is served" severity note;
         ck_int("T8 four updates during the burst", to_integer(s_upd), 4);
         ck_bit("T8 the torn window was recorded", s_torn, '1');

         -- T9. A burst that reads MORE than the measurement walks into the config
         --     register, and those bytes are NOT from the shadow.
         do_reset;
         configure(x"5A");
         update(x"BEEF");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         ck_int("T9 MSB", to_integer(unsigned(s_tx)), 16#BE#); take('1');
         ck_int("T9 LSB", to_integer(unsigned(s_tx)), 16#EF#); take('1');
         report "T9  a burst reading past the measurement reaches the config byte"
                severity note;
         ck_int("T9 the config register", to_integer(unsigned(s_tx)), 16#5A#);
         take('0'); ev_stop;

         -- T10. Writes to read-only registers are accepted and discarded by this
         --      device -- Chapter 16.1's question 4, answered the other way.
         do_reset;
         configure(x"81");
         update(x"1111");
         ev_start; send_addr('0'); send_data(R_MSB); send_data(x"FF");
         report "T10 a write to the measurement is accepted and discarded" severity note;
         ck_bit("T10 accepted", s_ack, '1');
         ev_stop;
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         ck_int("T10 the measurement is unchanged", to_integer(unsigned(s_tx)), 16#11#);
         take('0'); ev_stop;

         -- T11. A wrong address is ignored by both instances.
         do_reset;
         wait until falling_edge(clk);
         byte_in <= "1101001" & '0'; 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 "T11 another device's address is ignored" severity note;
         ck_bit("T11 latching instance silent",     s_ack, '0');
         ck_bit("T11 non-latching instance silent", n_ack, '0');
         ck_int("T11 both idle", to_integer(s_state), ST_IDLE);

         -- T12. The served-byte count, so a bench can prove a burst was actually a
         --      burst rather than several transactions.
         do_reset;
         configure(x"81");
         update(x"2468");
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         take('1'); take('1'); take('0');
         ev_stop;
         report "T12 the served-byte count proves a burst was one transaction"
                severity note;
         ck_int("T12 three bytes in one burst", to_integer(s_srv), 3);

         -- T13. THE FLAG MUST MEAN SOMETHING. torn_risk records an update that landed
         --      INSIDE a burst. An update between transactions is the normal case -- it
         --      is what the sensor is for -- and flagging those too would make the
         --      signal useless: it would be high on every working device.
         do_reset;
         configure(x"81");
         update(x"0111");                         -- no burst is open
         update(x"0222");
         report "T13 an update between transactions is not a torn-read window"
                severity note;
         ck_bit("T13 no burst was open, so no torn risk", s_torn, '0');
         ck_int("T13 the updates were still counted", to_integer(s_upd), 2);
         -- A complete burst with no update inside it: still clean.
         set_ptr(R_MSB);
         ev_start; send_addr('1'); take('1'); take('0'); ev_stop;
         ck_bit("T13 a burst with no update inside it is clean", s_torn, '0');
         -- And now one update inside a burst, which must flag.
         set_ptr(R_MSB);
         ev_start; send_addr('1');
         update(x"0333");
         take('1'); take('0'); ev_stop;
         ck_bit("T13 an update inside a burst does flag", s_torn, '1');

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

   end architecture sim;

7a. Decisions Worth Defending

The latch is taken on the FIRST read of a burst, and released at the STOP. Not on every byte — on the addressing that starts the burst. That is what makes a burst coherent, and it is why shadow_held exists as a separate flag from shadow itself.

The first byte of a burst comes from the live core, not the shadow. This looks like a bug and is not. At the address byte the shadow is being captured in the same clock, so shadow_held is still low and the byte must come from the live sample — which is the same value the shadow is capturing. It is the second and later bytes that must come from the frozen copy, and the named serving signal is read at exactly that one place. Getting this wrong by one cycle would serve the previous burst's sample as the first byte.

The sensor core updates whenever it likes, including mid-burst. sample_update is honoured unconditionally. A device that stopped measuring while being read would be a worse device, and the whole hazard exists because it does not stop. Test 8 counts four updates during one burst and asserts they all landed.

torn_risk records an update that landed INSIDE a burst, and nothing else. An update between transactions is the normal case — it is what the sensor is for — so flagging those too would make the signal useless: it would be high on every working device. Mutation E2-7 flags every update and is killed by two checks, and §8 explains why that mutation survived the first version of the suite.

A repeated START releases the latch, so a new burst re-latches. Correct, and it means a master that turns the transfer around mid-measurement loses coherency. Mutation E2-4 holds the latch across a repeated START — serving the old sample to a new burst — and two checks fail.

data_ready is cleared on the LSB read, not the first byte. The sample has been consumed only once both halves are out. Clearing it on the first byte would mark a sample consumed while the master still had half of it to fetch, and a master polling the status bit between the two reads would see a stale answer. Mutation E2-8 never clears it and one check fails.

Writes to the measurement registers are accepted and discarded. Chapter 16.1's question 4, answered the other way from the register file — deliberately, so the module shows both conventions in working designs rather than describing one and asserting the other exists. Test 10 proves the write is acknowledged and the data unchanged.

read_unconfigured flags a measurement read before the config register is written. §6's callout. It is a device reporting a master's mistake, which is the only kind of reporting available for a mistake that produces a plausible number.

A burst that reads past the measurement reaches the config byte, and that byte is NOT latched. Only the measurement registers are served from the shadow. Test 9 asserts it, and it is the test that stops the shadow from being a cache of the whole register map.

7b. Verified Execution

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, thirteen scenarios, one finish time
   $ iverilog -g2012 -o d i2c_sensor_shadow.sv i2c_sensor_shadow_tb.sv && ./d
   === i2c_sensor_shadow: one sample, two bytes, and the gap between them ===
   T1  reading a measurement before configuring is flagged
   T2  an undisturbed burst: both instances agree
   T3  the sample is latched on the first read of a burst
   T4  an update between the two byte reads, at a carry boundary
   T4  0x0000 is 255 counts below sample N and 256 below sample N+1
   T5  the torn value was never a real sample
   T6  a repeated START ends the burst and re-latches
   T7  a data-ready bit separates a fresh sample from a re-read
   T8  the core keeps measuring while a burst is served
   T9  a burst reading past the measurement reaches the config byte
   T10 a write to the measurement is accepted and discarded
   T11 another device's address is ignored
   T12 the served-byte count proves a burst was one transaction
   T13 an update between transactions is not a torn-read window
   === i2c_sensor_shadow: ALL CHECKS PASSED ===

   $ iverilog -g2005 -o v i2c_sensor_shadow.v i2c_sensor_shadow_tb.v && ./v
   === i2c_sensor_shadow: one sample, two bytes, and the gap between them ===
   T1  reading a measurement before configuring is flagged
   T2  an undisturbed burst: both instances agree
   T3  the sample is latched on the first read of a burst
   T4  an update between the two byte reads, at a carry boundary
   T4  0x0000 is 255 counts below sample N and 256 below sample N+1
   T5  the torn value was never a real sample
   T6  a repeated START ends the burst and re-latches
   T7  a data-ready bit separates a fresh sample from a re-read
   T8  the core keeps measuring while a burst is served
   T9  a burst reading past the measurement reaches the config byte
   T10 a write to the measurement is accepted and discarded
   T11 another device's address is ignored
   T12 the served-byte count proves a burst was one transaction
   T13 an update between transactions is not a torn-read window
   === i2c_sensor_shadow: ALL CHECKS PASSED ===

   $ nvc --std=2008 -a i2c_sensor_shadow.vhd i2c_sensor_shadow_tb.vhd
   $ nvc --std=2008 -e i2c_sensor_shadow_tb && nvc --std=2008 -r i2c_sensor_shadow_tb --stop-time=300us
   === i2c_sensor_shadow: one sample, two bytes, and the gap between them ===
   T1  reading a measurement before configuring is flagged
   T2  an undisturbed burst: both instances agree
   T3  the sample is latched on the first read of a burst
   T4  an update between the two byte reads, at a carry boundary
   T4  0x0000 is 255 counts below sample N and 256 below sample N+1
   T5  the torn value was never a real sample
   T6  a repeated START ends the burst and re-latches
   T7  a data-ready bit separates a fresh sample from a re-read
   T8  the core keeps measuring while a burst is served
   T9  a burst reading past the measurement reaches the config byte
   T10 a write to the measurement is accepted and discarded
   T11 another device's address is ignored
   T12 the served-byte count proves a burst was one transaction
   T13 an update between transactions is not a torn-read window
   === i2c_sensor_shadow: ALL CHECKS PASSED ===

7c. What The Testbench Proves

#scenariowhat it establishes
1a measurement read before configuringflagged; the value is meaningless
2a clean burst with no update inside itboth devices agree — the hazard is invisible
3the latch, at the first read of a bursttaken there, held, released at the STOP
4an update between the MSB and LSB reads, at a carry boundarylatching: 0x00FF; live: 0x0000
5the torn value against both samples255 below sample N, and a value neither held
6a repeated START mid-burstreleases the latch; the new burst re-latches
7the data-ready bit across four readsseparates a fresh sample from a re-read
8four core updates during one burstthe core keeps measuring; the burst is unaffected
9a burst reading past the measurementreaches the config byte, which is not latched
10a write to a measurement registeraccepted and discarded; question 4, the other way
11another device's addressignored by both devices
12the served-byte countproves a burst was one transaction, not several
13updates outside a burstnot flagged as torn — the flag means something

Test 2 is the most important test in the chapter and it asserts that nothing happens. The same burst with no update inside it returns 0xABCD from both devices. That is the case that makes the hazard invisible in testing, and asserting the agreement is how the bench documents that agreement is normal — which is the reason a latching and a non-latching part cannot be told apart in the field.

Test 4 is §0's worked example, run on two devices at once. The update is injected between the two take calls at exactly the carry boundary, and the two assembled values are asserted separately: 0x00FF from the latching instance and 0x0000 from the live one.

Test 5 states the damage numerically rather than asserting a magic number. It checks that 0x00FF - assembled is 255, and separately that the assembled value is below both samples. The second is the statement that matters: a stale reading is a real measurement from the past, and this is not a measurement at all.

Test 13 exists because a mutation found the hole. Every update the original suite made happened inside a burst, so a design that flagged every update passed. §8.

8. Mutation Testing

Twelve defects injected into the SystemVerilog sensor model.

#injected defectoutcome
E2-1a non-latching part latches anyway, hiding the hazardkilled — test 3
E2-2the previous shadow latched instead of the live samplekilled — 6 checks
E2-3later burst bytes served live despite a frozen copykilled — test 4
E2-4the frozen copy held across a repeated STARTkilled — 2 checks
E2-5the frozen copy never released at the STOPkilled — test 3
E2-6an update inside a burst not recordedkilled — 3 checks
E2-7every update flagged as a torn-read windowkilled — test 13
E2-8data_ready left set after the sample was read outkilled — test 7
E2-9a pre-configuration measurement read not flaggedkilled — test 1
E2-10the pointer not advanced between burst byteskilled — 2 checks
E2-11the two status bits swappedkilled — 2 checks
E2-12the served-byte count not maintainedkilled — test 12

Twelve of twelve, after one bench addition.

E2-7 survived the original suite, and the reason is a general one. It flags every sensor update as a torn-read window, burst or not. Every update the twelve-test suite made happened to be inside a burst — because those were the interesting ones — so a flag that was always high looked exactly like a flag that was correctly high.

A status flag can only be shown to mean something if the bench drives the case where it must be low. Test 13 does that: two updates between transactions, then a complete burst with no update inside it, then one update inside a burst. Three conditions, and the flag must be low, low, high.

That generalises past this design. Any flag whose purpose is to distinguish a bad case from a good one needs a test of the good case, and a suite built around the bad case will pass a flag that is stuck asserted.

E2-1 is the mutation that would ship. It makes a non-latching device latch, which fixes the hazard — and it is killed by test 3, which asserts that the non-latching instance holds nothing. A bench that only checked the latching device would pass it, and the design would then be unable to model the very part class the chapter is about.

E2-2 and E2-3 attack the same property from opposite ends and produce very different breadth: six checks and one. E2-2 latches the wrong thing, so every burst on the latching device is wrong; E2-3 serves the right thing for the first byte and the wrong thing afterwards, which only matters when an update lands mid-burst. One defect is always visible, the other only at the moment the chapter is about.

E2-11 swaps two status bits and kills with two checks, both from test 7. Note that a suite checking only that "the status register has a plausible value" would pass it — the bits are adjacent and both are frequently set.

9. Verification Connection — Generating the Failure You Cannot Reach by Accident

A torn read needs an update to land in a window a few microseconds wide, at a value that is crossing a carry. Neither happens in a functional test, and neither happens in a random regression unless something is aimed at it.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_sensor_uvm.sv — a core model that tears on purpose, and the coverage to prove it did
   // A sensor core model whose whole purpose is to produce the update TIMING that
   // causes a tear. A model that updates on a fixed interval will, almost always,
   // update between transactions -- which is the harmless case. The interesting
   // update lands between two byte reads of one burst.
   class sensor_core_model extends uvm_component;
      `uvm_component_utils(sensor_core_model)

      // The value the core is counting through. Starting it just below a carry
      // boundary is what makes a tear LARGE rather than off-by-one: section 1.
      rand bit [15:0] value;
      rand int        step;

      // Where the update should land, expressed in bytes of the burst. 0 means
      // before the burst (harmless); 1 means between byte 0 and byte 1 (the tear).
      rand int        update_after_byte;

      constraint c_boundary {
         // Bias hard towards carry boundaries. A uniformly random 16-bit value
         // crosses a high-byte boundary on 1 step in 256, so uniform randomization
         // produces a large tear roughly never.
         value[7:0] inside {[8'hFC:8'hFF]};
         step inside {[1:4]};
         update_after_byte inside {[0:2]};
      }

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

   // The check. Note it is NOT "the value is correct" -- the bench does not know what
   // the sensor should read. It is that the value is one the core ACTUALLY HELD, which
   // is the property a shadow register provides and a live read does not.
   class coherency_scoreboard extends uvm_component;
      `uvm_component_utils(coherency_scoreboard)

      // Every value the core has ever held, in order. A coherent read must match one
      // of these. A torn read matches NONE of them, which is the whole point: the
      // check does not need to know which sample the master should have got.
      protected bit [15:0] history[$];

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

      function void core_updated(bit [15:0] v);
         history.push_back(v);
      endfunction

      function void burst_read(bit [15:0] assembled, bit device_latches);
         bit found = 0;
         foreach (history[i])
            if (history[i] == assembled) found = 1;

         if (device_latches && !found)
            `uvm_error("COHERENCY",
               $sformatf("latching device returned 0x%04h, which the core never held",
                         assembled))

         // On a non-latching device a tear is EXPECTED, not a failure. Asserting
         // coherency there would fail a correct model of a correct part. What the
         // bench should do instead is COUNT the tears, so a regression can show the
         // stimulus reached the hazard at all.
         if (!device_latches && !found)
            `uvm_info("COHERENCY",
               $sformatf("torn read observed: 0x%04h was never a sample", assembled),
               UVM_MEDIUM)
      endfunction
   endclass

   // And the coverage that decides whether any of the above ran.
   covergroup tear_cg with function sample (int upd_in_burst, bit carry_crossed,
                                            bit was_torn);
      option.per_instance = 1;

      // Did an update land inside a burst at all? Without this bin filled, the
      // scoreboard above has never been exercised on the case it exists for.
      cp_where : coverpoint upd_in_burst {
         bins outside_burst = {0};
         bins inside_burst  = {1};
      }

      // Did the update cross a high-byte boundary? This is the bin that separates an
      // off-by-one tear from a 256-count one, and uniform randomization fills it
      // about once in 256 attempts.
      cp_carry : coverpoint carry_crossed { bins no = {0}; bins yes = {1}; }

      // The cross is the goal: an update inside a burst, across a carry.
      x_tear : cross cp_where, cp_carry, was_torn {
         // A tear that is inside a burst and crosses a carry is the chapter.
         bins the_hazard = binsof(cp_where.inside_burst) &&
                           binsof(cp_carry.yes) && binsof(was_torn) intersect {1};
      }
   endgroup

Four points, and the one that generalises furthest.

The check is not "the value is correct". The bench does not know what the sensor should read, and a scoreboard that tried to predict it would be modelling the physics rather than the protocol. The property is that the assembled value is one the core actually held at some point — which a coherent read always satisfies and a torn read never does, regardless of what the correct answer was.

A tear on a non-latching device is expected behaviour, not a failure. Asserting coherency there would fail a correct model of a correct part. What the bench does instead is count the tears, so a regression can demonstrate the stimulus reached the hazard.

Uniform randomization fills the interesting bin about once in 256 attempts. A 16-bit value crosses a high-byte boundary on one step in 256, so the large tear is effectively unreachable without a constraint aimed at it. The c_boundary constraint is not a convenience; without it the cover bin stays empty and the whole environment verifies the harmless case.

And update_after_byte has to be a randomized field, because "between byte 0 and byte 1 of the burst" is a timing requirement measured in bus events rather than in clock cycles. Expressing it in nanoseconds would make it depend on the bus frequency and break the first time someone changes SCL.

10. FPGA and ASIC Implications

A shadow register is N flip-flops and a mux, and it is the cheapest correctness mechanism in the device. For a 16-bit measurement that is sixteen flops, one enable term and a 2:1 mux on the read path. Leaving it out to save that is a false economy of a specific kind: the cost lands on every driver that ever talks to the part, forever.

The latch enable is the addressing that starts a burst, which means the read path needs to know a burst has begun. That is one flag, and it must be cleared on both a STOP and a repeated START. Clearing it on only the STOP leaves a repeated START serving the previous burst's sample — mutation E2-4.

The core must not stall while a burst is served. A sensor that paused its conversion during a read would have a measurement rate that depended on how often it was read, which is a worse problem than the one it solves. The shadow exists precisely so the core does not have to stop.

Document the latching behaviour prominently, because a driver cannot discover it. §3: the two device classes are indistinguishable on every transfer that does not straddle an update. A datasheet line saying "the measurement registers are latched on the first read of a transfer and must be read in a single transfer" is the only channel available.

A read-to-clear status register needs its side effect stated at least as prominently. §6: a fault register that clears on read cannot be read twice, and a driver that logs it and then re-reads it for a decision has thrown the fault away. This is a device convention with a destructive read, which is the sharpest form of Chapter 16.1's question 4.

A data-ready bit costs one flop and removes a genuine ambiguity. Without it, a master polling faster than the conversion rate cannot distinguish a fresh sample from the previous one — and the two look identical because they are identical bytes.

And expose a torn-risk indication if the budget allows it. It is one flop, set when an update lands inside a burst. On a latching device it is informational; on a non-latching one it tells a driver that the sample it just assembled may not be a sample at all, which is the only warning available.

11. Debugging — The Fan That Oscillated at One Particular Temperature

Symptom

A thermal management system reads a 16-bit temperature sensor once per second and turns a fan on above 45 degrees, off below 44. In the field, some units oscillate the fan continuously when the ambient holds the sensor near 45 degrees. Logs show occasional readings exactly 1.00 degree below the surrounding samples. The behaviour is almost never seen in the lab thermal chamber, which sweeps slowly from 0 to 70 degrees.

Root Cause

A torn read. The driver read the measurement as two separate transactions, and because the sensor latches per transfer, each read latched its own copy -- defeating the mechanism entirely. When an update landed between the two transactions at a whole-degree boundary, the MSB of the old sample combined with the LSB of the new one produced a value exactly one high-byte step low: 0x2C00 rather than 0x2D00, or 44.00 instead of 45.00. That one degree is exactly the width of the control loop's hysteresis band, which is why a single bad sample flipped the fan. The bus was never at fault; every capture was of a perfectly conforming transfer. And the temperature dependence was not thermal -- it was simply where the raw value sat relative to a carry boundary.

Fix
Read both bytes in a single transfer, as the datasheet requires -- a pointer write, a repeated START, then two bytes with the master acknowledging the first. That restores the sensor's latch. Then record the single-transfer requirement as a comment at the read function, because it is not inferable from the register map and the next person to simplify this code will split it again. For the regression: step a modelled sensor across a carry boundary between the two byte reads and assert that the assembled value is one the model actually held. Widening the hysteresis band would have hidden this particular symptom while leaving every other consumer of the reading wrong by a degree.

Three things generalise.

The bus captures were clean and that was the strongest clue, not the weakest. A well-formed transfer carrying a wrong value rules out the whole class of signal-integrity explanations and points at a protocol-level or device-level convention. Time spent on pull-ups was spent because clean captures looked like an absence of evidence.

The error was exactly one degree, which is exactly one high-byte step. That is the signature of a tear rather than of noise: noise is small and varies, and a tear is always one full step of the byte that changed. A reading that is wrong by a suspiciously round amount in the sensor's own units is worth suspecting immediately.

The datasheet said exactly what to do and the driver did not do it. "Must be read within a single transfer" is a correctness requirement — §3's callout — and it reads like a performance note. Splitting the read into two transactions is the kind of simplification that passes review because nothing in the register map explains why it is wrong.

12. Common Misconceptions

"A 16-bit sensor read is atomic." It is two byte transfers, and the sensor keeps measuring between them. §0.

"A torn read gives a stale value." It gives a value neither sample ever held, and at a carry boundary it is about 256 counts from both. A stale reading is at least a real measurement. §0 and §1.

"Reading LSB first avoids the problem." It produces the same magnitude of error with the opposite sign. The problem is two transfers straddling an update, not the order of the bytes. §2.

"Reading twice and comparing fixes it." Two chances to tear rather than one, and two consecutive tears at the same boundary agree with each other. §2.

"The device will report a torn read." Only if it was built to, and there is no protocol mechanism for it. A latching and a non-latching part are indistinguishable on the bus. §3.

"Splitting a burst read into single reads is just slower." On a device that latches per transfer it defeats the latch completely, which is a correctness change rather than a performance one. §3 and §11.

"A repeated START in the middle of a burst is harmless." It ends the burst, so the device re-latches and the master loses coherency for the bytes after it. §3.

"A sensor stops measuring while it is being read." It must not — a measurement rate that depended on read frequency would be a worse problem. §7a and §10.

"A data-ready bit is a convenience." Without it a master polling faster than the conversion rate cannot distinguish a fresh sample from a re-read, because the bytes are identical. §6.

"A fault register can be read twice." Not if it is read-to-clear. The second read returns zeros and the fault is gone. §6.

"Zero is an obviously bad temperature reading." Zero is a plausible temperature, a plausible voltage and a plausible angle. That is what makes an unconfigured read worse than an erroneous one. §6's callout.

"A torn-read test will happen eventually in a random regression." A 16-bit value crosses a high-byte boundary on one step in 256, and the update has to land inside a window a few microseconds wide. Without constraints aimed at both, the bin stays empty. §9.

13. Reason It Through

A master reads MSB then LSB of a 16-bit sensor and gets 0x0000. The sensor's actual readings were 255 and 256. Explain.

The MSB came from sample 255 (0x00) and the LSB from sample 256 (0x00), because an update landed between the two byte reads. The assembled value is 255 counts below both samples and is a number neither ever held. §0.

Why is the error largest exactly at a carry boundary?

Because a tear only matters when the two samples' high bytes differ, which happens only at a carry — and at a carry the low byte jumps from near-maximum to near-zero, so combining the old high byte with the new low byte is wrong by roughly one full high-byte step. §1.

A driver reads the two bytes in the opposite order to avoid tearing. Does it work?

No. LSB-first at the same boundary gives 0xFF from sample 255 and 0x01 from sample 256, assembling to 511 — 256 counts too high instead of 255 too low. The exposure is two transfers straddling an update, which no ordering changes. §2.

How can a master determine whether a device latches its measurement?

It cannot, from the bus. The two device classes are byte-for-byte identical on every transfer that does not straddle an update, which is nearly all of them. Only the datasheet says. §3.

Why does the first byte of a burst come from the live core rather than the shadow?

Because the shadow is being captured in the same clock as that byte is prepared, so shadow_held is still low — and the live value at that instant is exactly what the shadow is capturing, so the two are equal. Reading the shadow there would serve the previous burst's sample. §7a.

A design flags every sensor update as a torn-read window. Why did a twelve-test suite pass it?

Because every update the suite made happened to be inside a burst, which is where the interesting cases are. A flag that is always high looks exactly like a flag that is correctly high, and the only test that distinguishes them drives the case where it must be low. §8.

Why is a scoreboard check of "the value is one the core has held" better here than an exact prediction?

Because an exact prediction has to choose which sample was correct, and the answer differs between a latching and a non-latching device — both of which are correct. Membership in the set of values the core has held is satisfied by every coherent read and by no torn read, regardless of which sample was the right one. §9.

A spurious sensor reading clusters at one ambient temperature and cannot be reproduced in a thermal chamber. What does that suggest?

That the clustering is about where the raw value sits relative to a carry boundary rather than about temperature, and that the chamber's slow sweep crosses that boundary too rarely to reproduce the rate. Still air near ambient fluctuates across a boundary far more often than a 1-degree-per-minute sweep. §11.

14. Understanding Check

15. Summary

A measurement wider than a byte takes more than one transfer, and the device keeps measuring in between. That is the whole hazard, and it needs no bus fault to occur.

A torn read produces a value neither sample ever held. MSB of 0x00FF with LSB of 0x0100 assembles to 0x0000 — 255 counts below both.

The error is largest exactly at a carry boundary, which is exactly where a system is most likely to be watching for a transition. Almost everywhere else it is small or zero, which is why the bug survives testing.

Reversing the byte order does not help — same magnitude, opposite sign — and neither does reading twice and comparing, because two tears at the same boundary agree.

The remedy is a device convention: latch the whole measurement when the burst begins, and serve every byte of that burst from the frozen copy while the core keeps measuring.

Which makes a datasheet's "read both bytes in one transfer" a correctness requirement, not an optimisation. Splitting it defeats the latch completely, because the latch is taken per transfer.

And a repeated START mid-burst re-latches, correctly, so a master that turns the transfer around loses the protection.

Whether a device latches at all is not on the bus. The two classes are byte-for-byte identical on every transfer that does not straddle an update, so only the datasheet can tell you.

An RTC has the same problem with worse boundaries, and a read-to-clear fault register has a destructive read — both device conventions with no protocol expression.

A flag only means something if the bench drives the case where it must be low. A torn-risk flag that was always asserted passed a twelve-test suite, because every update the suite made was inside a burst.

And the right scoreboard check is membership, not prediction. A bench cannot know which sample a read should have returned — that differs between two correct devices — but it can require that the assembled value is one the core actually held, which every coherent read satisfies and no torn read does.

16. What Comes Next

Module 16 is complete, and its through-line was a single sentence of specification: "All decisions on auto-increment or decrement of previously accessed memory locations, etc., are taken by the designer of the device."

Five chapters followed that delegation out to its consequences. A register map has six questions the protocol does not answer and a datasheet often does not either. A word address has a width that is invisible on the bus, and getting it wrong hands your first data byte to the pointer. A write burst walks a page rather than an array, so running off the end destroys the bytes you already sent. A device that is busy is indistinguishable from one that is absent or wedged, so the only honest report of a failed wait is that the cause is unknown. And a measurement wider than a byte can be assembled into a number that was never a measurement.

Every one of those failures is silent. Every byte acknowledged, every frame well formed, every capture clean. The bus was working perfectly in all five chapters, which is precisely why these bugs reach production: there is nothing for a protocol analyser to flag.

Two verification lessons recurred often enough to be worth stating as rules. A stale-data bug hides whenever the stale data happens to equal the correct data — which is why a buffered write must be tested across regions and a flag must be tested in the state where it should be clear. And a surviving mutation is sometimes not a missing assertion but a missing configuration: two defects here were arithmetically unobservable until the bench instantiated a differently-sized part.

Module 17 turns from the device back to the wire, at a level this curriculum has so far taken for granted: the physical layer as an analogue system. Bus capacitance, rise times, pull-up sizing, and the reason a bus that works at 100 kHz with a 10 kΩ pull-up fails at 400 kHz with the same resistor — not intermittently, but completely, and for a reason that is calculable in advance.

Continue learning

Related tutorials