Ultra Fast-Mode: The Write-Only Bus
Explain what UFm keeps from I2C and what it drops, run a 5 MHz write with its checkers, and say why any target that drives, or any second controller, breaks a push-pull bus.
- Lesson
- P8.12
- Time
- 35 min
- Runs on
- Your browser
- Check
- 4 questions
A display board used UFm for its LED drivers: 7421 ns per four-byte update, 5.1 times faster than Fm+. Then firmware wanted to read a driver's fault flag. UFm cannot read: its targets never drive a line, so the flag needed its own pin.
- One talker
The controller addresses a target, sends data and stops. Targets never answer.
- A second controller
Two push-pull outputs on one wire would fight, so a second controller must be fully cut off by a one-hot mux.
Read the diagram as text
- Controller ASIC: push-pull USCL, USDA
- One-hot mux: only if a second controller exists
- LED controller 1: receives only
- LCD driver: receives only
- GPO: receives only
- Controller ASIC → LED controller 1: USDA, USCL
- Controller ASIC → LCD driver
- Controller ASIC → GPO
- One-hot mux → Controller ASIC
After UM10204 Fig. 21 and §3.2. When the bus is free the controller's upper transistors hold both lines HIGH (§3.2.1); there are no pull-up resistors.
What UFm Keeps and Drops
UFm is a push-pull bus from DC to 5 MHz that carries data one way (§3.2). It keeps the frame: START, address, eight data bits, a ninth clock, STOP. It drops everything that needs a second driver on a wire (§3.2.1, §5.4).
| Feature | Sm, Fm, Fm+ (Table 3) | UFm (Table 7) |
|---|---|---|
| START, STOP, 7-bit address | mandatory | mandatory |
| Acknowledge | mandatory | not possible |
| Synchronisation, arbitration | mandatory with several controllers | not possible |
| Clock stretching | optional | not possible |
| 10-bit address, general call, software reset | optional | optional |
| START byte | optional with several controllers | optional |
| Device ID | optional in a target | not possible |
UM10204 Table 3 and Table 7. UFm has one configuration, a single controller; everything that needs a target or a second controller to drive a line is gone.
UFm devices are not compatible with the bidirectional buses (§5.4), so a UFm segment holds only UFm devices.
The Frame on the Wire
Data is stable while USCL is HIGH and changes while it is LOW (§3.2.3). START and STOP are the same edges as on any I2C bus (§3.2.4). The controller drives USDA HIGH on every ninth clock, so each byte ends in a NACK (§3.2.5, §3.2.6).
- Cycle 1
START: USDA falls under a high USCL, as on every I2C bus.
- Cycle 32
The address 0x50 and W go out MSB first, one bit per 200 ns clock.
- Cycle 72
The ninth clock: the controller itself drives USDA HIGH. The target never drives.
Measured. The decoder logs I2C S 50 W NA 10 NA 5a NA a5 NA P: every ninth bit reads as NA, because it is a HIGH the controller drives.
| Parameter | Table 15 | Bench (shortest measured) |
|---|---|---|
| fUSCL | 0 to 5000 kHz | period 200 ns (5000 kHz) |
| tHD;STA | 50 ns min | 60 ns |
| tLOW | 50 ns min | 100 ns |
| tHIGH | 50 ns min | 100 ns |
| tHD;DAT | 10 ns min | 20 ns |
| tSU;DAT | 30 ns min | 80 ns |
| tSU;STO | 50 ns min | 60 ns |
| tBUF, tSU;STA | 80 ns, 50 ns min | controller settings 100 ns and 60 ns |
| tr, tf | 50 ns max (typical 25 ns) | not measurable on digital pins |
UM10204 Table 15. Measured from the write above, whose checkers are clean. The write takes 7421 ns; the same bytes on the Fm+ bench take 37751 ns. tVD;DAT in Table 15 is a minimum of 10 ns, not a maximum.
Addresses and Special Bytes
The R/W bit must be W: a read is not supported, except as the low bit of the START byte (§3.2.7). 10-bit addresses work in the write format only (§3.2.8). General call, software reset and the START byte keep their F/S meaning; no device may acknowledge the START byte (§3.2.10 to §3.2.12).
| Address | R/W | Sm, Fm, Fm+ (Table 4) | UFm (Table 8) |
|---|---|---|---|
| 0000 000 | 0 | general call | general call |
| 0000 000 | 1 | START byte | START byte |
| 0000 001 | X | CBUS address | reserved for future |
| 0000 010 | X | different bus format | reserved for future |
| 0000 011 | X | reserved for future | reserved for future |
| 0000 1XX | X | Hs-mode controller code | reserved for future |
| 1111 1XX | 1 (Table 4), X (Table 8) | device ID | reserved for future |
| 1111 0XX | X | 10-bit addressing | 10-bit addressing (write only) |
UM10204 Table 4 and Table 8 (§3.1.12, §3.2.9). UFm has a different physical layer, so its address range differs: the CBUS, other-format, Hs-code and device-ID entries become reserved for future use. As on the other buses, a reserved address may serve as a target address if it will never be used for its purpose.
| Transfer | Decoder | Target |
|---|---|---|
| Address 0x50 with R, then a byte | I2C S 50 R NA 10 NA P | reads_refused 1, led1 ptr 00, mem[10] 10 |
| START byte, Sr, then a write | I2C S 00 R NA Sr 50 W NA 20 NA 77 NA P | led1 mem[20] 77 |
| Two targets in one transfer, joined by Sr | I2C S 50 W NA 00 NA 11 NA Sr 51 W NA 00 NA 22 NA P | led1 mem[0] 11, led2 mem[0] 22 |
| General call, then 06h | I2C S 00 W NA 06 NA P | 06h: resets 1 and 1, led1 mem[06] 06, ptr 00 |
| General call, then 04h, 00h and 07h | three transfers like the one above | 04h: 1, ignored: 2, resets 1 |
Measured on the bench targets at 0x50 and 0x51. The code 06h resets both and restores their registers (§3.2.11). The code 04h writes the programmable address part without a reset. The code 00h is not allowed and 07h has B = 1, so both are ignored (§3.2.10).
Who May Drive
Only the controller drives, and only one controller may be connected at a time (§3.2). On open drain, a target pulling low is an ACK or a stretch. On push-pull it fights the controller's HIGH, so the line goes to x.
- Cycle 15
USCL falls after the eighth bit and the target switches its driver on, as an open-drain target would for an ACK.
- Cycle 16
The controller drives HIGH for the ninth bit and the target drives LOW: the line resolves to x.
Measured. On a wired-AND bus this would be an ACK; on push-pull it is a short between two outputs. Checker lines: thd_dat ×5, target_drives_usda ×4, contention_usda ×4, ninth_bit ×4.
| Switch | Checker lines | Rule broken |
|---|---|---|
led1.drives_ack = 1 | thd_dat ×5, target_drives_usda ×4, contention_usda ×4, ninth_bit ×4 | targets never drive USDA (§3.2.6) |
led1.stretches = 1 | target_drives_uscl ×4, contention_uscl ×4 | a target may not hold USCL LOW (§3.2, §3.2.5) |
other.enabled = 1 | contention_usda ×13, contention_uscl ×37 | one controller at a time, behind a one-hot mux (§3.2) |
ctrl.ninth_low = 1 | ninth_bit ×4 | the ninth bit is always HIGH (§3.2.6) |
ctrl.fast_uscl = 1 (40 ns phases) | tlow ×36, tsu_dat ×23, thigh ×36, fuscl ×36 | fUSCL, tLOW, tHIGH, tSU;DAT (Table 15) |
ctrl.short_hold = 1 | thd_dat ×23 | tHD;DAT 10 ns min (Table 15) |
Measured over the same write. With the second controller enabled the target reads led1 mem[10] 10, mem[11] 11: its registers were never written. With the ninth bit low the decoder logs I2C S 50 W A 10 A 5a A a5 A P, an ACK nobody sent.
Resets, IDs and Electrics
§3.2 has no bus-clear procedure: a target cannot hold a line it never drives. If a target stops responding, found by some means other than the bus, reset it with the software reset or its reset pin, or cycle power for its power-on reset (§3.2.13). The Device ID needs a read, so UFm has none (§3.2.14).
| Symbol | Table 14 (UFm) | Compare Sm, Fm, Fm+ (Table 10) |
|---|---|---|
| VIL, VIH | 0.3 VDD, 0.7 VDD | the same |
| Vhys | 0.05 VDD min | the same for Fm and Fm+; no Sm requirement |
| VOL | 0.4 V at 4 mA sink | 0.4 V at 3 mA (20 mA for Fm+) |
| VOH | VDD − 0.4 V at 4 mA source | none: open drain cannot drive HIGH |
| Leakage | ±1 µA at 3.6 V, ±10 µA at 5.5 V | ±10 µA input current |
| Ci | 10 pF max | 10 pF max |
| tSP | 10 ns max | 50 ns for Fm and Fm+; no Sm requirement |
UM10204 Table 14 and Table 10, VDD > 2 V for VOL and VOH. The VOH row is the whole difference in one line: a UFm output drives both levels.
Try It
The controller writes register pointer 0x10 and two data bytes to LED controller 1. ck is the shared edge-time checker set to the Table 15 minima; uck watches for contention, target drivers, the ninth bit and fUSCL.
Set led1.drives_ack = 1; before the write and read the checker lines: the bus goes to x on every ninth clock.
I2C S 50 W NA 10 NA 5a NA a5 NA P 7421 ns led1 mem[10] 5a, mem[11] a5 | ck.broken 0, uck.broken 0Read included file: i2c_ufm_ctrl.sv
`timescale 1ns/1ns
// UFm controller (§3.2.1): push-pull on both lines, write only. While enabled it always drives USCL and
// USDA, high when the bus is free. enabled=0 models the isolated side of a one-hot mux (§3.2).
module i2c_ufm_ctrl(output wire uscl,usda,output wire drives);
parameter time TLOW=100,THIGH=100,HOLD=20,TBUF=100,THD_STA=60,TSU_STA=60,TSU_STO=60;
bit ninth_low=0,fast_uscl=0,short_hold=0; // fault switches
bit enabled=1,active=0;reg scl_v=1,sda_v=1;time stopped=0;int bytes=0;
assign drives=enabled;assign uscl=enabled?scl_v:1'bz;assign usda=enabled?sda_v:1'bz;
task automatic start();
if(!active)begin if($time-stopped<TBUF)#(TBUF-($time-stopped));end
else begin scl_v=0;#(HOLD);sda_v=1;#(TLOW-HOLD);scl_v=1;#(TSU_STA);end // Sr: USDA high, USCL high, setup
sda_v=0;#(THD_STA);scl_v=0;active=1;
endtask
task automatic bit_out(input bit v);time h,l,t;
h=short_hold?0:HOLD;l=fast_uscl?40:TLOW;t=fast_uscl?40:THIGH;
scl_v=0;#(h);sda_v=v;#(l-h);scl_v=1;#(t);scl_v=0;
endtask
// Eight data bits, then the ninth clock with USDA driven HIGH by the controller (§3.2.5, §3.2.6).
task automatic write_byte(input reg [7:0] value);
for(int b=7;b>=0;b--)bit_out(value[b]);bit_out(!ninth_low);bytes++;
endtask
task automatic address7(input reg [6:0] addr,input bit rd);write_byte({addr,rd});endtask
task automatic stop();
scl_v=0;#(HOLD);sda_v=0;#(TLOW-HOLD);scl_v=1;#(TSU_STO);sda_v=1;stopped=$time;active=0;#1;
endtask
endmodule
Read included file: i2c_ufm_target.sv
`timescale 1ns/1ns
// UFm target (§3.2): a receiver only, such as an LED driver. It never drives USDA or USCL; the fault
// switches make it try (an ACK, a stretch), which collides with the controller's push-pull outputs.
// Table 8: 0000 000+W is the general call (optional, §3.2.10), 0000 000+R the START byte (ignored), the
// rest of 0000 XXX and 1111 XXX are not this target's address. A READ header is not supported (§3.2.7).
module i2c_ufm_target #(parameter [6:0] ADDRESS=7'h50)(inout wire uscl,usda,output reg sda_en=0,scl_en=0);
bit drives_ack=0,stretches=0; // fault switches
bit general_call=1,software_reset=1; // optional features (Table 7)
reg [7:0] mem[0:255];reg [7:0] ptr=0,shift=0;int resets=0,prog_writes=0,ignored=0,reads_refused=0,count=0,bytes=0;
bit active=0,selected=0,need_ptr=1,gc=0,ninth_seen=0;
assign usda=sda_en?1'b0:1'bz;assign uscl=scl_en?1'b0:1'bz;
initial for(int i=0;i<256;i++)mem[i]=i[7:0];
always @(negedge usda)if(uscl===1)begin active=1;selected=0;gc=0;count=0;bytes=0;need_ptr=1;end // S or Sr
always @(posedge usda)if(uscl===1&&active)begin active=0;selected=0;gc=0;count=0;end // P
always @(posedge uscl)if(active)begin
if(count<8)begin shift={shift[6:0],usda};count++;
if(count==8)begin
if(bytes==0)begin
if(shift[7:1]==ADDRESS)begin selected=!shift[0];if(shift[0])reads_refused++;end
else gc=general_call&&shift==8'h00;
end else if(gc&&bytes==1)begin
if(shift==8'h06&&software_reset)begin for(int i=0;i<256;i++)mem[i]=i[7:0];ptr=0;resets++;end // §3.2.11
else if(shift==8'h04)prog_writes++; // §3.2.10
else ignored++; // 00h is not allowed; other codes and B = 1 are ignored
gc=0;
end else if(selected)begin if(need_ptr)begin ptr=shift;need_ptr=0;end else begin mem[ptr]=shift;ptr=ptr+1'b1;end end
bytes++;
end
end else begin count=0;ninth_seen=usda;end // the ninth clock: the controller drives USDA HIGH
end
always @(negedge uscl)if(active)begin
if(count==8&&drives_ack&&selected)sda_en=1; // fault: tries to ACK
else sda_en=0;
if(count==0&&bytes>0&&selected&&stretches&&!scl_en)begin scl_en=1;#300;scl_en=0;end // fault: tries to stretch
end
endmodule
Read included file: i2c_ufm_checker.sv
`timescale 1ns/1ns
// What a push-pull bus adds to the shared i2c_checker (which measures the Table 15 edge times):
// contention (a line at x: two drivers disagree), a target driver switched on, a ninth bit that is not
// HIGH (§3.2.6) and a USCL period under 200 ns (fUSCL 5000 kHz, Table 15).
module i2c_ufm_checker(input wire uscl,usda,input wire [1:0] target_drives);
int broken=0,pulses=0;bit active=0,x_sda=0,x_scl=0;time rise=0;
task automatic bad(input string rule);broken++;$display("UFM CHECK %s at %0t",rule,$time);endtask
always @(usda)begin if(usda===1'bx&&!x_sda)bad("contention_usda");x_sda=usda===1'bx;
if(uscl===1&&usda===0)begin active=1;pulses=0;end else if(uscl===1&&usda===1)active=0;end
always @(uscl)begin if(uscl===1'bx&&!x_scl)bad("contention_uscl");x_scl=uscl===1'bx;end
always @(posedge target_drives[0])bad("target_drives_usda");
always @(posedge target_drives[1])bad("target_drives_uscl");
always @(posedge uscl)begin
if(rise&&$time-rise<200&&active)bad("fuscl");rise=$time;
if(active)begin pulses++;if(pulses%9==0&&usda!==1'b1)bad("ninth_bit");end
end
endmodule
Read included file: i2c_checker.sv
`timescale 1ns/1ns
// Logical Fm timing only. Instantaneous digital pins cannot measure RC rise/fall or
// establish which high-SCL SDA edge was intended as data; driver intent is separate.
module i2c_checker #(parameter time TLOW=1300,THIGH=600,TBUF=1300,THD_STA=600,TSU_STA=600,TSU_DAT=100,THD_DAT=0,TSU_STO=600)
(input wire scl,sda,input wire [2:0] sda_lows,scl_lows,input wire high_drive);
int broken=0,starts=0,stops=0,pulses=0;bit active=0,seen_rise=0,seen_fall=0,start_pending=0;
time rise=0,fall=0,changed=0,stop_time=0,start_time=0;
task automatic bad(input string rule);broken++;$display("I2C CHECK %s at %0t",rule,$time);endtask
always @(posedge scl)begin
if(seen_fall&&$time-fall<TLOW)bad("tlow");
if(active&&$time-changed<TSU_DAT)bad("tsu_dat");rise=$time;seen_rise=1;if(active)pulses++;
end
always @(negedge scl)begin
if(active&&seen_rise&&$time-rise<THIGH)bad("thigh");
if(start_pending&&$time-start_time<THD_STA)bad("thd_sta");start_pending=0;fall=$time;seen_fall=1;
end
always @(sda)begin
if(scl===1)begin
if(sda===0)begin
if(!active&&stops>0&&$time-stop_time<TBUF)bad("tbuf");
if(active&&$time-rise<TSU_STA)bad("tsu_sta");starts++;active=1;start_pending=1;start_time=$time;
end else if(sda===1&&active)begin if($time-rise<TSU_STO)bad("tsu_sto");stops++;active=0;stop_time=$time;end
end else if(seen_fall&&$time-fall<THD_DAT)bad("thd_dat");
changed=$time;
end
always @(high_drive or sda_lows)begin #1;if(high_drive&&|sda_lows)bad("push_pull_contention");end
// A separate intent-aware bench checks ACK ownership/ninth slot, lost-controller
// withdrawal, timeouts and accidental START/STOP. Those are not inferable from SDA alone.
endmodule
Read included file: i2c_mon.sv
`timescale 1ns/1ns
module i2c_mon(input wire scl,sda);
bit active=0,header=1,trace=0;int count=0,frames=0,starts=0,stops=0;reg [7:0] value=0;
// Assemble the whole S ... Sr ... P transaction before one atomic report.
// Incomplete/stuck transactions produce no line; counters remain live.
string text="";
always @(negedge sda)if(scl===1)begin if(active)text={text," Sr"};else text="I2C S";active=1;header=1;count=0;starts++;end
always @(posedge sda)if(scl===1&&active)begin if(trace)$display("%s P",text);text="";active=0;count=0;frames++;stops++;end
always @(posedge scl)if(active)begin
if(count<8)begin value={value[6:0],sda};count++;end
else begin
if(header)text=$sformatf("%s %02h %s",text,value[7:1],value[0]?"R":"W");
else text=$sformatf("%s %02h",text,value);
text={text,sda?" NA":" A"};header=0;count=0;
end
end
endmodule
// Trusted recovery observer. Driver intent distinguishes the STOP setup edge
// from recovery pulses, including when a stuck target prevents an actual STOP.
module i2c_clear_mon(input wire scl,sda,ctrl_sda_low,enabled,done,timed_out);
int pulses=0;bit stopped=0;
always @(posedge enabled)begin pulses=0;stopped=0;end
always @(posedge scl)if(enabled&&!ctrl_sda_low)pulses++;
always @(posedge sda)if(enabled&&scl===1)stopped=1;
always @(posedge done)$display("I2C CLEAR PULSES %0d STOP %0d CLEARED %0d TIMEOUT %0d",pulses,stopped,(sda===1&&!timed_out),timed_out);
endmodule
Check Yourself
Remember
- UFm is push-pull and unidirectional, up to 5 MHz: one controller writes, and targets only receive.
- The frame keeps S, address, eight bits, the ninth clock and P; the controller drives the ninth bit HIGH.
- No ACK, stretching, synchronisation, arbitration, reads or Device ID: anything that needs a target to drive is gone.
- A target that drives, or a second connected controller, collides with push-pull outputs and the line goes to x.
- Table 8 frees the CBUS, other-format, Hs-code and device-ID addresses; a stuck target is reset by software, a reset pin or power.
Practice
- Send the UFm Ninth Bit High Fix · Medium