i2c_master VHDL Module

Overview

The i2c_master module is a synthesizable, parameterizable I2C master controller written in VHDL. It supports 7-bit addressing, single-byte read/write transactions, and implements essential I2C protocol features such as start/stop condition generation, acknowledge checking, and clock stretching. The design is suitable for integration into FPGA or ASIC projects requiring I2C master functionality.

This module is based on the open-source implementation by user “rick” on the Digi-Key TechForum, with minor modifications for documentation and maintainability.

Key Features

  • Parameterizable Clocking: The module accepts generics for the input system clock (input_clk) and desired I2C bus clock (bus_clk), allowing flexible adaptation to different hardware platforms.

  • I2C Protocol Compliance: Implements start and stop conditions, address and data transfer, acknowledge checking, and clock stretching as per the I2C specification.

  • Single-Byte Transactions: Each enable (ena) pulse initiates a single-byte read or write transaction. Repeated start and multi-byte transactions can be implemented by controlling ena and updating address/data inputs.

  • Bus Arbitration: The module does not support multi-master arbitration; it is intended for single-master applications.

  • High-Impedance Bus Control: SCL and SDA lines are driven low only when required; otherwise, they are set to high-impedance (‘Z’), allowing for proper I2C open-drain operation.

Module Overview

This section provides an overview of the firmware module implementing the I2C master controller. Below is a diagram of the input and output signals, followed by a table detailing each signal.

i2c_masterclkstd_logicreset_nstd_logicenastd_logicaddrstd_logic_vector(6 downto 0)rwstd_logicdata_wrstd_logic_vector(7 downto 0)busystd_logicdata_rdstd_logic_vector(7 downto 0)sdastd_logicsclstd_logic)ack_errorstd_logic

Input signals


IN ports

Name

Description

Type

clk

system clock

std_logic

reset_n

active low reset

std_logic

ena

latch in command

std_logic

addr

address of target slave (todo change to support 10 bit addressing)

std_logic_vector(6 downto 0)

rw

'0' is write, '1' is read

std_logic

data_wr

data to write to slave

std_logic_vector(7 downto 0)

Output signals


OUT ports

Name

Description

Type

busy

indicates transaction in progress

std_logic

data_rd

data read from slave

std_logic_vector(7 downto 0)

INOUT ports

Name

Description

Type

sda

serial data output of i2c bus

std_logic

scl

serial clock output of i2c bus

std_logic)

BUFFER ports

Name

Description

Type

ack_error

flag if improper acknowledge from slave

std_logic

Generics

  • input_clk (INTEGER): Input system clock frequency in Hz (default: 50,000,000).

  • bus_clk (INTEGER): Desired I2C bus clock (SCL) frequency in Hz (default: 400,000).

Ports

  • clk (IN STD_LOGIC): System clock.

  • reset_n (IN STD_LOGIC): Active-low synchronous reset.

  • ena (IN STD_LOGIC): Latch command to initiate a transaction.

  • addr (IN STD_LOGIC_VECTOR(6 DOWNTO 0)): 7-bit I2C slave address.

  • rw (IN STD_LOGIC): Read/Write control (‘0’ = write, ‘1’ = read).

  • data_wr (IN STD_LOGIC_VECTOR(7 DOWNTO 0)): Data byte to write.

  • busy (OUT STD_LOGIC): Indicates transaction in progress.

  • data_rd (OUT STD_LOGIC_VECTOR(7 DOWNTO 0)): Data byte read from slave.

  • ack_error (BUFFER STD_LOGIC): Acknowledge error flag (set if slave does not acknowledge address or data).

  • sda (INOUT STD_LOGIC): I2C serial data line (open-drain).

  • scl (INOUT STD_LOGIC): I2C serial clock line (open-drain).

Implementation Details

Clock Generation: The module derives the I2C SCL clock from the input clock using a divider. The divider is calculated as:

divider := (input_clk / bus_clk) / 4

This divider creates four phases per SCL period, allowing precise control of SCL and SDA timing for protocol compliance. Clock stretching is supported by monitoring the SCL line during the high phase; if a slave holds SCL low, the controller pauses until SCL is released.

State Machine: The core logic is implemented as a finite state machine (FSM) with the following states:

  • ready: Idle, waiting for a transaction.

  • start: Generate I2C start condition.

  • command: Send address and R/W bit.

  • slv_ack1: Wait for slave acknowledge (address/command).

  • wr: Write data byte.

  • rd: Read data byte.

  • slv_ack2: Wait for slave acknowledge (write).

  • mstr_ack: Master acknowledge after read.

  • stop: Generate I2C stop condition.

digraph i2c_master_fsm { rankdir=TD; ready [shape=ellipse, label="ready"]; start [shape=ellipse, label="start"]; command [shape=ellipse, label="command"]; slv_ack1 [shape=ellipse, label="slv_ack1"]; wr [shape=ellipse, label="wr"]; rd [shape=ellipse, label="rd"]; slv_ack2 [shape=ellipse, label="slv_ack2"]; mstr_ack [shape=ellipse, label="mstr_ack"]; stop [shape=ellipse, label="stop"]; ready -> start [label="ena=1"]; ready -> ready [label="ena=0"]; start -> command [label=""]; command -> slv_ack1 [label="bit_cnt=0"]; command -> command [label="bit_cnt>0"]; slv_ack1 -> wr [label="rw=0"]; slv_ack1 -> rd [label="rw=1"]; wr -> slv_ack2 [label="bit_cnt=0"]; wr -> wr [label="bit_cnt>0"]; rd -> mstr_ack [label="bit_cnt=0"]; rd -> rd [label="bit_cnt>0"]; slv_ack2 -> wr [label="ena=1, addr_rw=addr&rw"]; slv_ack2 -> start [label="ena=1, addr_rw!=addr&rw"]; slv_ack2 -> stop [label="ena=0"]; mstr_ack -> rd [label="ena=1, addr_rw=addr&rw"]; mstr_ack -> start [label="ena=1, addr_rw!=addr&rw"]; mstr_ack -> stop [label="ena=0"]; stop -> ready [label=""]; }

Transaction Flow: 1. Start Condition: Generated by pulling SDA low while SCL is high.

  1. Address/Command Byte: The 7-bit address and R/W bit are shifted out MSB first.

  2. Acknowledge: After each byte, the slave must acknowledge by pulling SDA low. If not, ack_error is set.

  3. Data Transfer: - Write: Data byte is shifted out to the slave. - Read: Data byte is shifted in from the slave.

  4. Stop Condition: Generated by releasing SDA high while SCL is high.

Clock Stretching: During the SCL high phase, if the slave holds SCL low, the controller pauses until SCL is released, supporting clock stretching.

Bus Control: SCL and SDA are driven low only when required; otherwise, they are set to high-impedance (‘Z’), enabling open-drain operation and allowing external pull-ups.

Limitations

  • Only 7-bit addressing is supported.

  • Single-byte transactions per enable pulse.

  • No multi-master or arbitration support.

  • No support for 10-bit addressing or general call.

Usage Example

i2c_master_inst : entity work.i2c_master
  generic map (
    input_clk => 50000000,
    bus_clk   => 400000
  )
  port map (
    clk       => clk,
    reset_n   => reset_n,
    ena       => ena,
    addr      => addr,
    rw        => rw,
    data_wr   => data_wr,
    busy      => busy,
    data_rd   => data_rd,
    ack_error => ack_error,
    sda       => sda,
    scl       => scl
  );

References

Authors

  • Original Author: rick (Digi-Key TechForum)

  • Maintainer: Xavier Ruche (F4E external)