I²C · Module 14
Ultra-Fast Mode — The Push-Pull, Write-Only Outlier
Push-pull, unidirectional, 5 Mbit/s, and five features Table 6 marks not merely optional but impossible. What a nine-bit frame means when the master always drives the ninth bit high, and what the specification says when it admits delivery cannot be confirmed.
Every mode in this curriculum so far has rested on one electrical arrangement: an open-drain output that can pull a line low or release it, and a wired-AND that resolves the result. Chapter 2.5 established it, and Chapter 13.5 counted six protocol mechanisms that fall out of it for free.
Ultra Fast-mode throws it away.
Two changes: the output becomes push-pull, and both lines become unidirectional. Those changes buy 5 Mbit/s. What they cost is the acknowledge, arbitration, clock synchronization, clock stretching, multi-master operation, the device ID, and any possibility of reading anything back.
This chapter takes UFm seriously on its own terms. It is a real mode in a real specification, it solves a real problem, and it is worth understanding precisely — including understanding why it is almost never the right answer, and being honest that the specification says so more plainly than most commentary does.
1. Two Changes, Stated
Note the names. USDA and USCL, not SDA and SCL — the specification renames the wires, which is the clearest possible signal that they are not the same wires. And note what the idle state now means: the lines are high because a transistor is holding them there, not because a resistor is. Nothing else may touch them.
That last sentence of §3.2.1 is a second signal, easy to skim. "Reflections from cable ends, connectors, and stubs" is transmission-line language, and it appears nowhere in the specification's treatment of the open-drain modes. An open-drain bus with a 1 kΩ pull-up and a 300 ns edge is an RC circuit. A push-pull driver with a 25 ns edge into a few tens of centimetres of ribbon cable is a transmission line, and it has to be treated like one. §12 returns to this.
2. Table 6 — Five Features That Are Not Possible
"n/p" is doing real work in that table, and it is worth separating from the alternatives. An optional feature is one a device may choose not to implement. An unimplemented feature is one a particular part happens to lack. Not possible means no device could implement it, because the bus makes it physically unavailable.
And the reason is one sentence:
Trace the consequences. Every one of the five impossible features requires a slave or a second master to pull a line low:
| feature | requires | UFm |
|---|---|---|
| Acknowledge | the slave pulling SDA low in bit 9 | no slave may drive SDA |
| Clock stretching | the slave holding SCL low | no slave may drive SCL |
| Arbitration | two masters driving and reading back | one master, and no readback |
| Synchronization | two masters' clocks wired-AND together | same |
| Device ID | a read transaction | reads do not exist |
Five features, one cause. This is the mirror image of Chapter 13.5's closing observation: there, one electrical rule produced six mechanisms for free. Here, removing that rule removes five of them at once. The wired-AND was not a convenience — it was the substrate.
3. The Ninth Bit Survives, And Means Nothing
The frame does not change. That is the strange part.
So a UFm byte is still nine bits. There is still a ninth clock pulse. The master still drives something on USDA during it — and what it drives is always a one, which in every other mode would be read as a not-acknowledge.
And Figure 25's own annotation, which is the bluntest statement in the whole section:
"Master drives the line HIGH on 9th clock cycle. Slave never drives the USDA line."
A UFm transfer is a sequence of frames each of which ends in a not-acknowledge. Every byte is NACKed, by the master, on principle.
The ninth bit is vestigial. It costs 11 % of the bus's throughput and exists so that a UFm frame can be counted, framed and analysed by tooling built for I²C. That is a defensible trade — compatibility of shape is worth something even when compatibility of meaning is gone — but it should be understood for what it is.
4. Write-Only, With One Exception
So the R/W bit still exists in the address byte, and it must be zero. A read request cannot be attempted, let alone completed — there is no return path.
The single exception is worth implementing correctly because it is the kind of detail that produces a puzzling refusal in the field. The START byte is 0000 0001: address 0000 000 with the direction bit set. It is a synchronisation aid for software-polled microcontrollers, not a read, and it is permitted:
A transmitter that refuses every set direction bit will refuse the START byte too, and §9's design implements the exception explicitly for that reason — accepting a set direction bit only when the address is 0000 000. Mutation U3 is the over-strict version.
5. The Cost, Admitted
The specification does not soften this, and the sentence is easy to miss because it sits in a numbered note.
Read that last clause on its own. It is impossible to determine that each slave is responsive.
Not difficult. Not unreliable. Impossible — because determining it would require information travelling from slave to master, and there is no path for it. A UFm master writing to an address learns exactly nothing about whether anything is there:
| question | Sm / Fm / Fm+ / Hs | UFm |
|---|---|---|
| Is a device at this address? | the acknowledge answers it | unanswerable |
| Did the byte arrive? | the acknowledge answers it | unanswerable |
| Is the device ready for more? | stretching or a NACK answers it | unanswerable |
| What does the device contain? | a read answers it | unanswerable |
| Did the device reset? | the device ID or a read answers it | unanswerable |
Every one of those questions was answered by a mechanism Module 7 through Module 12 built, and every one of those mechanisms needed a slave to be able to pull a line low.
UFm is fire-and-forget. The design in §9 reports
bytes_sentand deliberately has nobytes_deliveredoutput, because a signal named that would be a lie.
That absence is a design decision and it is asserted by the testbench. Mutation U8 sets the block's unverifiable flag low — claiming confidence it cannot have — and a check catches it.
6. Recovery Without Evidence
If a master cannot detect that a slave has stopped responding, how is a wedged slave ever recovered? The specification answers directly, and the answer tells you what kind of system UFm belongs in.
Two things there.
Detection is explicitly external. "Determined through external feedback, not through UFm I²C-bus" — the specification states outright that the bus cannot tell you. Something else must: a status pin, a separate management interface, a watchdog, or the observable behaviour of whatever the slave controls. In the common UFm application, LED drivers, the external feedback is that somebody can see the lights are wrong.
The remedy is a three-rung ladder and it descends into increasingly blunt instruments: a software reset over the bus (general call plus 0000 0110), then a hardware reset pin, then a power cycle to invoke the mandatory POR. Compare this with the bus-clear procedure Module 15 takes up — nine clock pulses to free a stuck data line — which does not appear in Table 6 at all, because a stuck data line is not a thing that can happen when only one device drives it.
§9's design implements that ladder, driven by an input that deliberately does not come from the bus. Test 9 asserts that no amount of bus activity can raise the recovery state, which is the property that keeps the design honest about where its information comes from.
7. What The Electrical Tables Reveal
Two rows in Table 13 exist nowhere else in the specification, and one absence in Table 14 is as informative as anything present.
VOH appears only here. No other mode's table has a HIGH-level output voltage row, and the reason is that no other mode has a device that drives a high. In Sm, Fm, Fm+ and Hs-mode the high level is made by a resistor and its quality is a property of the board; here it is made by a transistor sourcing 4 mA and it is a property of the silicon. A specified VOH is the signature of a push-pull bus.
Vhys is 0.05 VDD — half the 0.1 VDD every bidirectional mode requires. That is a real reduction in noise immunity at the input, and it is a consequence rather than an oversight: a push-pull driver produces fast, hard edges that do not linger near the threshold, so there is less dwell time for hysteresis to protect. It is a reasonable trade on a short, controlled interconnect and a poor one on a long cable — which is exactly the constraint §3.2.1 flags with its mention of reflections.
7a. UFm Is The Only Mode With A Symmetric Clock
tLOW(min) = tHIGH(min) = 50 ns. Look at what that breaks from:
| mode | tLOW(min) | tHIGH(min) | ratio |
|---|---|---|---|
| Standard | 4700 ns | 4000 ns | 1.18 |
| Fast | 1300 ns | 600 ns | 2.17 |
| Fast-mode Plus | 500 ns | 260 ns | 1.92 |
| Hs-mode (100 pF) | 160 ns | 60 ns | 2.67 |
| Ultra Fast-mode | 50 ns | 50 ns | 1.00 |
Every bidirectional mode demands a longer low phase than high phase, and Chapter 11.2 explained why: the low phase is when SDA may change, so it has to accommodate the data hold time, the new bit's propagation and the set-up time before the next rising edge — and it is also when a slave may stretch.
In UFm the low phase carries none of that extra burden. Nothing stretches, nothing arbitrates, and the only thing that happens in the low phase is one driver changing one level. So the clock is symmetric, and the symmetry is a direct readout of everything the mode gave up.
7b. And The Budget Identity Holds Exactly
Chapter 14.1 §4 established the identity for the three mainstream modes; Chapter 14.2 §6 found Hs-mode over by 2.00 %. UFm:
tLOW | tHIGH | tr | tf | sum | 1/fUSCL(max) | verdict | |
|---|---|---|---|---|---|---|---|
| UFm | 50 | 50 | 50 | 50 | 200 ns | 200 ns | exact |
50 + 50 + 50 + 50 = 200 ns, and 1/5000 kHz = 200 ns. Exact, like the mainstream three.
So the tally across all five categories is: four exact, one over by 2.00 %.
| mode | sum of four | 1/fmax | verdict |
|---|---|---|---|
| Standard | 10000 ns | 10000 ns | exact |
| Fast | 2500 ns | 2500 ns | exact |
| Fast-mode Plus | 1000 ns | 1000 ns | exact |
| Hs-mode | 300 / 600 ns | 294.12 / 588.24 ns | over by 2.00 % |
| Ultra Fast-mode | 200 ns | 200 ns | exact |
Which is worth stating plainly, because it retrospectively confirms Chapter 14.2 §6's conclusion. The identity is a deliberate construction principle applied consistently across the specification's tables. Hs-mode is not an exception to the principle; it is the principle plus a rounded headline number. And the fact that UFm — a mode designed years later, on different electrical foundations, by a different logic — lands exactly on it is good evidence that the reading is right.
And note the shape of UFm's budget: half the period is phases and half is edges. In Standard-mode the edges are 13 % of the period; here they are 50 %. At 5 Mbit/s with a 50 ns edge allowance, the bus spends as much time transitioning as it does settled, which is another way of saying the same thing §3.2.1 says about reflections.
8. A UFm Frame, And What Is Missing From It
One UFm byte: nine bits, and the ninth is the master's own always-high not-acknowledge
10 cyclesThe two bottom rows are the whole chapter. Identical for eight intervals, different for one — and that one interval is where the acknowledge, and with it every question a master can ask, used to live.
9. The UFm Transmitter in Three Languages
The design is a UFm master transmitter. Writing it is mostly an exercise in not providing things: there is no sda_in port, no stretch input, no arbitration logic, and no delivered-byte count. Each of those absences is deliberate and each is asserted by the testbench, because an absence is the easiest thing in a design to accidentally fill in.
The output pair usda_out / usda_oe expresses push-pull directly: usda_oe is high at all times outside reset, because there is no high-impedance state to enter. A device held in reset releases the bus — which is correct, and is the one exception.
// -----------------------------------------------------------------------------
// i2c_ufm_transmitter.sv
// Ultra Fast-mode master transmitter (UM10204 3.2, 5.4, Table 6, Table 14).
//
// UFm keeps the I2C frame and throws away the I2C conversation. USDA and USCL
// are unidirectional push-pull outputs; no slave may ever drive either line.
// The consequences are structural, not numeric, and this block is written to
// make each one visible rather than to hide it:
//
// 1. Nine bits per byte, MSB first, exactly as in every other mode -- but the
// ninth bit is generated by the MASTER and is always driven HIGH. Table 6
// lists Acknowledge as "not possible"; the slot survives only so the frame
// stays byte-compatible. This block drives it HIGH and counts it.
// 2. The direction bit must be WRITE. A read request cannot be honoured on a
// unidirectional bus, so it is rejected up front rather than attempted.
// (UM10204 3.2.7 allows exactly one exception, the START byte, whose
// address 0000 0001 carries a set direction bit and is never a real read.)
// 3. No clock stretching: Table 6 lists it as not possible, and 3.2.5 states a
// slave "is not allowed to hold the clock LOW". There is no input by which
// it could, so the clock never pauses. The block has no stretch input, and
// that absence is the point.
// 4. No arbitration and no synchronization: only one master may be on the bus.
// There is no line to read back, so there is nothing to lose to.
// 5. Because nothing is ever read back, delivery is UNVERIFIABLE. The block
// reports bytes_sent, and deliberately reports no "delivered" count -- the
// spec itself concedes it is "impossible to determine that each slave is
// responsive". `unverifiable` is tied high whenever traffic has been sent.
// 6. Recovery therefore cannot be triggered by the bus. UM10204 3.2.13 makes
// detection external and prescribes a ladder: software reset, then hardware
// reset, then a power cycle to invoke the mandatory POR. That ladder is
// implemented here, driven by an input that does not come from the bus.
//
// Push-pull matters for the outputs: both lines are actively driven to both
// rails, so an idle line is HIGH because a transistor holds it there, not
// because a resistor does. `usda_oe`/`usda_out` express that directly.
// -----------------------------------------------------------------------------
module i2c_ufm_transmitter #(
parameter int CNT_W = 12
) (
input logic clk,
input logic rst_n,
// ---- command interface -------------------------------------------------
input logic go, // begin a transfer
input logic [6:0] slave_addr,
input logic rw_bit, // must be 0; a 1 is refused
input logic [7:0] wdata,
input logic wdata_valid, // another byte is available
input logic stop_req,
// ---- recovery, from OUTSIDE the bus (UM10204 3.2.13) -------------------
input logic slave_unresponsive, // external evidence, never bus evidence
input logic supports_swrst,
input logic supports_hwrst,
// ---- push-pull line outputs -------------------------------------------
output logic usda_out, // driven to both rails: push-pull
output logic usda_oe, // always 1 outside reset: never released
output logic uscl_out,
output logic uscl_oe,
// ---- status -----------------------------------------------------------
output logic busy,
output logic read_refused, // a read was requested and rejected
output logic unverifiable, // traffic was sent and cannot be confirmed
output logic [CNT_W-1:0] bytes_sent,
output logic [CNT_W-1:0] ninth_bits_driven_high,
output logic [2:0] recovery, // see RECOV_* below
output logic [3:0] state
);
localparam [3:0] S_IDLE = 4'd0,
S_START = 4'd1,
S_ADDR = 4'd2,
S_NINTH = 4'd3, // the acknowledge slot, driven HIGH by us
S_DATA = 4'd4,
S_STOP = 4'd5,
S_RECOV = 4'd6;
localparam [2:0] RECOV_NONE = 3'd0,
RECOV_SOFT = 3'd1, // General Call + 0000 0110
RECOV_HARD = 3'd2, // hardware reset pin
RECOV_POWER = 3'd3; // cycle power, invoke the mandatory POR
logic [7:0] shreg;
logic [3:0] bit_idx; // 7..0
logic scl_ph; // 0 = USCL LOW half, 1 = USCL HIGH half
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
usda_out <= 1'b1;
usda_oe <= 1'b0;
uscl_out <= 1'b1;
uscl_oe <= 1'b0;
busy <= 1'b0;
read_refused <= 1'b0;
unverifiable <= 1'b0;
bytes_sent <= {CNT_W{1'b0}};
ninth_bits_driven_high <= {CNT_W{1'b0}};
recovery <= RECOV_NONE;
shreg <= 8'h00;
bit_idx <= 4'd7;
scl_ph <= 1'b0;
end else begin
// Push-pull: both lines are always actively driven once out of reset.
// There is no high-impedance state and no pull-up resistor to wait for.
usda_oe <= 1'b1;
uscl_oe <= 1'b1;
case (state)
// ---------------------------------------------------------------
S_IDLE: begin
busy <= 1'b0;
usda_out <= 1'b1; // idle HIGH, held by the upper transistor
uscl_out <= 1'b1;
scl_ph <= 1'b0;
if (slave_unresponsive) begin
// Obligation 6. Detection came from outside the bus; pick the
// highest rung of the ladder the device actually supports.
if (supports_swrst) recovery <= RECOV_SOFT;
else if (supports_hwrst) recovery <= RECOV_HARD;
else recovery <= RECOV_POWER;
state <= S_RECOV;
end else if (go) begin
if (rw_bit && (slave_addr != 7'b0000_000)) begin
// Obligation 2. A read cannot be performed on a
// unidirectional bus. Refuse it; do not emit a START.
// The sole exception, the START byte 0000 0001, falls
// through to the transmit path below.
read_refused <= 1'b1;
state <= S_IDLE;
end else begin
read_refused <= 1'b0;
recovery <= RECOV_NONE;
shreg <= {slave_addr, rw_bit};
bit_idx <= 4'd7;
busy <= 1'b1;
state <= S_START;
end
end
end
// START: USDA HIGH -> LOW while USCL is HIGH.
S_START: begin
uscl_out <= 1'b1;
usda_out <= 1'b0;
scl_ph <= 1'b0;
state <= S_ADDR;
end
// ---------------------------------------------------------------
// Address byte and data bytes share one shifter. Data changes while
// USCL is LOW and is stable while it is HIGH (3.2.3, unchanged from
// standard I2C).
// ---------------------------------------------------------------
S_ADDR, S_DATA: begin
if (!scl_ph) begin
uscl_out <= 1'b0;
usda_out <= shreg[bit_idx[2:0]];
scl_ph <= 1'b1;
end else begin
uscl_out <= 1'b1; // the bit is sampled in this half
scl_ph <= 1'b0;
if (bit_idx == 4'd0) begin
bytes_sent <= bytes_sent + 1'b1;
unverifiable <= 1'b1; // obligation 5: sent, never confirmed
state <= S_NINTH;
end else begin
bit_idx <= bit_idx - 1'b1;
end
end
end
// ---------------------------------------------------------------
// The ninth clock. Obligation 1: the master generates it and drives
// the data line HIGH. No slave contributes anything here, so this
// is a NACK in shape and a formality in substance.
// ---------------------------------------------------------------
S_NINTH: begin
if (!scl_ph) begin
uscl_out <= 1'b0;
usda_out <= 1'b1; // always HIGH: Table 6, Acknowledge n/p
scl_ph <= 1'b1;
end else begin
uscl_out <= 1'b1;
scl_ph <= 1'b0;
ninth_bits_driven_high <= ninth_bits_driven_high + 1'b1;
if (stop_req || !wdata_valid) begin
state <= S_STOP;
end else begin
shreg <= wdata;
bit_idx <= 4'd7;
state <= S_DATA;
end
end
end
// STOP: USDA LOW -> HIGH while USCL is HIGH.
S_STOP: begin
uscl_out <= 1'b1;
usda_out <= 1'b1;
busy <= 1'b0;
state <= S_IDLE;
end
// ---------------------------------------------------------------
// Recovery. Nothing here is driven by the bus, because nothing on
// the bus could have told us anything.
// ---------------------------------------------------------------
S_RECOV: begin
busy <= 1'b0;
usda_out <= 1'b1;
uscl_out <= 1'b1;
if (!slave_unresponsive) state <= S_IDLE;
end
default: state <= S_IDLE;
endcase
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_ufm_transmitter_tb.sv
// Independent oracle for i2c_ufm_transmitter.
//
// The bench reconstructs every byte by sampling USDA on the rising edge of
// USCL -- the way a real UFm slave would -- and compares it against what it
// asked to be sent. It also asserts the properties UFm gives UP, which is the
// harder half: that no slave input exists, that the ninth bit is always HIGH,
// that both lines are always actively driven, and that a read is refused.
//
// Note what the bench CANNOT do: it cannot check that a byte arrived. Nothing
// in UFm makes that observable. The suite therefore checks that the design does
// not pretend otherwise.
// -----------------------------------------------------------------------------
module i2c_ufm_transmitter_tb;
localparam [3:0] S_IDLE = 4'd0, S_START = 4'd1, S_ADDR = 4'd2,
S_NINTH = 4'd3, S_DATA = 4'd4, S_STOP = 4'd5, S_RECOV = 4'd6;
localparam [2:0] RECOV_NONE = 3'd0, RECOV_SOFT = 3'd1,
RECOV_HARD = 3'd2, RECOV_POWER = 3'd3;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic go = 1'b0;
logic [6:0] slave_addr = 7'h2A;
logic rw_bit = 1'b0;
logic [7:0] wdata = 8'h00;
logic wdata_valid = 1'b0;
logic stop_req = 1'b0;
logic slave_unresponsive = 1'b0;
logic supports_swrst = 1'b1;
logic supports_hwrst = 1'b1;
logic usda_out, usda_oe, uscl_out, uscl_oe;
logic busy, read_refused, unverifiable;
logic [11:0] bytes_sent, ninth_bits_driven_high;
logic [2:0] recovery;
logic [3:0] state;
integer errors = 0;
integer n;
i2c_ufm_transmitter #(.CNT_W(12)) dut (
.clk(clk), .rst_n(rst_n),
.go(go), .slave_addr(slave_addr), .rw_bit(rw_bit),
.wdata(wdata), .wdata_valid(wdata_valid), .stop_req(stop_req),
.slave_unresponsive(slave_unresponsive),
.supports_swrst(supports_swrst), .supports_hwrst(supports_hwrst),
.usda_out(usda_out), .usda_oe(usda_oe),
.uscl_out(uscl_out), .uscl_oe(uscl_oe),
.busy(busy), .read_refused(read_refused), .unverifiable(unverifiable),
.bytes_sent(bytes_sent), .ninth_bits_driven_high(ninth_bits_driven_high),
.recovery(recovery), .state(state));
always #10 clk = ~clk;
// ------------------------------------------------------------------
// The slave's view of the bus: sample USDA on every USCL rising edge.
// This is an observer only -- it never drives anything, because in UFm it
// physically could not.
// ------------------------------------------------------------------
logic uscl_q = 1'b1;
logic [8:0] rx_sh = 9'h000; // 9 bits: 8 data + the ninth slot
integer rx_bits = 0;
integer rx_count = 0;
logic [8:0] rx_frame [0:15];
logic oe_violation = 1'b0;
integer settled = 0;
always @(posedge clk) begin
if (rst_n) begin
// Both lines must be actively driven at all times: push-pull, no
// high-impedance state, ever. In reset the outputs are released, which
// is correct -- a device held in reset must not drive the bus -- so the
// check starts one cycle after reset is lifted.
settled <= settled + 1;
if (settled >= 2 && (!usda_oe || !uscl_oe)) oe_violation <= 1'b1;
if (uscl_out && !uscl_q) begin // USCL rising edge
rx_sh <= {rx_sh[7:0], usda_out};
rx_bits = rx_bits + 1;
if (rx_bits == 9) begin
rx_frame[rx_count] = {rx_sh[7:0], usda_out};
rx_count = rx_count + 1;
rx_bits = 0;
end
end
uscl_q <= uscl_out;
end else begin
uscl_q <= 1'b1;
settled <= 0;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; go = 1'b0; wdata_valid = 1'b0; stop_req = 1'b0;
slave_unresponsive = 1'b0; rw_bit = 1'b0;
rx_bits = 0; rx_count = 0; oe_violation = 1'b0; settled = 0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
@(posedge clk); @(negedge clk);
end
endtask
task ck_int (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input got, input exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0b expected %0b", what, got, exp);
errors = errors + 1;
end
end
endtask
// Run until the DUT is idle again, or a generous bound elapses.
task run_until_idle (input integer bound);
begin
n = 0;
while (state != S_IDLE && n < bound) begin step; n = n + 1; end
if (n >= bound) begin
$display(" FAIL run_until_idle: still in state %0d after %0d ticks", state, bound);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_ufm_transmitter: Ultra Fast-mode write path ===");
// ----------------------------------------------------------------
// T1. A one-byte write. The bench reconstructs both frames from the
// wire and checks them, including the ninth bit of each.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h2A; rw_bit = 1'b0; wdata = 8'hC3; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
// one data byte only
n = 0;
while (bytes_sent < 2 && n < 200) begin step; n = n + 1; end
@(negedge clk); wdata_valid = 1'b0; // stop after the data byte, not the address
run_until_idle(400);
$display("T1 single-byte write reconstructed from the wire");
ck_int("T1 two frames seen (address + data)", rx_count, 2);
// Frame 0: address byte 0x2A with W=0 -> 0101 0100, then the ninth bit HIGH.
ck_int("T1 address byte on the wire", rx_frame[0][8:1], {7'h2A, 1'b0});
ck_bit("T1 address frame ninth bit HIGH", rx_frame[0][0], 1'b1);
ck_int("T1 data byte on the wire", rx_frame[1][8:1], 8'hC3);
ck_bit("T1 data frame ninth bit HIGH", rx_frame[1][0], 1'b1);
ck_int("T1 bytes_sent", bytes_sent, 2);
ck_int("T1 ninth bits driven HIGH", ninth_bits_driven_high, 2);
ck_bit("T1 delivery is unverifiable", unverifiable, 1'b1);
ck_bit("T1 no output ever released", oe_violation, 1'b0);
ck_bit("T1 not busy at the end", busy, 1'b0);
// ----------------------------------------------------------------
// T2. THE DEFINING PROPERTY. Four data bytes, and every single ninth
// bit is HIGH. In any bidirectional mode at least one of these
// would be a slave-driven LOW; here a LOW is impossible, so the
// count of ninth bits equals the count of bytes and every one is 1.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h55; rw_bit = 1'b0; wdata = 8'h01; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
for (n = 0; n < 4; n = n + 1) begin
while (state != S_NINTH) step;
@(negedge clk); wdata = 8'h10 + n[7:0];
while (state == S_NINTH) step;
end
@(negedge clk); wdata_valid = 1'b0;
run_until_idle(600);
$display("T2 every ninth bit is master-driven HIGH");
ck_int("T2 five frames (address + four data)", rx_count, 5);
for (n = 0; n < 5; n = n + 1) begin
if (rx_frame[n][0] !== 1'b1) begin
$display(" FAIL T2 frame %0d ninth bit: got %0b expected 1", n, rx_frame[n][0]);
errors = errors + 1;
end
end
ck_int("T2 ninth bits == frames", ninth_bits_driven_high, rx_count);
ck_int("T2 bytes_sent == frames", bytes_sent, rx_count);
// ----------------------------------------------------------------
// T3. A READ IS REFUSED. rw_bit = 1 on a real address cannot work on a
// unidirectional bus. No START must be emitted and no byte sent.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h2A; rw_bit = 1'b1; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; step; @(negedge clk); go = 1'b0;
step; step;
$display("T3 a read request is refused, not attempted");
ck_bit("T3 read refused", read_refused, 1'b1);
ck_int("T3 stayed idle", state, S_IDLE);
ck_int("T3 nothing sent", bytes_sent, 0);
ck_int("T3 nothing on the wire", rx_count, 0);
ck_bit("T3 never became busy", busy, 1'b0);
// ----------------------------------------------------------------
// T4. THE ONE EXCEPTION. The START byte is 0000 0001: address 0000 000
// with the direction bit set. UM10204 3.2.7 permits it, so it must
// NOT be refused, and it must appear on the wire verbatim.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'b0000_000; rw_bit = 1'b1; wdata_valid = 1'b0;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
run_until_idle(400);
$display("T4 the START byte 0000 0001 is permitted");
ck_bit("T4 not refused", read_refused, 1'b0);
ck_int("T4 one frame sent", rx_count, 1);
ck_int("T4 START byte on the wire", rx_frame[0][8:1], 8'b0000_0001);
ck_bit("T4 its ninth bit is HIGH too", rx_frame[0][0], 1'b1);
// ----------------------------------------------------------------
// T5. DATA VALIDITY (3.2.3). USDA may change only while USCL is LOW.
// Sampled across a whole multi-byte transfer.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h3C; rw_bit = 1'b0; wdata = 8'hA5; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
// UM10204 3.2.3 requires USDA to be stable for the WHOLE HIGH period, and
// that includes the rising edge itself. Flagging only changes strictly
// inside a HIGH plateau misses the worst case: a change landing exactly ON
// the rising edge, where a slave sampling there catches a racing value.
// The test is therefore "did USDA change into a cycle whose USCL is HIGH",
// excluding the START and STOP states, where SDA moving while SCL is HIGH
// is the defining event rather than a fault.
begin : validity
logic usda_prev, uscl_prev;
logic [3:0] state_prev;
integer viol;
viol = 0;
usda_prev = usda_out; uscl_prev = uscl_out; state_prev = state;
for (n = 0; n < 250; n = n + 1) begin
step;
if ((usda_out !== usda_prev) && uscl_out
&& (state_prev == S_ADDR || state_prev == S_NINTH || state_prev == S_DATA)
&& (state == S_ADDR || state == S_NINTH || state == S_DATA))
viol = viol + 1;
usda_prev = usda_out; uscl_prev = uscl_out; state_prev = state;
if (n == 120) begin @(negedge clk); wdata_valid = 1'b0; end
end
$display("T5 USDA is stable for the whole USCL HIGH period, edge included");
ck_int("T5 data-validity violations", viol, 0);
end
run_until_idle(400);
// ----------------------------------------------------------------
// T6. RECOVERY LADDER (3.2.13). Detection is external. With software
// reset available, that is the rung chosen.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b1; supports_hwrst = 1'b1;
@(negedge clk); slave_unresponsive = 1'b1;
step; step;
$display("T6 external evidence selects the software reset rung");
ck_int("T6 recovery = software reset", recovery, RECOV_SOFT);
ck_int("T6 in recovery", state, S_RECOV);
ck_bit("T6 not busy", busy, 1'b0);
@(negedge clk); slave_unresponsive = 1'b0;
step; step;
ck_int("T6 returns to idle", state, S_IDLE);
// ----------------------------------------------------------------
// T7. No software reset: fall to the hardware reset rung.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b0; supports_hwrst = 1'b1;
@(negedge clk); slave_unresponsive = 1'b1;
step; step;
$display("T7 without software reset, the hardware pin is chosen");
ck_int("T7 recovery = hardware reset", recovery, RECOV_HARD);
// ----------------------------------------------------------------
// T8. Neither supported: the only remaining option is a power cycle,
// which invokes the mandatory internal POR.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b0; supports_hwrst = 1'b0;
@(negedge clk); slave_unresponsive = 1'b1;
step; step;
$display("T8 with neither, a power cycle is the only rung left");
ck_int("T8 recovery = power cycle", recovery, RECOV_POWER);
// ----------------------------------------------------------------
// T9. The recovery path is never reachable FROM the bus. Send a full
// clean transfer with slave_unresponsive held LOW throughout and
// confirm recovery stays NONE -- nothing the transfer did, and
// nothing any slave could have done, can raise it.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b1; supports_hwrst = 1'b1;
slave_addr = 7'h11; rw_bit = 1'b0; wdata = 8'hFF; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
for (n = 0; n < 150; n = n + 1) begin
step;
if (recovery !== RECOV_NONE) begin
$display(" FAIL T9 recovery rose during a normal transfer");
errors = errors + 1;
n = 150;
end
end
@(negedge clk); wdata_valid = 1'b0;
run_until_idle(400);
$display("T9 no bus activity can trigger recovery");
ck_int("T9 recovery still NONE", recovery, RECOV_NONE);
// ----------------------------------------------------------------
// T10. An explicit STOP request ends the transfer after the byte in
// flight, not in the middle of it.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h7F; rw_bit = 1'b0; wdata = 8'h5A; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
while (bytes_sent < 2) step; // address byte + one data byte
@(negedge clk); stop_req = 1'b1;
run_until_idle(400);
@(negedge clk); stop_req = 1'b0;
$display("T10 STOP ends the transfer on a byte boundary");
ck_int("T10 whole frames only", rx_count, bytes_sent);
ck_bit("T10 idle and not busy", busy, 1'b0);
ck_bit("T10 lines left HIGH", usda_out & uscl_out, 1'b1);
ck_bit("T10 outputs still driven", usda_oe & uscl_oe, 1'b1);
if (errors == 0)
$display("=== i2c_ufm_transmitter: ALL CHECKS PASSED ===");
else
$display("=== i2c_ufm_transmitter: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule // -----------------------------------------------------------------------------
// i2c_ufm_transmitter.sv
// Ultra Fast-mode master transmitter (UM10204 3.2, 5.4, Table 6, Table 14).
//
// UFm keeps the I2C frame and throws away the I2C conversation. USDA and USCL
// are unidirectional push-pull outputs; no slave may ever drive either line.
// The consequences are structural, not numeric, and this block is written to
// make each one visible rather than to hide it:
//
// 1. Nine bits per byte, MSB first, exactly as in every other mode -- but the
// ninth bit is generated by the MASTER and is always driven HIGH. Table 6
// lists Acknowledge as "not possible"; the slot survives only so the frame
// stays byte-compatible. This block drives it HIGH and counts it.
// 2. The direction bit must be WRITE. A read request cannot be honoured on a
// unidirectional bus, so it is rejected up front rather than attempted.
// (UM10204 3.2.7 allows exactly one exception, the START byte, whose
// address 0000 0001 carries a set direction bit and is never a real read.)
// 3. No clock stretching: Table 6 lists it as not possible, and 3.2.5 states a
// slave "is not allowed to hold the clock LOW". There is no input by which
// it could, so the clock never pauses. The block has no stretch input, and
// that absence is the point.
// 4. No arbitration and no synchronization: only one master may be on the bus.
// There is no line to read back, so there is nothing to lose to.
// 5. Because nothing is ever read back, delivery is UNVERIFIABLE. The block
// reports bytes_sent, and deliberately reports no "delivered" count -- the
// spec itself concedes it is "impossible to determine that each slave is
// responsive". `unverifiable` is tied high whenever traffic has been sent.
// 6. Recovery therefore cannot be triggered by the bus. UM10204 3.2.13 makes
// detection external and prescribes a ladder: software reset, then hardware
// reset, then a power cycle to invoke the mandatory POR. That ladder is
// implemented here, driven by an input that does not come from the bus.
//
// Push-pull matters for the outputs: both lines are actively driven to both
// rails, so an idle line is HIGH because a transistor holds it there, not
// because a resistor does. `usda_oe`/`usda_out` express that directly.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_ufm_transmitter #(
parameter CNT_W = 12
) (
input wire clk,
input wire rst_n,
// ---- command interface -------------------------------------------------
input wire go, // begin a transfer
input wire [6:0] slave_addr,
input wire rw_bit, // must be 0; a 1 is refused
input wire [7:0] wdata,
input wire wdata_valid, // another byte is available
input wire stop_req,
// ---- recovery, from OUTSIDE the bus (UM10204 3.2.13) -------------------
input wire slave_unresponsive, // external evidence, never bus evidence
input wire supports_swrst,
input wire supports_hwrst,
// ---- push-pull line outputs -------------------------------------------
output reg usda_out, // driven to both rails: push-pull
output reg usda_oe, // always 1 outside reset: never released
output reg uscl_out,
output reg uscl_oe,
// ---- status -----------------------------------------------------------
output reg busy,
output reg read_refused, // a read was requested and rejected
output reg unverifiable, // traffic was sent and cannot be confirmed
output reg [CNT_W-1:0] bytes_sent,
output reg [CNT_W-1:0] ninth_bits_driven_high,
output reg [2:0] recovery, // see RECOV_* below
output reg [3:0] state
);
localparam [3:0] S_IDLE = 4'd0,
S_START = 4'd1,
S_ADDR = 4'd2,
S_NINTH = 4'd3, // the acknowledge slot, driven HIGH by us
S_DATA = 4'd4,
S_STOP = 4'd5,
S_RECOV = 4'd6;
localparam [2:0] RECOV_NONE = 3'd0,
RECOV_SOFT = 3'd1, // General Call + 0000 0110
RECOV_HARD = 3'd2, // hardware reset pin
RECOV_POWER = 3'd3; // cycle power, invoke the mandatory POR
reg [7:0] shreg;
reg [3:0] bit_idx; // 7..0
reg scl_ph; // 0 = USCL LOW half, 1 = USCL HIGH half
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_IDLE;
usda_out <= 1'b1;
usda_oe <= 1'b0;
uscl_out <= 1'b1;
uscl_oe <= 1'b0;
busy <= 1'b0;
read_refused <= 1'b0;
unverifiable <= 1'b0;
bytes_sent <= {CNT_W{1'b0}};
ninth_bits_driven_high <= {CNT_W{1'b0}};
recovery <= RECOV_NONE;
shreg <= 8'h00;
bit_idx <= 4'd7;
scl_ph <= 1'b0;
end else begin
// Push-pull: both lines are always actively driven once out of reset.
// There is no high-impedance state and no pull-up resistor to wait for.
usda_oe <= 1'b1;
uscl_oe <= 1'b1;
case (state)
// ---------------------------------------------------------------
S_IDLE: begin
busy <= 1'b0;
usda_out <= 1'b1; // idle HIGH, held by the upper transistor
uscl_out <= 1'b1;
scl_ph <= 1'b0;
if (slave_unresponsive) begin
// Obligation 6. Detection came from outside the bus; pick the
// highest rung of the ladder the device actually supports.
if (supports_swrst) recovery <= RECOV_SOFT;
else if (supports_hwrst) recovery <= RECOV_HARD;
else recovery <= RECOV_POWER;
state <= S_RECOV;
end else if (go) begin
if (rw_bit && (slave_addr != 7'b0000_000)) begin
// Obligation 2. A read cannot be performed on a
// unidirectional bus. Refuse it; do not emit a START.
// The sole exception, the START byte 0000 0001, falls
// through to the transmit path below.
read_refused <= 1'b1;
state <= S_IDLE;
end else begin
read_refused <= 1'b0;
recovery <= RECOV_NONE;
shreg <= {slave_addr, rw_bit};
bit_idx <= 4'd7;
busy <= 1'b1;
state <= S_START;
end
end
end
// START: USDA HIGH -> LOW while USCL is HIGH.
S_START: begin
uscl_out <= 1'b1;
usda_out <= 1'b0;
scl_ph <= 1'b0;
state <= S_ADDR;
end
// ---------------------------------------------------------------
// Address byte and data bytes share one shifter. Data changes while
// USCL is LOW and is stable while it is HIGH (3.2.3, unchanged from
// standard I2C).
// ---------------------------------------------------------------
S_ADDR, S_DATA: begin
if (!scl_ph) begin
uscl_out <= 1'b0;
usda_out <= shreg[bit_idx[2:0]];
scl_ph <= 1'b1;
end else begin
uscl_out <= 1'b1; // the bit is sampled in this half
scl_ph <= 1'b0;
if (bit_idx == 4'd0) begin
bytes_sent <= bytes_sent + 1'b1;
unverifiable <= 1'b1; // obligation 5: sent, never confirmed
state <= S_NINTH;
end else begin
bit_idx <= bit_idx - 1'b1;
end
end
end
// ---------------------------------------------------------------
// The ninth clock. Obligation 1: the master generates it and drives
// the data line HIGH. No slave contributes anything here, so this
// is a NACK in shape and a formality in substance.
// ---------------------------------------------------------------
S_NINTH: begin
if (!scl_ph) begin
uscl_out <= 1'b0;
usda_out <= 1'b1; // always HIGH: Table 6, Acknowledge n/p
scl_ph <= 1'b1;
end else begin
uscl_out <= 1'b1;
scl_ph <= 1'b0;
ninth_bits_driven_high <= ninth_bits_driven_high + 1'b1;
if (stop_req || !wdata_valid) begin
state <= S_STOP;
end else begin
shreg <= wdata;
bit_idx <= 4'd7;
state <= S_DATA;
end
end
end
// STOP: USDA LOW -> HIGH while USCL is HIGH.
S_STOP: begin
uscl_out <= 1'b1;
usda_out <= 1'b1;
busy <= 1'b0;
state <= S_IDLE;
end
// ---------------------------------------------------------------
// Recovery. Nothing here is driven by the bus, because nothing on
// the bus could have told us anything.
// ---------------------------------------------------------------
S_RECOV: begin
busy <= 1'b0;
usda_out <= 1'b1;
uscl_out <= 1'b1;
if (!slave_unresponsive) state <= S_IDLE;
end
default: state <= S_IDLE;
endcase
end
end
endmodule `timescale 1ns/1ps
// -----------------------------------------------------------------------------
// i2c_ufm_transmitter_tb.sv
// Independent oracle for i2c_ufm_transmitter.
//
// The bench reconstructs every byte by sampling USDA on the rising edge of
// USCL -- the way a real UFm slave would -- and compares it against what it
// asked to be sent. It also asserts the properties UFm gives UP, which is the
// harder half: that no slave input exists, that the ninth bit is always HIGH,
// that both lines are always actively driven, and that a read is refused.
//
// Note what the bench CANNOT do: it cannot check that a byte arrived. Nothing
// in UFm makes that observable. The suite therefore checks that the design does
// not pretend otherwise.
// -----------------------------------------------------------------------------
// (Verilog-2001 -- structurally identical to the SystemVerilog above.)
module i2c_ufm_transmitter_tb;
localparam [3:0] S_IDLE = 4'd0, S_START = 4'd1, S_ADDR = 4'd2,
S_NINTH = 4'd3, S_DATA = 4'd4, S_STOP = 4'd5, S_RECOV = 4'd6;
localparam [2:0] RECOV_NONE = 3'd0, RECOV_SOFT = 3'd1,
RECOV_HARD = 3'd2, RECOV_POWER = 3'd3;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg go = 1'b0;
reg [6:0] slave_addr = 7'h2A;
reg rw_bit = 1'b0;
reg [7:0] wdata = 8'h00;
reg wdata_valid = 1'b0;
reg stop_req = 1'b0;
reg slave_unresponsive = 1'b0;
reg supports_swrst = 1'b1;
reg supports_hwrst = 1'b1;
wire usda_out, usda_oe, uscl_out, uscl_oe;
wire busy, read_refused, unverifiable;
wire [11:0] bytes_sent, ninth_bits_driven_high;
wire [2:0] recovery;
wire [3:0] state;
integer errors = 0;
integer n;
i2c_ufm_transmitter #(.CNT_W(12)) dut (
.clk(clk), .rst_n(rst_n),
.go(go), .slave_addr(slave_addr), .rw_bit(rw_bit),
.wdata(wdata), .wdata_valid(wdata_valid), .stop_req(stop_req),
.slave_unresponsive(slave_unresponsive),
.supports_swrst(supports_swrst), .supports_hwrst(supports_hwrst),
.usda_out(usda_out), .usda_oe(usda_oe),
.uscl_out(uscl_out), .uscl_oe(uscl_oe),
.busy(busy), .read_refused(read_refused), .unverifiable(unverifiable),
.bytes_sent(bytes_sent), .ninth_bits_driven_high(ninth_bits_driven_high),
.recovery(recovery), .state(state));
always #10 clk = ~clk;
// ------------------------------------------------------------------
// The slave's view of the bus: sample USDA on every USCL rising edge.
// This is an observer only -- it never drives anything, because in UFm it
// physically could not.
// ------------------------------------------------------------------
reg uscl_q = 1'b1;
reg [8:0] rx_sh = 9'h000; // 9 bits: 8 data + the ninth slot
integer rx_bits = 0;
integer rx_count = 0;
reg [8:0] rx_frame [0:15];
reg oe_violation = 1'b0;
integer settled = 0;
always @(posedge clk) begin
if (rst_n) begin
// Both lines must be actively driven at all times: push-pull, no
// high-impedance state, ever. In reset the outputs are released, which
// is correct -- a device held in reset must not drive the bus -- so the
// check starts one cycle after reset is lifted.
settled <= settled + 1;
if (settled >= 2 && (!usda_oe || !uscl_oe)) oe_violation <= 1'b1;
if (uscl_out && !uscl_q) begin // USCL rising edge
rx_sh <= {rx_sh[7:0], usda_out};
rx_bits = rx_bits + 1;
if (rx_bits == 9) begin
rx_frame[rx_count] = {rx_sh[7:0], usda_out};
rx_count = rx_count + 1;
rx_bits = 0;
end
end
uscl_q <= uscl_out;
end else begin
uscl_q <= 1'b1;
settled <= 0;
end
end
task step; begin @(posedge clk); @(negedge clk); end endtask
task do_reset;
begin
@(negedge clk);
rst_n = 1'b0; go = 1'b0; wdata_valid = 1'b0; stop_req = 1'b0;
slave_unresponsive = 1'b0; rw_bit = 1'b0;
rx_bits = 0; rx_count = 0; oe_violation = 1'b0; settled = 0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
@(posedge clk); @(negedge clk);
end
endtask
task ck_int (input [200*8:1] what, input integer got, input integer exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0d expected %0d", what, got, exp);
errors = errors + 1;
end
end
endtask
task ck_bit (input [200*8:1] what, input got, input exp);
begin
if (got !== exp) begin
$display(" FAIL %0s: got %0b expected %0b", what, got, exp);
errors = errors + 1;
end
end
endtask
// Run until the DUT is idle again, or a generous bound elapses.
task run_until_idle (input integer bound);
begin
n = 0;
while (state != S_IDLE && n < bound) begin step; n = n + 1; end
if (n >= bound) begin
$display(" FAIL run_until_idle: still in state %0d after %0d ticks", state, bound);
errors = errors + 1;
end
end
endtask
initial begin
$display("=== i2c_ufm_transmitter: Ultra Fast-mode write path ===");
// ----------------------------------------------------------------
// T1. A one-byte write. The bench reconstructs both frames from the
// wire and checks them, including the ninth bit of each.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h2A; rw_bit = 1'b0; wdata = 8'hC3; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
// one data byte only
n = 0;
while (bytes_sent < 2 && n < 200) begin step; n = n + 1; end
@(negedge clk); wdata_valid = 1'b0; // stop after the data byte, not the address
run_until_idle(400);
$display("T1 single-byte write reconstructed from the wire");
ck_int("T1 two frames seen (address + data)", rx_count, 2);
// Frame 0: address byte 0x2A with W=0 -> 0101 0100, then the ninth bit HIGH.
ck_int("T1 address byte on the wire", rx_frame[0][8:1], {7'h2A, 1'b0});
ck_bit("T1 address frame ninth bit HIGH", rx_frame[0][0], 1'b1);
ck_int("T1 data byte on the wire", rx_frame[1][8:1], 8'hC3);
ck_bit("T1 data frame ninth bit HIGH", rx_frame[1][0], 1'b1);
ck_int("T1 bytes_sent", bytes_sent, 2);
ck_int("T1 ninth bits driven HIGH", ninth_bits_driven_high, 2);
ck_bit("T1 delivery is unverifiable", unverifiable, 1'b1);
ck_bit("T1 no output ever released", oe_violation, 1'b0);
ck_bit("T1 not busy at the end", busy, 1'b0);
// ----------------------------------------------------------------
// T2. THE DEFINING PROPERTY. Four data bytes, and every single ninth
// bit is HIGH. In any bidirectional mode at least one of these
// would be a slave-driven LOW; here a LOW is impossible, so the
// count of ninth bits equals the count of bytes and every one is 1.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h55; rw_bit = 1'b0; wdata = 8'h01; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
for (n = 0; n < 4; n = n + 1) begin
while (state != S_NINTH) step;
@(negedge clk); wdata = 8'h10 + n[7:0];
while (state == S_NINTH) step;
end
@(negedge clk); wdata_valid = 1'b0;
run_until_idle(600);
$display("T2 every ninth bit is master-driven HIGH");
ck_int("T2 five frames (address + four data)", rx_count, 5);
for (n = 0; n < 5; n = n + 1) begin
if (rx_frame[n][0] !== 1'b1) begin
$display(" FAIL T2 frame %0d ninth bit: got %0b expected 1", n, rx_frame[n][0]);
errors = errors + 1;
end
end
ck_int("T2 ninth bits == frames", ninth_bits_driven_high, rx_count);
ck_int("T2 bytes_sent == frames", bytes_sent, rx_count);
// ----------------------------------------------------------------
// T3. A READ IS REFUSED. rw_bit = 1 on a real address cannot work on a
// unidirectional bus. No START must be emitted and no byte sent.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h2A; rw_bit = 1'b1; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; step; @(negedge clk); go = 1'b0;
step; step;
$display("T3 a read request is refused, not attempted");
ck_bit("T3 read refused", read_refused, 1'b1);
ck_int("T3 stayed idle", state, S_IDLE);
ck_int("T3 nothing sent", bytes_sent, 0);
ck_int("T3 nothing on the wire", rx_count, 0);
ck_bit("T3 never became busy", busy, 1'b0);
// ----------------------------------------------------------------
// T4. THE ONE EXCEPTION. The START byte is 0000 0001: address 0000 000
// with the direction bit set. UM10204 3.2.7 permits it, so it must
// NOT be refused, and it must appear on the wire verbatim.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'b0000_000; rw_bit = 1'b1; wdata_valid = 1'b0;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
run_until_idle(400);
$display("T4 the START byte 0000 0001 is permitted");
ck_bit("T4 not refused", read_refused, 1'b0);
ck_int("T4 one frame sent", rx_count, 1);
ck_int("T4 START byte on the wire", rx_frame[0][8:1], 8'b0000_0001);
ck_bit("T4 its ninth bit is HIGH too", rx_frame[0][0], 1'b1);
// ----------------------------------------------------------------
// T5. DATA VALIDITY (3.2.3). USDA may change only while USCL is LOW.
// Sampled across a whole multi-byte transfer.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h3C; rw_bit = 1'b0; wdata = 8'hA5; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
// UM10204 3.2.3 requires USDA to be stable for the WHOLE HIGH period, and
// that includes the rising edge itself. Flagging only changes strictly
// inside a HIGH plateau misses the worst case: a change landing exactly ON
// the rising edge, where a slave sampling there catches a racing value.
// The test is therefore "did USDA change into a cycle whose USCL is HIGH",
// excluding the START and STOP states, where SDA moving while SCL is HIGH
// is the defining event rather than a fault.
begin : validity
reg usda_prev, uscl_prev;
reg [3:0] state_prev;
integer viol;
viol = 0;
usda_prev = usda_out; uscl_prev = uscl_out; state_prev = state;
for (n = 0; n < 250; n = n + 1) begin
step;
if ((usda_out !== usda_prev) && uscl_out
&& (state_prev == S_ADDR || state_prev == S_NINTH || state_prev == S_DATA)
&& (state == S_ADDR || state == S_NINTH || state == S_DATA))
viol = viol + 1;
usda_prev = usda_out; uscl_prev = uscl_out; state_prev = state;
if (n == 120) begin @(negedge clk); wdata_valid = 1'b0; end
end
$display("T5 USDA is stable for the whole USCL HIGH period, edge included");
ck_int("T5 data-validity violations", viol, 0);
end
run_until_idle(400);
// ----------------------------------------------------------------
// T6. RECOVERY LADDER (3.2.13). Detection is external. With software
// reset available, that is the rung chosen.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b1; supports_hwrst = 1'b1;
@(negedge clk); slave_unresponsive = 1'b1;
step; step;
$display("T6 external evidence selects the software reset rung");
ck_int("T6 recovery = software reset", recovery, RECOV_SOFT);
ck_int("T6 in recovery", state, S_RECOV);
ck_bit("T6 not busy", busy, 1'b0);
@(negedge clk); slave_unresponsive = 1'b0;
step; step;
ck_int("T6 returns to idle", state, S_IDLE);
// ----------------------------------------------------------------
// T7. No software reset: fall to the hardware reset rung.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b0; supports_hwrst = 1'b1;
@(negedge clk); slave_unresponsive = 1'b1;
step; step;
$display("T7 without software reset, the hardware pin is chosen");
ck_int("T7 recovery = hardware reset", recovery, RECOV_HARD);
// ----------------------------------------------------------------
// T8. Neither supported: the only remaining option is a power cycle,
// which invokes the mandatory internal POR.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b0; supports_hwrst = 1'b0;
@(negedge clk); slave_unresponsive = 1'b1;
step; step;
$display("T8 with neither, a power cycle is the only rung left");
ck_int("T8 recovery = power cycle", recovery, RECOV_POWER);
// ----------------------------------------------------------------
// T9. The recovery path is never reachable FROM the bus. Send a full
// clean transfer with slave_unresponsive held LOW throughout and
// confirm recovery stays NONE -- nothing the transfer did, and
// nothing any slave could have done, can raise it.
// ----------------------------------------------------------------
do_reset;
supports_swrst = 1'b1; supports_hwrst = 1'b1;
slave_addr = 7'h11; rw_bit = 1'b0; wdata = 8'hFF; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
for (n = 0; n < 150; n = n + 1) begin
step;
if (recovery !== RECOV_NONE) begin
$display(" FAIL T9 recovery rose during a normal transfer");
errors = errors + 1;
n = 150;
end
end
@(negedge clk); wdata_valid = 1'b0;
run_until_idle(400);
$display("T9 no bus activity can trigger recovery");
ck_int("T9 recovery still NONE", recovery, RECOV_NONE);
// ----------------------------------------------------------------
// T10. An explicit STOP request ends the transfer after the byte in
// flight, not in the middle of it.
// ----------------------------------------------------------------
do_reset;
slave_addr = 7'h7F; rw_bit = 1'b0; wdata = 8'h5A; wdata_valid = 1'b1;
@(negedge clk); go = 1'b1; step; @(negedge clk); go = 1'b0;
while (bytes_sent < 2) step; // address byte + one data byte
@(negedge clk); stop_req = 1'b1;
run_until_idle(400);
@(negedge clk); stop_req = 1'b0;
$display("T10 STOP ends the transfer on a byte boundary");
ck_int("T10 whole frames only", rx_count, bytes_sent);
ck_bit("T10 idle and not busy", busy, 1'b0);
ck_bit("T10 lines left HIGH", usda_out & uscl_out, 1'b1);
ck_bit("T10 outputs still driven", usda_oe & uscl_oe, 1'b1);
if (errors == 0)
$display("=== i2c_ufm_transmitter: ALL CHECKS PASSED ===");
else
$display("=== i2c_ufm_transmitter: %0d CHECK(S) FAILED ===", errors);
$finish;
end
endmodule -- ---------------------------------------------------------------------------
-- i2c_ufm_transmitter.vhd
-- Ultra Fast-mode master transmitter (UM10204 3.2, 5.4, Table 6, Table 14).
-- Behavioural twin of i2c_ufm_transmitter.sv / .v.
--
-- UFm keeps the I2C frame and throws away the I2C conversation. USDA and USCL
-- are unidirectional push-pull outputs; no slave may ever drive either line.
-- The consequences are structural, not numeric:
--
-- 1. Nine bits per byte, MSB first, exactly as in every other mode -- but the
-- ninth bit is generated by the MASTER and is always driven HIGH. Table 6
-- lists Acknowledge as "not possible"; the slot survives only so the frame
-- stays byte-compatible.
-- 2. The direction bit must be WRITE. A read request cannot be honoured on a
-- unidirectional bus, so it is rejected up front. UM10204 3.2.7 allows
-- exactly one exception: the START byte 0000 0001, which carries a set
-- direction bit but is a synchronisation aid, not a read. That exception is
-- implemented -- address 0000 000 with the direction bit set is accepted.
-- 3. No clock stretching: Table 6 lists it as not possible, and 3.2.5 states a
-- slave "is not allowed to hold the clock LOW". There is no input by which
-- it could, so the clock never pauses. The absence of that input is the point.
-- 4. No arbitration and no synchronization: only one master may be on the bus.
-- 5. Because nothing is ever read back, delivery is UNVERIFIABLE. The block
-- reports bytes_sent and deliberately reports no "delivered" count -- the
-- spec itself concedes it is "impossible to determine that each slave is
-- responsive".
-- 6. Recovery therefore cannot be triggered by the bus. UM10204 3.2.13 makes
-- detection external and prescribes a ladder: software reset, then hardware
-- reset, then a power cycle to invoke the mandatory POR.
--
-- Push-pull matters for the outputs: both lines are actively driven to both
-- rails, so an idle line is HIGH because a transistor holds it there, not
-- because a resistor does.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_ufm_transmitter is
generic (
CNT_W : integer := 12
);
port (
clk : in std_logic;
rst_n : in std_logic;
-- command interface
go : in std_logic;
slave_addr : in std_logic_vector(6 downto 0);
rw_bit : in std_logic; -- must be 0; a 1 is refused
wdata : in std_logic_vector(7 downto 0);
wdata_valid : in std_logic;
stop_req : in std_logic;
-- recovery, from OUTSIDE the bus (UM10204 3.2.13)
slave_unresponsive : in std_logic; -- external evidence, never bus evidence
supports_swrst : in std_logic;
supports_hwrst : in std_logic;
-- push-pull line outputs
usda_out : out std_logic; -- driven to both rails: push-pull
usda_oe : out std_logic; -- always 1 outside reset: never released
uscl_out : out std_logic;
uscl_oe : out std_logic;
-- status
busy : out std_logic;
read_refused : out std_logic;
unverifiable : out std_logic;
bytes_sent : out unsigned(CNT_W-1 downto 0);
ninth_bits_driven_high : out unsigned(CNT_W-1 downto 0);
recovery : out unsigned(2 downto 0);
state : out unsigned(3 downto 0)
);
end entity i2c_ufm_transmitter;
architecture rtl of i2c_ufm_transmitter is
constant ST_IDLE : integer := 0;
constant ST_START : integer := 1;
constant ST_ADDR : integer := 2;
constant ST_NINTH : integer := 3; -- the acknowledge slot, driven HIGH by us
constant ST_DATA : integer := 4;
constant ST_STOP : integer := 5;
constant ST_RECOV : integer := 6;
constant RECOV_NONE : integer := 0;
constant RECOV_SOFT : integer := 1; -- General Call + 0000 0110
constant RECOV_HARD : integer := 2; -- hardware reset pin
constant RECOV_POWER : integer := 3; -- cycle power, invoke the mandatory POR
signal st : integer := ST_IDLE;
signal shreg : std_logic_vector(7 downto 0) := (others => '0');
signal bit_idx : integer := 7;
signal scl_ph : std_logic := '0'; -- 0 = USCL LOW half, 1 = USCL HIGH half
signal n_bytes : integer := 0;
signal n_ninth : integer := 0;
begin
state <= to_unsigned(st, 4);
process (clk, rst_n)
begin
if rst_n = '0' then
st <= ST_IDLE;
usda_out <= '1';
usda_oe <= '0';
uscl_out <= '1';
uscl_oe <= '0';
busy <= '0';
read_refused <= '0';
unverifiable <= '0';
bytes_sent <= (others => '0');
ninth_bits_driven_high <= (others => '0');
recovery <= to_unsigned(RECOV_NONE, 3);
shreg <= (others => '0');
bit_idx <= 7;
scl_ph <= '0';
n_bytes <= 0;
n_ninth <= 0;
elsif rising_edge(clk) then
-- Push-pull: both lines are always actively driven once out of reset.
-- There is no high-impedance state and no pull-up resistor to wait for.
usda_oe <= '1';
uscl_oe <= '1';
case st is
when ST_IDLE =>
busy <= '0';
usda_out <= '1'; -- idle HIGH, held by the upper transistor
uscl_out <= '1';
scl_ph <= '0';
if slave_unresponsive = '1' then
-- Obligation 6. Detection came from outside the bus; pick the
-- highest rung of the ladder the device actually supports.
if supports_swrst = '1' then
recovery <= to_unsigned(RECOV_SOFT, 3);
elsif supports_hwrst = '1' then
recovery <= to_unsigned(RECOV_HARD, 3);
else
recovery <= to_unsigned(RECOV_POWER, 3);
end if;
st <= ST_RECOV;
elsif go = '1' then
if rw_bit = '1' and slave_addr /= "0000000" then
-- Obligation 2. A read cannot be performed on a
-- unidirectional bus. Refuse it; do not emit a START. The
-- sole exception, the START byte 0000 0001, falls through.
read_refused <= '1';
st <= ST_IDLE;
else
read_refused <= '0';
recovery <= to_unsigned(RECOV_NONE, 3);
shreg <= slave_addr & rw_bit;
bit_idx <= 7;
busy <= '1';
st <= ST_START;
end if;
end if;
-- START: USDA HIGH -> LOW while USCL is HIGH.
when ST_START =>
uscl_out <= '1';
usda_out <= '0';
scl_ph <= '0';
st <= ST_ADDR;
-- Address byte and data bytes share one shifter. Data changes while
-- USCL is LOW and is stable while it is HIGH (3.2.3, unchanged from
-- standard I2C).
when ST_ADDR | ST_DATA =>
if scl_ph = '0' then
uscl_out <= '0';
usda_out <= shreg(bit_idx);
scl_ph <= '1';
else
uscl_out <= '1'; -- the bit is sampled in this half
scl_ph <= '0';
if bit_idx = 0 then
n_bytes <= n_bytes + 1;
bytes_sent <= to_unsigned(n_bytes + 1, CNT_W);
unverifiable <= '1'; -- obligation 5: sent, never confirmed
st <= ST_NINTH;
else
bit_idx <= bit_idx - 1;
end if;
end if;
-- The ninth clock. Obligation 1: the master generates it and drives
-- the data line HIGH. No slave contributes anything here, so this is
-- a NACK in shape and a formality in substance.
when ST_NINTH =>
if scl_ph = '0' then
uscl_out <= '0';
usda_out <= '1'; -- always HIGH: Table 6, Acknowledge n/p
scl_ph <= '1';
else
uscl_out <= '1';
scl_ph <= '0';
n_ninth <= n_ninth + 1;
ninth_bits_driven_high <= to_unsigned(n_ninth + 1, CNT_W);
if stop_req = '1' or wdata_valid = '0' then
st <= ST_STOP;
else
shreg <= wdata;
bit_idx <= 7;
st <= ST_DATA;
end if;
end if;
-- STOP: USDA LOW -> HIGH while USCL is HIGH.
when ST_STOP =>
uscl_out <= '1';
usda_out <= '1';
busy <= '0';
st <= ST_IDLE;
-- Recovery. Nothing here is driven by the bus, because nothing on the
-- bus could have told us anything.
when ST_RECOV =>
busy <= '0';
usda_out <= '1';
uscl_out <= '1';
if slave_unresponsive = '0' then
st <= ST_IDLE;
end if;
when others =>
st <= ST_IDLE;
end case;
end if;
end process;
end architecture rtl; -- ---------------------------------------------------------------------------
-- i2c_ufm_transmitter_tb.vhd
-- Independent oracle for i2c_ufm_transmitter. Behavioural twin of the
-- SystemVerilog and Verilog benches.
--
-- The bench reconstructs every byte by sampling USDA on the rising edge of
-- USCL -- the way a real UFm slave would -- and compares it against what it
-- asked to be sent. It also asserts the properties UFm gives UP, which is the
-- harder half: that the ninth bit is always HIGH, that both lines are always
-- actively driven, and that a read is refused.
--
-- Note what the bench CANNOT do: it cannot check that a byte arrived. Nothing
-- in UFm makes that observable. The suite therefore checks that the design does
-- not pretend otherwise.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity i2c_ufm_transmitter_tb is
end entity i2c_ufm_transmitter_tb;
architecture sim of i2c_ufm_transmitter_tb is
constant CNT_W : integer := 12;
constant TCLK : time := 20 ns;
constant ST_IDLE : integer := 0;
constant ST_ADDR : integer := 2;
constant ST_NINTH : integer := 3;
constant ST_DATA : integer := 4;
constant RECOV_NONE : integer := 0;
constant RECOV_SOFT : integer := 1;
constant RECOV_HARD : integer := 2;
constant RECOV_POWER : integer := 3;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal go : std_logic := '0';
signal slave_addr : std_logic_vector(6 downto 0) := "0101010";
signal rw_bit : std_logic := '0';
signal wdata : std_logic_vector(7 downto 0) := (others => '0');
signal wdata_valid : std_logic := '0';
signal stop_req : std_logic := '0';
signal slave_unresponsive : std_logic := '0';
signal supports_swrst : std_logic := '1';
signal supports_hwrst : std_logic := '1';
signal usda_out, usda_oe, uscl_out, uscl_oe : std_logic;
signal busy, read_refused, unverifiable : std_logic;
signal bytes_sent, ninth_bits_driven_high : unsigned(CNT_W-1 downto 0);
signal recovery : unsigned(2 downto 0);
signal st_o : unsigned(3 downto 0);
-- The slave's view of the bus. An observer only: in UFm it physically could
-- not drive anything.
type frame_arr is array (0 to 15) of std_logic_vector(8 downto 0);
signal rx_frame : frame_arr := (others => (others => '0'));
signal rx_count : integer := 0;
signal oe_violation : std_logic := '0';
signal halt : boolean := false;
begin
dut : entity work.i2c_ufm_transmitter
generic map (CNT_W => CNT_W)
port map (
clk => clk, rst_n => rst_n,
go => go, slave_addr => slave_addr, rw_bit => rw_bit,
wdata => wdata, wdata_valid => wdata_valid, stop_req => stop_req,
slave_unresponsive => slave_unresponsive,
supports_swrst => supports_swrst, supports_hwrst => supports_hwrst,
usda_out => usda_out, usda_oe => usda_oe,
uscl_out => uscl_out, uscl_oe => uscl_oe,
busy => busy, read_refused => read_refused, unverifiable => unverifiable,
bytes_sent => bytes_sent,
ninth_bits_driven_high => ninth_bits_driven_high,
recovery => recovery, state => st_o);
clkgen : process
begin
while not halt loop
clk <= '0'; wait for TCLK/2;
clk <= '1'; wait for TCLK/2;
end loop;
wait;
end process;
-- Sample USDA on every USCL rising edge and assemble 9-bit frames.
observer : process (clk, rst_n)
variable uscl_q : std_logic := '1';
variable sh : std_logic_vector(8 downto 0) := (others => '0');
variable nbits : integer := 0;
variable settled : integer := 0;
begin
if rst_n = '0' then
uscl_q := '1';
nbits := 0;
settled := 0;
-- Cleared here, not in the stimulus process: a signal of an unresolved
-- type may have exactly one driver in VHDL.
rx_count <= 0;
oe_violation <= '0';
elsif rising_edge(clk) then
-- Both lines must be actively driven at all times: push-pull, no
-- high-impedance state, ever. In reset the outputs are released, which
-- is correct -- a device held in reset must not drive the bus -- so the
-- check starts one cycle after reset is lifted.
settled := settled + 1;
if settled >= 2 and (usda_oe /= '1' or uscl_oe /= '1') then
oe_violation <= '1';
end if;
if uscl_out = '1' and uscl_q = '0' then -- USCL rising edge
sh := sh(7 downto 0) & usda_out;
nbits := nbits + 1;
if nbits = 9 then
if rx_count < 16 then
rx_frame(rx_count) <= sh;
end if;
rx_count <= rx_count + 1;
nbits := 0;
end if;
end if;
uscl_q := uscl_out;
end if;
end process;
stim : process
variable err : integer := 0;
procedure ck_int (what : string; got : integer; exp : integer) is
begin
if got /= exp then
report " FAIL " & what & ": got " & integer'image(got)
& " expected " & integer'image(exp) severity note;
err := err + 1;
end if;
end procedure;
procedure ck_bit (what : string; got : std_logic; exp : std_logic) is
begin
if got /= exp then
report " FAIL " & what & ": got " & std_logic'image(got)
& " expected " & std_logic'image(exp) severity note;
err := err + 1;
end if;
end procedure;
procedure step is
begin
wait until rising_edge(clk);
wait until falling_edge(clk);
end procedure;
procedure do_reset is
begin
wait until falling_edge(clk);
rst_n <= '0'; go <= '0'; wdata_valid <= '0'; stop_req <= '0';
slave_unresponsive <= '0'; rw_bit <= '0';
for k in 0 to 2 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk);
rst_n <= '1';
wait until rising_edge(clk);
wait until falling_edge(clk);
end procedure;
-- Run until the DUT is idle again, or a generous bound elapses.
procedure run_until_idle (bound : integer) is
variable n : integer := 0;
begin
while to_integer(st_o) /= ST_IDLE and n < bound loop
step;
n := n + 1;
end loop;
if n >= bound then
report " FAIL run_until_idle: still in state "
& integer'image(to_integer(st_o)) severity note;
err := err + 1;
end if;
end procedure;
variable n : integer;
-- VHDL has no inline declarative block inside a process body, so the
-- data-validity checker's variables live here with the rest.
variable usda_prev, uscl_prev : std_logic;
variable state_prev : integer;
variable viol : integer;
begin
report "=== i2c_ufm_transmitter: Ultra Fast-mode write path ===" severity note;
-- T1. A one-byte write, reconstructed from the wire, including the ninth
-- bit of each frame.
do_reset;
slave_addr <= "0101010"; rw_bit <= '0'; wdata <= x"C3"; wdata_valid <= '1';
wait until falling_edge(clk); go <= '1';
step;
wait until falling_edge(clk); go <= '0';
n := 0;
while to_integer(bytes_sent) < 2 and n < 200 loop step; n := n + 1; end loop;
wait until falling_edge(clk);
wdata_valid <= '0'; -- stop after the data byte, not the address
run_until_idle(400);
report "T1 single-byte write reconstructed from the wire" severity note;
ck_int("T1 two frames seen (address + data)", rx_count, 2);
-- Frame 0: address byte 0x2A with W=0, then the ninth bit HIGH.
ck_int("T1 address byte on the wire",
to_integer(unsigned(rx_frame(0)(8 downto 1))), 16#54#);
ck_bit("T1 address frame ninth bit HIGH", rx_frame(0)(0), '1');
ck_int("T1 data byte on the wire",
to_integer(unsigned(rx_frame(1)(8 downto 1))), 16#C3#);
ck_bit("T1 data frame ninth bit HIGH", rx_frame(1)(0), '1');
ck_int("T1 bytes_sent", to_integer(bytes_sent), 2);
ck_int("T1 ninth bits driven HIGH", to_integer(ninth_bits_driven_high), 2);
ck_bit("T1 delivery is unverifiable", unverifiable, '1');
ck_bit("T1 no output ever released", oe_violation, '0');
ck_bit("T1 not busy at the end", busy, '0');
-- T2. THE DEFINING PROPERTY. Four data bytes, and every single ninth bit
-- is HIGH. In any bidirectional mode at least one would be a
-- slave-driven LOW; here a LOW is impossible.
do_reset;
slave_addr <= "1010101"; rw_bit <= '0'; wdata <= x"01"; wdata_valid <= '1';
wait until falling_edge(clk); go <= '1';
step;
wait until falling_edge(clk); go <= '0';
for k in 0 to 3 loop
while to_integer(st_o) /= ST_NINTH loop step; end loop;
wait until falling_edge(clk);
wdata <= std_logic_vector(to_unsigned(16#10# + k, 8));
while to_integer(st_o) = ST_NINTH loop step; end loop;
end loop;
wait until falling_edge(clk); wdata_valid <= '0';
run_until_idle(600);
report "T2 every ninth bit is master-driven HIGH" severity note;
ck_int("T2 five frames (address + four data)", rx_count, 5);
for k in 0 to 4 loop
if rx_frame(k)(0) /= '1' then
report " FAIL T2 frame " & integer'image(k)
& " ninth bit is not HIGH" severity note;
err := err + 1;
end if;
end loop;
ck_int("T2 ninth bits == frames", to_integer(ninth_bits_driven_high), rx_count);
ck_int("T2 bytes_sent == frames", to_integer(bytes_sent), rx_count);
-- T3. A READ IS REFUSED. rw_bit = 1 on a real address cannot work on a
-- unidirectional bus. No START must be emitted and no byte sent.
do_reset;
slave_addr <= "0101010"; rw_bit <= '1'; wdata_valid <= '1';
wait until falling_edge(clk); go <= '1';
step; step;
wait until falling_edge(clk); go <= '0';
step; step;
report "T3 a read request is refused, not attempted" severity note;
ck_bit("T3 read refused", read_refused, '1');
ck_int("T3 stayed idle", to_integer(st_o), ST_IDLE);
ck_int("T3 nothing sent", to_integer(bytes_sent), 0);
ck_int("T3 nothing on the wire", rx_count, 0);
ck_bit("T3 never became busy", busy, '0');
-- T4. THE ONE EXCEPTION. The START byte is 0000 0001: address 0000 000
-- with the direction bit set. UM10204 3.2.7 permits it.
do_reset;
slave_addr <= "0000000"; rw_bit <= '1'; wdata_valid <= '0';
wait until falling_edge(clk); go <= '1';
step;
wait until falling_edge(clk); go <= '0';
run_until_idle(400);
report "T4 the START byte 0000 0001 is permitted" severity note;
ck_bit("T4 not refused", read_refused, '0');
ck_int("T4 one frame sent", rx_count, 1);
ck_int("T4 START byte on the wire",
to_integer(unsigned(rx_frame(0)(8 downto 1))), 1);
ck_bit("T4 its ninth bit is HIGH too", rx_frame(0)(0), '1');
-- T5. DATA VALIDITY (3.2.3). USDA may change only while USCL is LOW.
do_reset;
slave_addr <= "0111100"; rw_bit <= '0'; wdata <= x"A5"; wdata_valid <= '1';
wait until falling_edge(clk); go <= '1';
step;
wait until falling_edge(clk); go <= '0';
-- UM10204 3.2.3 requires USDA to be stable for the WHOLE HIGH period, and
-- that includes the rising edge itself. Flagging only changes strictly
-- inside a HIGH plateau misses the worst case: a change landing exactly ON
-- the rising edge, where a slave sampling there catches a racing value.
-- START and STOP are excluded, since SDA moving while SCL is HIGH is the
-- defining event there rather than a fault.
viol := 0;
usda_prev := usda_out; uscl_prev := uscl_out;
state_prev := to_integer(st_o);
for k in 0 to 249 loop
step;
if usda_out /= usda_prev and uscl_out = '1'
and (state_prev = ST_ADDR or state_prev = ST_NINTH or state_prev = ST_DATA)
and (to_integer(st_o) = ST_ADDR or to_integer(st_o) = ST_NINTH
or to_integer(st_o) = ST_DATA) then
viol := viol + 1;
end if;
usda_prev := usda_out; uscl_prev := uscl_out;
state_prev := to_integer(st_o);
if k = 120 then
wait until falling_edge(clk);
wdata_valid <= '0';
end if;
end loop;
report "T5 USDA is stable for the whole USCL HIGH period, edge included"
severity note;
ck_int("T5 data-validity violations", viol, 0);
run_until_idle(400);
-- T6. RECOVERY LADDER (3.2.13). Detection is external. With software reset
-- available, that is the rung chosen.
do_reset;
supports_swrst <= '1'; supports_hwrst <= '1';
wait until falling_edge(clk); slave_unresponsive <= '1';
step; step;
report "T6 external evidence selects the software reset rung" severity note;
ck_int("T6 recovery = software reset", to_integer(recovery), RECOV_SOFT);
ck_int("T6 in recovery", to_integer(st_o), 6);
ck_bit("T6 not busy", busy, '0');
wait until falling_edge(clk); slave_unresponsive <= '0';
step; step;
ck_int("T6 returns to idle", to_integer(st_o), ST_IDLE);
-- T7. No software reset: fall to the hardware reset rung.
do_reset;
supports_swrst <= '0'; supports_hwrst <= '1';
wait until falling_edge(clk); slave_unresponsive <= '1';
step; step;
report "T7 without software reset, the hardware pin is chosen" severity note;
ck_int("T7 recovery = hardware reset", to_integer(recovery), RECOV_HARD);
-- T8. Neither supported: only a power cycle remains, invoking the
-- mandatory internal POR.
do_reset;
supports_swrst <= '0'; supports_hwrst <= '0';
wait until falling_edge(clk); slave_unresponsive <= '1';
step; step;
report "T8 with neither, a power cycle is the only rung left" severity note;
ck_int("T8 recovery = power cycle", to_integer(recovery), RECOV_POWER);
-- T9. The recovery path is never reachable FROM the bus.
do_reset;
supports_swrst <= '1'; supports_hwrst <= '1';
slave_addr <= "0010001"; rw_bit <= '0'; wdata <= x"FF"; wdata_valid <= '1';
wait until falling_edge(clk); go <= '1';
step;
wait until falling_edge(clk); go <= '0';
for k in 0 to 149 loop
step;
if to_integer(recovery) /= RECOV_NONE then
report " FAIL T9 recovery rose during a normal transfer" severity note;
err := err + 1;
exit;
end if;
end loop;
wait until falling_edge(clk); wdata_valid <= '0';
run_until_idle(400);
report "T9 no bus activity can trigger recovery" severity note;
ck_int("T9 recovery still NONE", to_integer(recovery), RECOV_NONE);
-- T10. An explicit STOP request ends the transfer after the byte in flight,
-- not in the middle of it.
do_reset;
slave_addr <= "1111111"; rw_bit <= '0'; wdata <= x"5A"; wdata_valid <= '1';
wait until falling_edge(clk); go <= '1';
step;
wait until falling_edge(clk); go <= '0';
while to_integer(bytes_sent) < 2 loop step; end loop;
wait until falling_edge(clk); stop_req <= '1';
run_until_idle(400);
wait until falling_edge(clk); stop_req <= '0';
report "T10 STOP ends the transfer on a byte boundary" severity note;
ck_int("T10 whole frames only", rx_count, to_integer(bytes_sent));
ck_bit("T10 idle and not busy", busy, '0');
ck_bit("T10 USDA left HIGH", usda_out, '1');
ck_bit("T10 USCL left HIGH", uscl_out, '1');
ck_bit("T10 USDA still driven", usda_oe, '1');
ck_bit("T10 USCL still driven", uscl_oe, '1');
if err = 0 then
report "=== i2c_ufm_transmitter: ALL CHECKS PASSED ===" severity note;
else
report "=== i2c_ufm_transmitter: " & integer'image(err)
& " CHECK(S) FAILED ===" severity note;
end if;
halt <= true;
wait;
end process;
end architecture sim;9a. Six Decisions Worth Defending
There is no input from the bus, and that is the design. A reviewer's first instinct is to add usda_in for robustness. It would be unconnectable: §3.2.6 says slaves never drive USDA, so there is nothing to read. An input port that can only ever return the value this device is driving is worse than no port, because it invites logic that appears to check something.
usda_oe is asserted unconditionally after reset. Not "when transmitting" — always. A push-pull bus has no idle state in which the line floats, and the idle high is produced by this device's upper transistor. Mutation U4 releases one of the two outputs and the bench's push-pull assertion catches it within two cycles.
The ninth bit is driven high from a constant, not from a variable. It could have been computed, or defaulted, or left at whatever the shifter held. Writing usda_out <= 1'b1 with the Table 6 reference beside it makes the intent unmistakable, and mutation U1 — driving it low — fails nine checks because the bench reconstructs every frame from the wire and inspects bit nine of each.
A read is refused before a START is emitted, not abandoned partway. read_refused is raised and the state machine stays idle, so nothing appears on the bus at all. Refusing after the START would leave a slave addressed and a transaction dangling, which on a bus with no acknowledge is unrecoverable by any means short of §6's ladder.
unverifiable is an output. It goes high as soon as any byte has been sent, and it never goes low. It carries no information a caller could act on — which is exactly the point: it is the design refusing to let a caller believe a write was confirmed. The alternative, a delivered count, would be a fabrication, and §5 is why.
Recovery is driven by an input that is documented as external. slave_unresponsive is commented as evidence from outside the bus, and test 9 drives a complete clean transfer while asserting that the recovery state never rises. That test proves a negative about information flow, which is the only way to keep an honest boundary between what the bus can and cannot tell you.
9b. Verified Execution
$ iverilog -g2012 -o d i2c_ufm_transmitter.sv i2c_ufm_transmitter_tb.sv && ./d
=== i2c_ufm_transmitter: Ultra Fast-mode write path ===
T1 single-byte write reconstructed from the wire
T2 every ninth bit is master-driven HIGH
T3 a read request is refused, not attempted
T4 the START byte 0000 0001 is permitted
T5 USDA is stable for the whole USCL HIGH period, edge included
T6 external evidence selects the software reset rung
T7 without software reset, the hardware pin is chosen
T8 with neither, a power cycle is the only rung left
T9 no bus activity can trigger recovery
T10 STOP ends the transfer on a byte boundary
=== i2c_ufm_transmitter: ALL CHECKS PASSED ===
i2c_ufm_transmitter_tb.sv:339: $finish called at 13700000 (1ps)
$ iverilog -g2005 -o v i2c_ufm_transmitter.v i2c_ufm_transmitter_tb.v && ./v
=== i2c_ufm_transmitter: Ultra Fast-mode write path ===
T1 single-byte write reconstructed from the wire
T2 every ninth bit is master-driven HIGH
T3 a read request is refused, not attempted
T4 the START byte 0000 0001 is permitted
T5 USDA is stable for the whole USCL HIGH period, edge included
T6 external evidence selects the software reset rung
T7 without software reset, the hardware pin is chosen
T8 with neither, a power cycle is the only rung left
T9 no bus activity can trigger recovery
T10 STOP ends the transfer on a byte boundary
=== i2c_ufm_transmitter: ALL CHECKS PASSED ===
i2c_ufm_transmitter_tb.v:340: $finish called at 13700000 (1ps)
$ nvc --std=2008 -a i2c_ufm_transmitter.vhd i2c_ufm_transmitter_tb.vhd
$ nvc --std=2008 -e i2c_ufm_transmitter_tb && nvc --std=2008 -r i2c_ufm_transmitter_tb --stop-time=500us
** Note: 0ms+0: === i2c_ufm_transmitter: Ultra Fast-mode write path ===
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 900ns+1: T1 single-byte write reconstructed from the wire
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 2880ns+1: T2 every ninth bit is master-driven HIGH
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 3100ns+1: T3 a read request is refused, not attempted
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 3640ns+1: T4 the START byte 0000 0001 is permitted
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 8820ns+1: T5 USDA is stable for the whole USCL HIGH period, edge included
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 8980ns+1: T6 external evidence selects the software reset rung
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 9200ns+1: T7 without software reset, the hardware pin is chosen
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 9360ns+1: T8 with neither, a power cycle is the only rung left
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 12780ns+1: T9 no bus activity can trigger recovery
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132
** Note: 13700ns+1: T10 STOP ends the transfer on a byte boundary
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:377
** Note: 13700ns+1: === i2c_ufm_transmitter: ALL CHECKS PASSED ===
Process :i2c_ufm_transmitter_tb:stim at i2c_ufm_transmitter_tb.vhd:132All three at 13700 ns. The VHDL bench differs structurally from the two Verilog ones in a way worth noting: VHDL permits only one driver for a signal of an unresolved type, so the frame counter and the push-pull violation flag belong to the observer process alone and are cleared by reset rather than by the stimulus. In the Verilog benches they are plain variables that either process may touch. The constraint forced a cleaner separation of observer from stimulus, and it is the kind of thing that makes the VHDL port a review rather than a translation.
9c. What The Testbench Proves
| # | scenario | what it establishes |
|---|---|---|
| 1 | a one-byte write | both frames reconstructed from the wire; both ninth bits high |
| 2 | four data bytes | every ninth bit high; the count equals the frame count |
| 3 | a read requested on a real address | refused; no START, no byte, never busy |
| 4 | the START byte 0000 0001 | permitted, and on the wire verbatim |
| 5 | a whole multi-byte transfer | USDA stable for every USCL high period, edge included |
| 6 | external evidence, software reset available | the software reset rung |
| 7 | no software reset | the hardware reset rung |
| 8 | neither available | the power cycle rung |
| 9 | a complete clean transfer | recovery never rises from bus activity |
| 10 | a STOP mid-stream | ends on a byte boundary; lines left high and driven |
Test 2 is the chapter's central claim and it is checked per frame, not in aggregate. Five frames, five explicit assertions that bit nine is a one. A count-based check would pass a design that drove one frame's ninth bit low and another's high twice.
Test 3 asserts four separate absences. Not refused-and-stopped, but: the flag raised, the state still idle, bytes_sent still zero, and nothing on the wire at all. The last of those is the one that matters, and it needs the bench's own frame observer to establish.
Test 5 is the check that a first version got wrong, and the fix is instructive. The obvious formulation — flag USDA changing while USCL was high both before and after — misses a change that lands exactly on the rising edge, which is the worst case rather than a benign one. Mutation U5 does precisely that and survived the first suite. The corrected check asks whether USDA changed into a cycle whose USCL is high, and it has to exclude the START and STOP states, where SDA moving while SCL is high is the defining event rather than a fault.
Test 9 proves a negative, which is why it runs for 150 cycles. A complete legal transfer, with the recovery state checked on every cycle. If any bus condition could raise it, the design would be pretending to have information it cannot have.
Test 10's last two checks are about the idle state. Both lines high and both outputs still driven. A design that released the lines at the end of a transfer would leave them floating, and on a push-pull bus with no pull-up resistor there is nothing to define the level.
10. Mutation Testing
Ten defects injected into the SystemVerilog transmitter.
| # | injected defect | outcome |
|---|---|---|
| U1 | the ninth bit driven LOW | killed — 8 checks |
| U2 | a read attempted instead of refused | killed — test 3 |
| U3 | the START byte refused too | killed — test 4 |
| U4 | one push-pull output released | killed — test 1 |
| U5 | USDA changed while USCL is HIGH | killed — test 5 |
| U6 | the hardware rung preferred over software reset | killed — test 6 |
| U7 | always falls to the power-cycle rung | killed — test 6 |
| U8 | unverifiable never asserted | killed — test 1 |
| U9 | shifted LSB first instead of MSB first | killed — test 1 |
| U10 | the ninth bits not counted | killed — test 2 |
Ten of ten, but U5 is the one worth dwelling on because it survived the first run of this suite.
The original data-validity check flagged USDA changing while USCL was high on both sides of the change. U5 moves the data assignment into the same half-cycle as the clock rising, so USDA changes simultaneously with USCL going high — and the check, which needed the clock high before the change too, never fired. The mutation was a genuine violation of §3.2.3 and the bench could not see it.
A change landing exactly on the sampling edge is the worst case, not a boundary case. A check that only looks inside the plateau has excluded the situation it exists to catch.
The fix also had to carve out START and STOP, because those conditions are defined as SDA moving while SCL is high. So the corrected check needed the state as well as the levels — which is the general shape of the lesson: a timing property on a protocol bus usually cannot be checked from the waveform alone.
U6 and U7 both kill with one check, and both are about the ladder's order. §3.2.13 presents software reset and hardware reset as alternatives and the power cycle as the fallback, so preferring the hardware pin when software reset is available is a defect — it is a more disruptive remedy than necessary. A single check catches it because the ladder is a pure function of the two capability inputs, and one directed case per rung is sufficient coverage of a pure function.
U9 kills on test 1 alone. Shifting LSB first produces a byte the bench reconstructs as 0xC3 reversed, which fails the address and data comparisons. It is included because bit order is the kind of thing that is obviously right until it is obviously wrong, and a suite that only checked counts of bits would miss it entirely.
11. Verification Connection — Verifying A Bus With No Readback
UFm inverts the usual verification problem. There is no response to check, so a scoreboard has nothing to compare against — and the temptation is to build a passive monitor and call the job done.
The productive framing is that the bench must supply the observability the bus lacks, and must be explicit that it is doing so.
// On a bidirectional bus the scoreboard compares the master's expectation with
// the ACKNOWLEDGE the slave returned. In UFm there is no acknowledge, so there
// is nothing on the bus to compare with.
//
// The only correct structure is therefore: the bench contains a SLAVE MODEL that
// reconstructs each frame from USDA and USCL, and the scoreboard compares the
// master's intent with what the MODEL received. That comparison is valid inside
// the testbench and IS NOT AVAILABLE ON SILICON -- which must be stated, because
// a reviewer reading a green scoreboard will otherwise assume the real system has
// the same check.
class i2c_ufm_scoreboard extends uvm_scoreboard;
`uvm_component_utils(i2c_ufm_scoreboard)
uvm_analysis_imp_intent #(i2c_ufm_item, i2c_ufm_scoreboard) intent_ap;
uvm_analysis_imp_wire #(i2c_ufm_item, i2c_ufm_scoreboard) wire_ap;
i2c_ufm_item expected[$];
int unsigned frames_compared;
function new(string name, uvm_component parent);
super.new(name, parent);
intent_ap = new("intent_ap", this);
wire_ap = new("wire_ap", this);
endfunction
// What the master says it is sending.
function void write_intent(i2c_ufm_item t);
expected.push_back(t);
endfunction
// What the bench's slave model actually saw on the wire.
function void write_wire(i2c_ufm_item t);
i2c_ufm_item e;
if (expected.size() == 0) begin
`uvm_error("UFM_SB", $sformatf("frame 0x%02h on the wire with no intent queued", t.data))
return;
end
e = expected.pop_front();
frames_compared++;
if (t.data !== e.data)
`uvm_error("UFM_SB", $sformatf("frame %0d: wire 0x%02h, intent 0x%02h",
frames_compared, t.data, e.data))
// THE UFm-SPECIFIC CHECK. Table 6 lists Acknowledge as not possible, so the
// ninth bit must be a one on EVERY frame. A slave-driven zero here would mean
// the DUT slave model is driving USDA, which is forbidden outright.
if (t.ninth_bit !== 1'b1)
`uvm_error("UFM_SB", $sformatf("frame %0d: ninth bit is %0b -- only the master may drive it, and only HIGH",
frames_compared, t.ninth_bit))
// And the direction bit, on address frames only.
if (t.is_address && t.rw_bit !== 1'b0)
`uvm_error("UFM_SB", "address frame with the direction bit set: UFm is write-only")
endfunction
function void report_phase(uvm_phase phase);
// Stated, every run, deliberately. A green scoreboard on a UFm bus is a
// statement about the BENCH's observability, not about the system's.
`uvm_info("UFM_SB", $sformatf(
"%0d frames compared against a bench-side slave model. NOTE: this comparison "
"has no silicon equivalent -- UFm provides no path by which a master can learn "
"that a byte arrived (UM10204 3.2.7 note 6).", frames_compared), UVM_LOW)
if (expected.size() != 0)
`uvm_error("UFM_SB", $sformatf("%0d intended frames never reached the wire", expected.size()))
endfunction
endclassWhat to randomise. Byte counts, byte values, addresses, and the inter-transfer gap against tBUF. What not to randomise: the direction bit. A set direction bit on a real address is a DUT bug, and the expected behaviour is §9's read_refused — that belongs in a directed test with a stated expectation, not in the random space where it would look like a constraint failure.
12. FPGA and ASIC Implications
The biggest change is that this is a transmission line. §3.2.1 names reflections, connectors and stubs, and Table 13's 25 ns typical edge into any appreciable length of trace means the interconnect has to be treated as a distributed system: controlled impedance, series termination at the driver, short stubs, and no free branching. An open-drain I²C bus tolerates a star topology of arbitrary awfulness because the RC time constant swamps everything. A UFm bus does not.
Push-pull means contention is destructive rather than benign. On an open-drain bus two devices driving simultaneously is the normal case and the wired-AND resolves it (Chapter 2.2). On UFm two push-pull drivers in opposition are a short from VDD to ground through two transistors. This is why the mode permits only one master and forbids slaves from driving at all — and why a UFm bus must be designed so that it cannot happen, since nothing detects it.
There is no pull-up resistor, and therefore no resistor to get wrong. The entire Chapter 14.4 apparatus — the floor, the two ceilings, the empty windows — simply does not apply. That is a genuine simplification and one of the mode's real attractions: the bus works or it does not, and it does not depend on a board-level component value.
But there is no fail-safe either. An open-drain bus with an unpowered device on it still works, because the device's pins float and the resistor still defines the level. §5.1 makes that explicit for Fast-mode: the I/O pins "must be floating so that they do not obstruct the bus lines." A UFm device whose supply is off has its output transistors in an undefined state on a line with nothing else defining the level.
The input filter is as tight as Hs-mode's. tSP max 10 ns in Table 13, the same as Table 11's Hs-mode figure and five times tighter than Fast-mode's 50 ns. With Vhys at half the usual value on top of that, the input is less tolerant on both axes than any bidirectional mode's.
Ask whether the application is actually a bus. UFm has one master, no readback, and no device discovery. That is a broadcast fan-out, not a bus, and it is the right shape for exactly the applications it was designed for — strings of LED drivers where the data rate matters, the topology is fixed at design time, and the feedback path is that a human can see the output. If an application needs to know whether a device responded, UFm is the wrong mode and no amount of care will fix that.
13. Debugging — The Slave That Was Never There
A UFm LED driver string works on the bench. On the production unit, one driver in eight shows no output. The master reports every transfer as successful, no errors are logged, and a logic analyser capture of USDA and USCL shows clean, correctly framed 5 Mbit/s traffic addressed to all eight devices.
A reflection, not a protocol fault. A 22 ns edge into an unterminated 6 cm stub rings for tens of nanoseconds, and at a 200 ns bit period that ringing overlaps the sampling point. With only 0.05 VDD of input hysteresis the eighth driver's Schmitt trigger re-triggers on the ringing and latches wrong bits. The master cannot possibly detect this: UFm gives it no readback, no acknowledge and no way to ask, so from its point of view eight transfers succeeded. §3.2.1 names exactly this cause — 'reflections from cable ends, connectors, and stubs'.
Treat the interconnect as a transmission line: series-terminate USDA and USCL at the master with resistors matched to the trace impedance, eliminate the stub by daisy-chaining the connector rather than branching to it, and re-measure at the far end. If the topology cannot be fixed, drop to Fm+ over open-drain, where a 120 ns edge and 0.1 VDD of hysteresis tolerate the wiring the product actually has.The general lesson is about where the diagnostic information lives. On a bidirectional bus this fault announces itself: the eighth device NACKs, or returns wrong data on a read-back, and the master logs an error. On UFm the master is structurally incapable of noticing, so the only evidence is at the far end of the wire, with a probe.
That is the practical meaning of §3.2.7's note 6. "Impossible to determine that each slave is responsive" is not an abstract limitation — it is the reason this bug reached production.
14. Common Misconceptions
"UFm is just I²C at 5 Mbit/s." It is unidirectional and push-pull, and it is not compatible with bidirectional I²C devices. §5.4 says so directly.
"UFm slaves don't usually acknowledge." They cannot. Table 6 lists Acknowledge as n/p — not possible — and §3.2.6 states that slaves are not allowed to drive USDA at any time.
"The ninth clock is removed to gain throughput." It is preserved, and the master drives it high, so UFm spends 11 % of its bandwidth on a bit that carries nothing. The reason is frame compatibility with I²C tooling.
"A UFm master can read a status register if the slave supports it." There is no return path. A set direction bit is refused, with one exception — the START byte 0000 0001, which is a synchronisation aid and not a read.
"You can detect a missing UFm device by a timeout." There is nothing to time out on. No response is expected, so the absence of one carries no information. Detection must come from outside the bus, which §3.2.13 states explicitly.
"Clock stretching is discouraged in UFm." It is impossible. §3.2.5: a slave "is not allowed to hold the clock LOW", and it has no means to.
"Eliminating the pull-up resistor removes the electrical design problem." It removes the resistor problem and introduces a transmission-line problem, along with the loss of the fail-safe behaviour that an unpowered device on an open-drain bus has. §12.
"Multi-master UFm is possible if the masters coordinate out of band." Table 6 lists arbitration and synchronization as not possible, and two push-pull drivers in opposition are a short circuit rather than a contest. Coordination has to be absolute, which is why the specification simply says one master.
"UFm's symmetric clock is an arbitrary choice." It is a direct consequence of the low phase no longer carrying stretching or arbitration obligations. Every bidirectional mode requires a longer low phase; UFm is the only one that does not. §7a.
15. Reason It Through
A UFm master writes three bytes to address 0x20. The device at 0x20 was removed from the board. What does the master observe?
Exactly what it observes when the device is present: four frames clocked out, each ninth bit read back as the one it drove itself, no errors. Nothing distinguishes the two cases. This is §5's claim in its simplest form, and it is why the design in §9 exposes unverifiable rather than a success count.
How many bits per second of useful payload does a 5 Mbit/s UFm bus actually deliver for single-byte writes to a 7-bit address?
Each transfer is an address frame and a data frame: two frames of nine bits, so 18 bits, plus START and STOP overhead. At 200 ns per bit that is 3.6 µs for 8 bits of payload — about 2.2 Mbit/s of payload from a 5 Mbit/s bus. The ninth bits alone account for 2 of those 18 bits, or 11 %. Batching several data bytes behind one address amortises the address frame but never the ninth bits, which are one per byte forever.
Why does UFm specify VOH when no other mode does?
Because no other mode has a device that drives a high. In Sm, Fm, Fm+ and Hs-mode the high level is produced by a pull-up resistor, so its quality is a board property and the tables specify VOL only. A UFm output sources 4 mA to make the high itself, so the minimum high it produces is a silicon specification. A VOH row is the diagnostic signature of a push-pull bus.
A UFm slave's internal FIFO is full. What can it do?
Nothing, on the bus. It cannot stretch the clock, cannot NACK, and cannot signal back in any way. Its options are to drop the data or to have an out-of-band flow-control path, and if it is going to overflow in normal operation then the system has to be designed so that the master never sends faster than the slave consumes — an open-loop timing contract rather than a negotiated one.
Why is UFm's input hysteresis half that of every bidirectional mode, and when does that become a problem?
Because a push-pull driver produces fast, hard edges that do not dwell near the threshold, so there is less need for hysteresis to reject slow crossings. It becomes a problem when the interconnect rings, because ringing does produce repeated threshold crossings, and 0.05 VDD of hysteresis rejects less of it. §13 is that failure.
Both 0000 1XXX in Table 7 and the Hs-mode master code in Table 3 occupy the same eight addresses. Why the difference?
Because a UFm bus cannot enter Hs-mode — Hs-mode requires arbitration during the master code, and UFm has no arbitration and one master. The eight codes therefore have no function in UFm and Table 7 marks them reserved for future purposes. The address space is the same; the meaning is mode-dependent.
16. Understanding Check
17. Summary
UFm makes two changes and loses five features. Push-pull outputs and unidirectional lines buy 5 Mbit/s; Table 6 marks acknowledge, synchronization, arbitration, clock stretching and device ID as not possible — because each one needed a slave or a second master to pull a line low.
The nine-bit frame survives; the ninth bit does not mean anything. The master generates it and always drives it high, so every byte on a UFm bus is not-acknowledged on principle. It is kept for frame compatibility and costs 11 % of throughput.
The mode is write-only, with one exception. A set direction bit is refused except for the START byte 0000 0001, which is a synchronisation aid rather than a read.
Delivery is unverifiable, and the specification says so. §3.2.7 note 6: "impossible to determine that each slave is responsive." A master learns nothing about presence, arrival, readiness or content.
So recovery is external and blunt. §3.2.13 makes detection explicitly off-bus and prescribes software reset, then hardware reset, then a power cycle.
VOH exists only in UFm's table, because only UFm has a device that drives a high rather than releasing to a resistor. And Vhys is 0.05 VDD — half every other mode's — which is a reasonable trade on a short controlled link and a poor one on a cable.
UFm is the only mode with a symmetric clock. 50 ns low and 50 ns high, because the low phase no longer carries stretching or arbitration.
The budget identity holds exactly. 50 + 50 + 50 + 50 = 200 ns, which makes four of the five categories exact and confirms Hs-mode's 2.00 % as rounding.
Half of UFm's period is edges, against 13 % in Standard-mode — which is the same fact as §3.2.1's warning about reflections from cable ends, connectors and stubs.
It is a broadcast fan-out rather than a bus, and that is the right shape for the applications it was designed for. If a system needs to know whether a device responded, UFm cannot provide it.
18. What Comes Next
Four chapters have described what the modes are. Chapter 14.4 asks the question an engineer actually has to answer: given this board, which mode can it run?
The answer turns out to be a resistor, and the resistor has one floor and two independent ceilings — the sink current the output stage is rated for, the RC rise time the mode allows, and the leakage current the HIGH noise margin can absorb. All three have to be satisfied at once, and the interval between them is sometimes empty.
That is not a hypothetical. UM10204 §7.2.4 works one example through in prose and arrives at 1.7 kΩ and "about 200 pF", and the chapter reproduces both figures from the specification's own Equation 1 — then shows that a Fast-mode bus at its own maximum 400 pF has no legal pull-up resistor at all with a 3 mA output stage. Which is exactly why §5.1 stops permitting a plain resistor at 200 pF, and why Fast-mode Plus is defined by a 20 mA driver rather than by its timing.
Continue learning
Related tutorials
- Related topic
Why Push-Pull Fails on a Shared Bus
An ordinary digital output drives both levels actively, which assumes it owns the node. Attach two of them to one conductor, let them disagree, and the result is not a confused message but a low-impedance path from supply to ground through two output stages — an electrical fault no protocol discipline can prevent.
- Related topic
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.
- 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.
