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.
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 signSo 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
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 cyclesThe 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.
// -----------------------------------------------------------------------------
// 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 `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 // -----------------------------------------------------------------------------
// 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 `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 -- ---------------------------------------------------------------------------
-- 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; -- ---------------------------------------------------------------------------
-- 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
$ 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
| # | scenario | what it establishes |
|---|---|---|
| 1 | a measurement read before configuring | flagged; the value is meaningless |
| 2 | a clean burst with no update inside it | both devices agree — the hazard is invisible |
| 3 | the latch, at the first read of a burst | taken there, held, released at the STOP |
| 4 | an update between the MSB and LSB reads, at a carry boundary | latching: 0x00FF; live: 0x0000 |
| 5 | the torn value against both samples | 255 below sample N, and a value neither held |
| 6 | a repeated START mid-burst | releases the latch; the new burst re-latches |
| 7 | the data-ready bit across four reads | separates a fresh sample from a re-read |
| 8 | four core updates during one burst | the core keeps measuring; the burst is unaffected |
| 9 | a burst reading past the measurement | reaches the config byte, which is not latched |
| 10 | a write to a measurement register | accepted and discarded; question 4, the other way |
| 11 | another device's address | ignored by both devices |
| 12 | the served-byte count | proves a burst was one transaction, not several |
| 13 | updates outside a burst | not 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 defect | outcome |
|---|---|---|
| E2-1 | a non-latching part latches anyway, hiding the hazard | killed — test 3 |
| E2-2 | the previous shadow latched instead of the live sample | killed — 6 checks |
| E2-3 | later burst bytes served live despite a frozen copy | killed — test 4 |
| E2-4 | the frozen copy held across a repeated START | killed — 2 checks |
| E2-5 | the frozen copy never released at the STOP | killed — test 3 |
| E2-6 | an update inside a burst not recorded | killed — 3 checks |
| E2-7 | every update flagged as a torn-read window | killed — test 13 |
| E2-8 | data_ready left set after the sample was read out | killed — test 7 |
| E2-9 | a pre-configuration measurement read not flagged | killed — test 1 |
| E2-10 | the pointer not advanced between burst bytes | killed — 2 checks |
| E2-11 | the two status bits swapped | killed — 2 checks |
| E2-12 | the served-byte count not maintained | killed — 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.
// 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};
}
endgroupFour 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
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.
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.
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
- Related topic
Where I²C Lives — Boards, SoCs and Real Devices
Place the derived bus in a real system: the host controller inside an SoC or FPGA, the regulators, sensors, memories and clock devices attached to it, and what each one is actually doing. The traffic turns out to have a specific shape — control plane, not data plane — and that shape is why the bus remains useful.
- Related topic
UART vs Other Interfaces: Choosing the Right Link
Serial interfaces differ first in where the receiver's timing comes from, then in what organises a shared medium — and capability is paid for in what the system must already provide. A question order for choosing between UART, SPI, I2C, CAN, USB and Ethernet.
- Related topic
Why Chips on a Board Need a Bus
A connection between two chips is not a wire. It is a pin on each package, a routed trace, the board area and layers that trace consumes, and an I/O cell driving it — and all of that is paid for again for every device added. This is the cost structure that makes dedicating an interface per peripheral stop scaling, and that forces a board to share one set of wires instead.
- Related topic
From Parallel Buses to Two Wires
Derive the bus rather than meet it. Trading wires for time gives serialisation; trading exclusivity for coordination gives a shared medium; losing the wire as an implicit address forces a logical one. Each step is a deliberate exchange, and what falls out is a two-wire addressed bus — which is what Philips specified as I²C.
