Skip to content
VLSI Mentor

I²C · Module 22

Negative Testing — NACK, Truncated Bytes and Malformed Framing

Why a NACK is not an error, what traffic is genuinely illegal on this bus, and three of five negative tests that violated nothing at all — each of which presented as a property that would not fire.

Negative testing has a reputation for being straightforward: drive something wrong and check nothing breaks. In practice it fails in two directions that look identical from the outside — the test drives something that is not actually wrong, or it drives something wrong and nothing was watching.

Both produce a green result.

1. A NACK Is Not an Error

The most common mistake in an I²C negative-test plan is putting NACKs in it.

A NACK is a released line in an acknowledge slot. It is legal, it is meaningful, and it is the correct answer in at least four ordinary situations:

situationthe NACK means
the address is not oursthis device is not being addressed
a write to a read-only registerthe device refuses the byte
the controller ending a readstop sourcing data
the device is busy or unconfigurednot now

None of those is negative testing. They are positive tests of refusal behaviour, and the distinction matters because a test plan that files them under "error cases" tends to treat a NACK as a failure signal somewhere in the environment — and then every legal refusal is a false alarm.

2. What Genuine Negative Traffic Looks Like

The traffic that is actually illegal is narrower than most plans assume, because I²C has few rules that a controller can break by accident:

tracewhy it is illegal
SDA moved while SCL was high, mid-byteviolates the data-valid rule
a STOP with no transfer openframing with nothing to frame
a START before the bus-free intervalviolates the minimum idle
a STOP part way through a bytethe bits were never received
two devices pulling a line across a bit boundarya second driver

These are the five the bench drives, and they are the five the properties exist for. Notice what is absent: nothing about values. There is no illegal byte, no illegal address, no illegal register — because the protocol does not constrain content.

3. Three of Five Negative Tests Did Not Work

This is the part worth carrying away, because the failures were not exotic.

A STOP mid-byte that produced no STOP. The trace drove three bits and then a STOP — but the last bit was a '1', so SDA was already released and the "release" produced no edge. No framing event occurred at all. Chapter 22.4 §3 has the detail; the fix was to end on a '0'.

A bus-free violation with seventeen cycles of idle. The trace composed the library's b_stop and b_start, each of which waits a half period, so the gap satisfied the very rule the test targeted. Chapter 22.3 §3 has the detail; the fix was a dedicated sequence with no waits in it.

A contention test with only one contender. The extra puller was OR-ed into the bench's own drive bit rather than given its own device index, so the holder count never exceeded one and the property could not fire:

Azvya Education Pvt. Ltd.VLSI Mentor
Before and after, from i2c_assert_tb.vhd — the wiring that made a contention test uncontended
      -- before: one device, driven by two sources
      sda_drv <= d_sda_low & (m_sda_low or x_sda_low);

      -- after: three devices, and the third one is real
      sda_drv <= x_sda_low & d_sda_low & m_sda_low;

4. Truncated Bytes Deserve a First-Class Test

A truncated byte is the most realistic negative case on this bus, because real controllers produce it: software times out, a master loses arbitration, a device resets mid-transfer.

The interesting question is not whether the target survives it but what everything downstream does with the partial bits. Three components have to agree:

  • the monitor must discard them — seven bits are not a byte, and publishing them as a value would report data no device received;
  • the properties must report the truncation — NO_STOP_MID_BYTE fires;
  • the scoreboard must not compare the transfer's payload against a contract, because there is no complete transfer to compare.

Module 20's monitor and this module's property set reach the same conclusion independently, which is the useful part: they were written from the specification rather than from each other, so their agreement is evidence rather than coincidence.

5. Malformed Framing Is Not the Same as Noise

One distinction worth drawing, because it changes what the test proves.

Malformed framing is a legal edge in an illegal place — a START where the bus-free interval has not elapsed, a STOP with nothing open. Every device will interpret it as framing, because it is framing; the fault is its position.

Noise is a disturbance that may or may not be interpreted as anything, depending on where it lands relative to a sampling edge. Chapter 20.9 measures that directly: at one position a 2-clock disturbance is invisible and a 40-clock one turns 0xFF into 0x7F with every byte still acknowledged.

The first belongs in a negative test with an assertion watching. The second belongs in an error-injection campaign with a scoreboard watching, because its damage is to data and leaves the protocol intact.

The negative test suite where nothing was negative

Pitfall — a NACK filed as an error case
Buggy Code
// From a test plan's "error cases" section:
//
//    ERR-01  target NACKs the address        -> expect error reported
//    ERR-02  target NACKs a data byte        -> expect error reported
//    ERR-03  controller NACKs the last read  -> expect error reported
//
// and the environment grew a check to match:
//
//    if addr_acked = '0' then
//       report "target did not acknowledge" severity error;
//    end if;
//
// Now every transfer addressed to another device on the bus is an error. So is
// every legal refusal of a read-only write. So is ERR-03, which is the CONTROLLER
// correctly ending a read.
//
// Within a week the check has a waiver, and the environment no longer notices a
// target that fails to answer its own address -- which was the real case.
Root Cause

Categorising a legal protocol response as an error installs a check that fires on correct traffic, and the waiver that follows removes it for the genuine case too. The underlying confusion is between a signal's value and its correctness: a NACK's legality is a protocol fact, while its correctness is a relation to a contract, and only the second is checkable — by a scoreboard, with an expected value.

Fix
// A NACK is legal. What is checkable is whether it was the CORRECT answer, and
// that needs an expectation from the datasheet:
//
//    -- predictor, from the device contract
//    exp_addr_ack := '1' when observed_addr = MY_ADDR else '0';
//    exp_data_ack := '0' when is_read_only(ptr) else '1';
//
//    -- scoreboard
//    if observed_addr_acked /= exp_addr_ack then
//       n_ack_mismatch := n_ack_mismatch + 1;
//    end if;
//
// and the plan is re-filed accordingly:
//
//    POS-07  foreign address is NOT acknowledged   -> expect NACK, no error
//    POS-08  read-only write is refused            -> expect NACK, no error
//    POS-09  controller ends a read with a NACK    -> expect NACK, no error
//    NEG-01  target fails to answer its OWN address -> expect ACK, mismatch
//
// THE TEST for whether something belongs in the negative section: can you draw a
// waveform that violates it, with no device contract available? A NACK fails that
// test -- the same waveform is correct for a device at another address.
Pitfall — a contention test with one contender
Buggy Code
// A test for two devices driving SDA simultaneously. An extra puller is added to
// the bench, and wired in the obvious way:
//
//    signal x_sda_low : std_logic := '0';   -- the second contender
//    ...
//    sda_drv <= d_sda_low & (m_sda_low or x_sda_low);
//                            ^^^^^^^^^^^^^^^^^^^^^^
// The bench's own drive bit and the extra puller are OR-ed into ONE device's
// position in the bus model.
//
// So the holder count NEVER EXCEEDS ONE. The wired-AND result is identical, the
// bus looks exactly as expected, and the property that watches for a second
// driver cannot fire -- because by construction there is no second driver.
//
// The test passes. It has demonstrated nothing.
Root Cause

OR-ing two sources into one drive bit produces an identical waveform and a holder count that never rises, so the situation the test exists to create never exists. It is undetectable from the resolved bus by construction, which is why the count has to be checked rather than the line. The general form: a test for simultaneous behaviour must confirm the simultaneity happened, using an observable that can distinguish one participant from two.

Fix
// Give the contender its own device index, and widen the bus model:
//
//    bus_model : entity work.i2c_line_model
//       generic map (N_DEV => 3)                     -- was 2
//       port map (...);
//
//    scl_drv <= '0'        & d_scl_low & m_scl_low;
//    sda_drv <= x_sda_low  & d_sda_low & m_sda_low;  -- three separate devices
//
// and hold the overlap longer than a bit period, because a handover overlap is
// legal and short (see 22.5):
//
//    wait until falling_edge(clk); m_sda_low <= '1';
//    for i in 1 to 4  loop step; end loop;
//    wait until falling_edge(clk); x_sda_low <= '1';   -- now TWO devices
//    for i in 1 to 24 loop step; end loop;             -- past a bit boundary
//
// THE DIAGNOSTIC that would have caught it immediately: the bus model exports a
// holder count, so print it.
//
//    "sda_holders max during the test = 1"   -> there was never a second driver
//
// The resolved LINE cannot reveal this -- low is low, and one puller and two are
// bit-identical. That is the whole reason the count is exported, and a contention
// test that does not consult it is testing its own wiring.

6. What 22.7 Settled

A NACK is legal, and filing it as an error installs a check that fires on correct traffic and is then waived away for the real case.

Genuinely illegal I²C traffic is a short list, and none of it concerns values. The protocol constrains timing and framing, not content.

Three of five negative tests initially violated nothing, and all three looked like a broken property. Event counters beside violation counters are what distinguish the two.

A test for simultaneous behaviour must confirm the simultaneity happened, using an observable that can tell one participant from two — the resolved line cannot.

Truncation is the realistic negative case, and three components must agree on discarding, reporting and not-comparing it.

Next, the conditions that are hostile without being illegal — where there is no rule to violate and therefore nothing for an assertion to catch. Chapter 22.8 — Corner Cases.

Continue learning