Skip to content
VLSI Mentor

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:

featurerequiresUFm
Acknowledgethe slave pulling SDA low in bit 9no slave may drive SDA
Clock stretchingthe slave holding SCL lowno slave may drive SCL
Arbitrationtwo masters driving and reading backone master, and no readback
Synchronizationtwo masters' clocks wired-AND togethersame
Device IDa read transactionreads 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:

questionSm / Fm / Fm+ / HsUFm
Is a device at this address?the acknowledge answers itunanswerable
Did the byte arrive?the acknowledge answers itunanswerable
Is the device ready for more?stretching or a NACK answers itunanswerable
What does the device contain?a read answers itunanswerable
Did the device reset?the device ID or a read answers itunanswerable

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_sent and deliberately has no bytes_delivered output, 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:

modetLOW(min)tHIGH(min)ratio
Standard4700 ns4000 ns1.18
Fast1300 ns600 ns2.17
Fast-mode Plus500 ns260 ns1.92
Hs-mode (100 pF)160 ns60 ns2.67
Ultra Fast-mode50 ns50 ns1.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:

tLOWtHIGHtrtfsum1/fUSCL(max)verdict
UFm50505050200 ns200 nsexact

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 %.

modesum of four1/fmaxverdict
Standard10000 ns10000 nsexact
Fast2500 ns2500 nsexact
Fast-mode Plus1000 ns1000 nsexact
Hs-mode300 / 600 ns294.12 / 588.24 nsover by 2.00 %
Ultra Fast-mode200 ns200 nsexact

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 cycles
Ten intervals showing one UFm data byte transmitted most significant bit first, followed by the ninth clock cycle and a STOP condition. The data row carries the byte value C3 in hexadecimal. Two rows beneath compare who drives the line: in Ultra Fast-mode the master drives every interval including the ninth, whereas on a bidirectional bus the slave would drive the ninth interval to acknowledge. The ninth interval is marked to show the master drives it high unconditionally.one byte, MSB first, 0xC3one byte, MSB first, 0xC3the vestigial ACK slotthevestigi…9th bit: master drives HIGH, always9th bit: master drivesHIGH, alwaysSTOP: USDA rises while USCL is HIGHSTOP: USDA rises while USCLis HIGHusdabitb7b6b5b4b3b2b1b09thPUFmMMMMMMMMMMbidirMMMMMMMMSMt0t1t2t3t4t5t6t7t8t9

The 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.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_transmitter.sv — a write-only master whose ninth bit is always its own
   // -----------------------------------------------------------------------------
   // 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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_transmitter_tb.sv — ten scenarios, including the properties UFm gives up
   `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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_transmitter.v — the same transmitter in Verilog-2001
   // -----------------------------------------------------------------------------
   // 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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_transmitter_tb.v — the same ten scenarios, independently
   `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
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_transmitter.vhd — the same transmitter in VHDL-2008
   -- ---------------------------------------------------------------------------
   -- 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;
Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_transmitter_tb.vhd — the same ten scenarios, with the observer owning the frame counters
   -- ---------------------------------------------------------------------------
   -- 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

Azvya Education Pvt. Ltd.VLSI Mentor
terminal — three simulators, ten scenarios, one finish time
   $ 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:132

All 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

#scenariowhat it establishes
1a one-byte writeboth frames reconstructed from the wire; both ninth bits high
2four data bytesevery ninth bit high; the count equals the frame count
3a read requested on a real addressrefused; no START, no byte, never busy
4the START byte 0000 0001permitted, and on the wire verbatim
5a whole multi-byte transferUSDA stable for every USCL high period, edge included
6external evidence, software reset availablethe software reset rung
7no software resetthe hardware reset rung
8neither availablethe power cycle rung
9a complete clean transferrecovery never rises from bus activity
10a STOP mid-streamends 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 defectoutcome
U1the ninth bit driven LOWkilled — 8 checks
U2a read attempted instead of refusedkilled — test 3
U3the START byte refused tookilled — test 4
U4one push-pull output releasedkilled — test 1
U5USDA changed while USCL is HIGHkilled — test 5
U6the hardware rung preferred over software resetkilled — test 6
U7always falls to the power-cycle rungkilled — test 6
U8unverifiable never assertedkilled — test 1
U9shifted LSB first instead of MSB firstkilled — test 1
U10the ninth bits not countedkilled — 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.

Azvya Education Pvt. Ltd.VLSI Mentor
i2c_ufm_scoreboard.sv — the slave model IS the observability, and it must say 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
   endclass

What 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

Symptom

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.

Root Cause

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'.

Fix
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