I²C · Module 22
Asserting ACK Timing and Byte Boundaries
Nine clocks to a byte, why a transfer must not end mid-byte, a tracker that counted a STOP's own clock edge as a data bit, and a violation scenario that produced no violation because the line was already released.
A byte on this bus is nine clocks: eight bits and an acknowledge slot. That count is the backbone of every structural property, because it is what says where a byte ends — and therefore what makes "the transfer ended mid-byte" a statement with content.
Getting the count right turns out to be harder than the rule it supports.
1. The Property
-- psl NO_STOP_MID_BYTE : assert always (
-- (stop_now = '1' and in_byte = '1') -> false)
-- report "22.4 a STOP arrived mid-byte, truncating a transfer";in_byte means bits have accumulated and no acknowledge slot has closed them. A STOP at that point means some number of bits were clocked onto the bus that no device received — there was no acknowledge slot, so no receiver ever confirmed them, and nothing downstream can treat them as data.
This is the same conclusion Module 20's monitor reaches from the other direction: 20.7 discards a truncated byte rather than publishing seven bits as a value. The property and the monitor agree because both are reading the specification, not each other.
2. The Counter That Counted Framing
The tracker's first version incremented the bit count on the rising edge of SCL, which is the instant a bit is sampled and the obvious place to count it.
The general lesson is that framing and data share the clock line. Any counter driven by SCL edges will see framing edges too, and it has to be built so that framing wins.
3. Framing Needs an Edge, So the Preceding Level Matters
The violation scenario for this property took two attempts, and the first failure is instructive in a different way.
b_start;
b_put(ADDRV & '0', ackbit);
b_bit('1'); b_bit('0'); b_bit('1'); -- three bits, then framing
b_stop;Three bits and a STOP: an obvious mid-byte truncation. The property stayed quiet.
This is worth generalising because it applies to every framing test ever written: a framing condition is an edge, and an edge depends on the level before it. A test that drives a STOP without establishing that SDA is low is a test that may drive nothing at all — and nothing is indistinguishable from a passing check.
4. What the Acknowledge Properties Cannot Be
The obvious property to want here is the target acknowledges its own address. It is not in the file, and 22.1 measures why: a released line in an acknowledge slot is a NACK, which is legal and frequently correct, so there is no illegal waveform to assert against. Written as a property it fires on every transfer addressed elsewhere.
What is assertable about the acknowledge slot is structural:
- it arrives on the ninth clock, not the eighth or tenth — which the bit counter enforces and
NO_STOP_MID_BYTEreports when violated; - exactly one device drives it — which needs a count of drive intents and is 22.5's subject;
- SDA does not move within it while SCL is high — which is 22.2's property applying to the ninth slot like any other.
Every legal STOP reported as a truncated transfer
Pitfall — a bit counter that counts the framing clock
// The bit counter, incremented where a bit is sampled -- the obvious place:
//
// elsif scl_rise = '1' and active = '1' then
// if bitcnt < 8 then
// bitcnt <= bitcnt + 1;
// in_byte <= '1';
// else
// bitcnt <= 0;
// in_byte <= '0';
// end if;
// end if;
//
// and the property that depends on it:
//
// -- psl NO_STOP_MID_BYTE : assert always (
// -- (stop_now = '1' and in_byte = '1') -> false);
//
// Three tests of ORDINARY LEGAL TRAFFIC now fail. Every complete write reports a
// mid-byte truncation at its STOP.
//
// Because a STOP sequence is: pull SCL low, RELEASE SCL (a rising edge), then
// release SDA while SCL is high. That rising edge is counted as a data bit, so
// in_byte is high exactly when the STOP arrives.
//
// The design is correct, the property is correct, and the ANTECEDENT is wrong.The property's logic was never in question; what was wrong was the derived state it was conditional on, and that state was wrong for a protocol reason rather than a coding one — the framing sequence necessarily contains a clock edge. Deferring the count to the falling edge gives the framing event, which occurs in between, the opportunity to cancel it. The episode is a reminder that derived antecedents are design and need the same scrutiny as the design under test.
// Separate sampling from counting, so framing can pre-empt the count:
//
// elsif scl_rise = '1' and active = '1' then
// pending <= '1'; -- SAMPLED here
// elsif scl_fall = '1' and active = '1' and pending = '1' then
// pending <= '0'; -- COUNTED here
// if bitcnt < 8 then
// bitcnt <= bitcnt + 1; in_byte <= '1';
// else
// bitcnt <= 0; in_byte <= '0';
// end if;
// end if;
//
// -- framing discards a sampled-but-uncounted bit, in either direction
// if start_now = '1' or stop_now = '1' then
// pending <= '0';
// end if;
//
// A framing event happens WHILE SCL IS HIGH -- after the rise, before the fall --
// so it always gets to cancel the pending bit.
//
// THE GENERAL RULE: on this bus framing and data share the clock line, so any
// counter driven by SCL edges sees framing edges too. Build it so framing wins,
// or every framing event will be counted as data somewhere.
//
// AND NOTE THE FAILURE DIRECTION: a wrong antecedent made the property fire on
// GOOD traffic. The other direction -- an antecedent that is never true -- is
// silent, and far worse. Both are the antecedent, not the property.Pitfall — a framing violation that produced no framing
// A scenario for the mid-byte property: three bits, then a STOP.
//
// b_start;
// b_put(ADDR & '0', ackbit);
// b_bit('1'); b_bit('0'); b_bit('1'); -- three bits
// b_stop; -- ... then framing
//
// The property does not fire. It looks like the property is broken, or like
// in_byte is not being set, or like the tracker resets too early.
//
// None of those. b_stop frames a STOP by RELEASING SDA, which produces a rising
// edge -- but only if SDA was low. The last bit driven was a '1', which means the
// line was ALREADY RELEASED.
//
// So the release produces no edge, no STOP is framed, and the trace simply ends
// with SCL and SDA both idle. The scenario violated nothing.Framing on this bus is defined by transitions, so a scenario that sets a line to the value it already holds produces no event whatsoever — and the resulting silence is naturally attributed to the checker. Counting the event separately from the violation makes the two cases distinguishable at a glance, which is the difference between a one-line answer and an afternoon spent auditing correct property logic.
// End on a bit that leaves SDA PULLED LOW, so the release is an edge:
//
// b_bit('1'); b_bit('1'); b_bit('0'); -- SDA left low
// b_stop;
//
// THE DIAGNOSTIC that answers this in one line -- an event counter beside the
// violation counter, in the property entity:
//
// if stop_now = '1' then
// c_stops <= c_stops + 1;
// if in_byte = '1' then c_stop_mid <= c_stop_mid + 1; end if;
// end if;
//
// before: "stops seen = 0, of which mid-byte = 0" <- no STOP happened
// after: "stops seen = 1, of which mid-byte = 1" <- it happened, and violated
//
// "stops seen = 0" is a statement about the STIMULUS and it ends the
// investigation immediately.
//
// THE GENERAL RULE: a framing condition is an EDGE, so it depends on the level
// before it. A test that drives a STOP without establishing that SDA is low may
// drive nothing at all -- and driving nothing is indistinguishable from a check
// that passed.5. What 22.4 Settled
A byte is nine clocks, and the count is what gives "mid-byte" meaning. Every structural property here rests on it.
Framing and data share the clock line, so a counter driven by SCL edges must be built so framing wins — otherwise the STOP sequence's own rise is counted as a data bit and every legal STOP is reported as truncation.
Framing is an edge, so the preceding level decides whether it happens at all. A STOP driven onto an already-released SDA produces nothing, and nothing looks exactly like a passing check.
Count events beside violations. stops seen = 0 ends an investigation that auditing the property would not.
The acknowledge slot is assertable structurally and not semantically. Which device drives it and when are waveform questions; whether the answer was right is not.
Next, the behaviours that only appear under stretching and contention — and a property that fired on every legal byte until its threshold was measured rather than guessed. Chapter 22.5 — Asserting Clock-Stretching, Arbitration and Bus-Idle Behavior.
Continue learning
Related tutorials
- Related topic
The Data-Valid Rule — SDA Stable While SCL Is High
One sentence governs every bit on an I²C bus, and it is derived rather than decreed: the receiver needs a settled value at the instant it looks. What falls out is that an SDA edge while SCL is HIGH cannot be data — which is why the bus reserves it for framing.
- Related topic
Address Decoding Inside a Slave
The first piece of slave hardware in the curriculum, and it is assembled rather than written: byte capture, reserved-map classification, one equality test, and a decision whose ordering matters — because a prohibition has to beat an address match.
- Related topic
The End-of-Transfer NACK
A master reading from a slave has no length field and no command for 'stop'. What it has is the acknowledge slot — so it ends a read by withholding one. The fifth NACK condition is not an error; it is the only way the conversation can be terminated.
- Related topic
The I²C Write Transaction End to End
Seven modules built the pieces. This one runs a write from START to STOP as a single continuous story, counts every bit on the wire, and builds the transaction-level sequencer that issues it — in SystemVerilog, Verilog and VHDL.
