DEV Community

Hedy
Hedy

Posted on

How to drive I2C pins with FPGA?

Here’s a clean, FPGA-friendly way to drive I²C (SCL/SDA). The big ideas: I²C lines are open-drain (never drive “1”), both lines need pull-ups, and your FPGA must use tri-state (or vendor open-drain) outputs.

1) Hardware hookup (must-dos)

  • Pull-ups: Add RPU from SCL/SDA to VDD (e.g., 3.3 V). Typical 2.2–10 kΩ.
    • Quick sizing: 𝑅𝑃𝑈≈𝑡𝑟/0.8473𝐶𝑏𝑢𝑠 and also obey 𝑅𝑃𝑈≤(𝑉𝑃𝑈−𝑉𝑂𝐿(𝑚𝑎𝑥))/𝐼𝑂𝐿.
    • Example: Fast-mode (300 ns), 𝐶𝑏𝑢𝑠=100 pF → 𝑅𝑃𝑈≈3.5𝑘Ω → choose 3.3–4.7 kΩ.
  • Levels: Most FPGA I/Os aren’t 5 V-tolerant. For 5 V I²C, use a level shifter (e.g., PCA9306/BSS138).
  • Routing: Keep SCL/SDA short and together; avoid heavy RC loading. Series 22–47 Ω near the FPGA is OK if edges ring.

2) Configure FPGA pins as open-drain

You implement open-drain by driving 0 for ‘0’ and high-Z for ‘1’. Read back the line to support clock stretching & arbitration.

Generic Verilog (vendor-agnostic)

// Top-level ports must be inout
inout wire SDA, SCL;

wire sda_i, scl_i;        // sampled inputs
reg  sda_oe, scl_oe;      // 1 = release (Z), 0 = drive low

// Open-drain behavior
assign SDA = sda_oe ? 1'bz : 1'b0;
assign SCL = scl_oe ? 1'bz : 1'b0;

// Always read the lines (needed for stretch/arbitration)
assign sda_i = SDA;
assign scl_i = SCL;
Enter fullscreen mode Exit fullscreen mode

Xilinx (recommended primitive)

wire SDA_i;  reg SDA_oe;  // oe=1 => release
OBUFT sda_buf (.I(1'b0), .O(SDA), .T(SDA_oe));  // drives 0 or Z
IBUF  sda_in  (.I(SDA),  .O(SDA_i));

wire SCL_i;  reg SCL_oe;
OBUFT scl_buf (.I(1'b0), .O(SCL), .T(SCL_oe));
IBUF  scl_in  (.I(SCL),  .O(SCL_i));
Enter fullscreen mode Exit fullscreen mode

Intel/Altera & Lattice: use ALT_OUTBUF_TRI / SB_IO (same concept: drive 0 or Z and always sample input).

3) Master timing essentials (bit-bang or soft IP)

  • Start: While SCL is high, pull SDA low.
  • Bit transfer (MSB first):
    • For data ‘0’: set sda_oe=0 (drive 0); for ‘1’: sda_oe=1 (release).
    • Pull SCL low → prepare bit → release SCL high → wait until scl_i==1 (handles clock stretching by slaves) → sample sda_i if needed → pull SCL low.
  • ACK bit: Release SDA (oen=1) for the 9th clock, raise SCL, sample sda_i==0 for ACK.
  • Stop: With SCL high, release SDA from low→high.

Tiny FSM sketch (ticks at 2× or 4× SCL):

case(state)
  IDLE:   if(start_req) begin sda_oe<=0; state<=START1; end // SDA low while SCL high
  START1: if(scl_i)     begin scl_oe<=0; state<=BIT_SETUP; end
  BIT_SETUP: begin
     sda_oe <= (tx_bit ? 1'b1 : 1'b0);  scl_oe<=0; state<=BIT_HIGH;
  end
  BIT_HIGH: begin
     scl_oe<=1; if(scl_i) state<=BIT_LOW;  // wait for stretch to end
  end
  BIT_LOW: begin
     scl_oe<=0; advance_bit_or_ack();
  end
  // ... ACK state (release SDA, clock high, sample sda_i) ...
  STOP: begin scl_oe<=1; if(scl_i) begin sda_oe<=1; state<=IDLE; end end
endcase
Enter fullscreen mode Exit fullscreen mode

4) Multi-master arbitration & stretching

  • Never drive ‘1’. If you release SDA but read sda_i==0, you lost arbitration—abort and release bus.
  • Always wait for scl_i==1 after you release SCL high; a slave may be stretching.

5) Bus speed generation

Make a tick from your system clock:

  • Standard (100 kHz): period ≈ 10 µs.
  • Fast (400 kHz): period ≈ 2.5 µs.
  • Fast-mode Plus (1 MHz): period ≈ 1 µs (needs stronger pull-ups and low Cbus).

6) Glitch filtering & sampling

I²C tolerates short spikes; add a 2-FF synchronizer and an optional minimum-pulse filter (~50 ns) on SDA/SCL before using *_i in logic to avoid metastability/glitches.

7) Pin constraints (example Xilinx XDC)

set_property IOSTANDARD LVCMOS33 [get_ports {SCL SDA}]
# Open-drain behavior comes from OBUFT usage; disable internal pull-ups unless desired
set_property PULLUP false [get_ports {SCL SDA}]
set_property SLEW SLOW   [get_ports {SCL SDA}]
Enter fullscreen mode Exit fullscreen mode

8) Testing & bring-up tips

  • Simulate pull-ups: tri1 SDA; pullup(SDA); (and same for SCL) so Z reads as ‘1’.
  • If bus is stuck low, a slave may hold SDA; try toggling SCL ~9 pulses to free it.
  • Verify rise time with a scope; if too slow, use lower RPU or reduce bus capacitance.

9) Don’t reinvent if you don’t need to

Most vendors provide I²C master IP:

Xilinx AXI IIC, Intel I2C Controller, Lattice I2C Master, plus solid open-source cores (e.g., OpenCores).
These already handle start/stop/ACK/stretch/arbitration; you still must wire pins as open-drain.

Top comments (0)