Driving an I²C OLED from an [FPGA](https://www.ampheo.com/c/fpgas-field-programmable-gate-array) is basically two parts:
1. build a reliable I²C master (START/STOP/ACK/byte send) with open-drain SDA/SCL,
2. implement an [OLED](https://www.onzuu.com/category/lcd-oled-character-and-numeric) controller state machine that sends the init commands + framebuffer data (most common OLEDs are SSD1306/SH1106).
Below is a practical, “works on real boards” approach.

**1) Hardware essentials (don’t skip these)**
**Bus wiring**
* SCL, SDA need pull-up [resistors](https://www.onzuu.com/category/resistors) to the OLED’s I/O voltage (often 3.3V).
* Typical: 4.7 kΩ (start here), 2.2–10 kΩ depending on bus speed/capacitance.
* FPGA pins must behave like open-drain:
* Drive 0 strongly
* For 1, release (Hi-Z) and let pull-ups bring the line high
**Voltage**
* Ensure FPGA I/O bank voltage matches OLED module I/O (3.3V common).
* Many small OLED modules accept 3.3V logic even when powered from 5V (but verify).
**2) I²C basics you must implement in FPGA**
I²C is byte-oriented with:
* START: SDA goes low while SCL high
* STOP: SDA goes high while SCL high
* Write byte: 8 bits MSB first, then ACK bit from slave (SDA pulled low = ACK)
For OLED modules:
* 7-bit slave address is commonly 0x3C or 0x3D
* Write address byte = (addr<<1) | 0 → 0x78 or 0x7A for 0x3C/0x3D
Pick a bus speed
* 100 kHz is easiest and very tolerant.
* 400 kHz is fine on short traces.
You’ll generate SCL by dividing the FPGA clock.
**3) OLED (SSD1306-style) I²C packet format**
After the address byte and ACK, SSD1306 uses a control byte:
* 0x00 = following bytes are commands
* 0x40 = following bytes are data (display RAM)
So you typically send:
* Init: [START][addrW][0x00][cmd1][cmd2]...[STOP]
* Frame update: [START][addrW][0x40][data…many bytes][STOP]
For 128×64 monochrome OLED:
* framebuffer is 128×64/8 = 1024 bytes
* data is often sent in 8 pages × 128 bytes/page
**4) Initialization sequence (typical SSD1306 128×64)**
Different modules can vary (SSD1306 vs SH1106), but this is a common SSD1306 init in page addressing mode (safe starter):
Commands (sent after control byte 0x00):
* AE display OFF
* D5 80 clock divide
* A8 3F multiplex 1/64
* D3 00 display offset
* 40 start line = 0
* 8D 14 charge pump ON (common for I²C modules)
* 20 02 memory mode = page addressing
* A1 segment remap (mirror X) (optional)
* C8 COM scan direction (mirror Y) (optional)
* DA 12 COM pins config (common for 128×64)
* 81 CF contrast (tune)
* D9 F1 pre-charge
* DB 40 VCOMH deselect
* A4 display follows RAM
* A6 normal (not inverted)
* AF display ON
If your image is upside down/mirrored, flip A1/C8.
**5) How to update the display (page write pattern)**
For each page 0..7:
1. set page + column:
* B0 | page (page select)
* 00 (lower column = 0)
* 10 (higher column = 0)
2. send 128 bytes of data with control byte 0x40
**6) Verilog skeleton: open-drain I²C lines**
**Open-drain pin driving (generic)**
```
// drive_low = 1 -> drive '0'
// drive_low = 0 -> release (Hi-Z) so pull-up makes it '1'
assign sda = sda_drive_low ? 1'b0 : 1'bz;
assign scl = scl_drive_low ? 1'b0 : 1'bz;
// Read the actual line level:
wire sda_in = sda;
wire scl_in = scl;
```
On [Xilinx](https://www.ampheo.com/manufacturer/amd) you can also use IOBUF if you prefer, but the above works if the pin supports tri-state.
**7) Verilog idea: I²C byte writer (state machine concept)**
You want a module like:
* Inputs: start, stop, write_byte, byte_in
* Outputs: busy, done, ack_error
Internally it:
* Generates SCL high/low phases
* Puts each data bit on SDA while SCL low, then raises SCL to sample
* Releases SDA on the 9th clock to read ACK
Key rule: change SDA only when SCL is low (except START/STOP).
**8) OLED controller architecture (what to build)**
**Minimal working blocks**
1. i2c_master (byte-level)
2. oled_ctrl (high-level sequences)
oled_ctrl does:
* power-up delay (a few ms)
* send init command list
* periodically push framebuffer (or only when changed)
**Framebuffer options**
* BRAM framebuffer: 1024 bytes for 128×64 (easy)
* Generate on the fly: if you only need text/simple graphics
**9) Debug checklist (saves hours)**
* Confirm pull-ups exist and match voltage.
* Check SCL frequency with a scope (start with 100 kHz).
* Verify you see:
* START, address byte, ACK low
* If no ACK:
* wrong address (0x3C vs 0x3D)
* OLED not powered / wrong voltage
* SDA/SCL swapped
* [FPGA](https://www.ampheoelec.de/c/fpgas-field-programmable-gate-array) pins not actually tri-stating for ‘1’