FPGA Implementation of EEPROM Communication Using the I2C Protocol

The Inter-Integrated Circuit (I2C) interface operates over a bidirectional two-wire connection. The serial data line is designated as SDA, while the serial clock line is labeled SCL. To acommodate multiple devices sharing the same bus, these lines typically utilize an open-drain configuration, requiring external pull-up resistors to maintain proper voltage levels.

Communication Timing Sequence

1. Idle Condition

In the absence of active transactions, both the SDA and SCL lines rest at a high logic level due to the pull-up resistors.

2. Initiation Signal

A start condition occurs when the SDA line transitions from a high state to a low state while the SCL line remains high.

3. Data Transfer Window

Data bits are transmitted synchronously with the SCL clock pulses during the active transaction phase.

4. Termination Signal

A stop condition is generated when the SDA line returns to a high state while the SCL line is high.

Read and Write Operatiosn

Standard Write Cycle

This involves sending the slave address followed by the specific memory address and then the data payload.

Continuous Write Mode

Distinguishes itself by allowing multiple data bytes to be sent immediately following the initial address byte without reissuing the start condition.

Random Access Read

Requires first setting the internal address pointer via a write cycle, followed by a restart and a read command. While modes exist for sequential reading or reading the current address without re-setting, limiting the design to random access maximizes code portability across different device configurations.

FPGA Architecture Overview

The system is constructed hierarchically, comprising a top-level wrapper that manages resource allocation, a dedicated control unit for data management, and a hardware driver responsible for signaling compliance.

System Top Level

module eeprom_i2c_system_controller(
    input                 clock_input       ,  
    input                 reset_active_low  ,  
    // Bus Interface
    output                clk_bus           ,  
    inout                 data_bus          ,  
);

// Memory Management Instance
mem_iface_logic #(
    .MAX_PACKET_SIZE   (8) 
) u_mem_controller(
    .clk             (clock_input      ), 
    .rst_n           (reset_active_low ), 
    .cmd_start       (start_command    ), 
    .op_type         (rw_control       ), 
    .reg_address     (addr_register    ), 
    .write_payload   (data_out_buffer  ), 
    .read_output     (data_in_buffer   ), 
    .txn_complete    (transaction_done ), 
    .ack_status      (bus_ack          ), 
    // External status
    .proc_finished   (task_complete    ), 
    .status_flag     (task_result      )  
);

// Hardware Driver Instance
i2c_hw_driver #(
    .TARGET_SLAVE_ID (7'hA0),       
    .SOURCE_CLOCK_HZ (26'd50_000_000),
    .BUS_BAUD_RATE   (18'd250_000    )     
) u_bus_driver(
    .clk             (clock_input      ),   
    .rst_n           (reset_active_low ),   
    .cmd_start       (start_command    ), 
    .addr_width      (bit_select       ), 
    .op_type         (rw_control       ), 
    .reg_address     (addr_register    ), 
    .write_payload   (data_out_buffer  ), 
    .read_output     (data_in_buffer   ), 
    .txn_complete    (transaction_done ), 
    .ack_status      (bus_ack          ), 
    .scl_line        (clk_bus          ),  
    .sda_line        (data_bus         ),  
    .drv_clock       (drv_clk          )    
);

endmodule

Memory Interface Controller

This layer handles higher-level logic, determining whether the operation is a write or read and managing the flow of data buffers.

module mem_iface_logic(
    input                 clock_input   , 
    input                 reset_active_low  , 

    // Control Lines
    output    reg         rw_control    , 
    output    reg         start_command , 
    output    reg [15:0]  addr_register , 
    output    reg [ 7:0]  data_out_buffer , 
    
    // Feedback
    input         [ 7:0]  data_in_buffer , 
    input                 transaction_done , 
    input                 bus_ack        
);

// Logic implementation details...
endmodule

Hardware Driver Logic

The core of the implementation resides within the driver module, which generates the precise electrical sequences required by the I2C specification.

module i2c_hw_driver
    # (
      parameter TARGET_SLAVE_ID = 7'b1010100 ,
      parameter SOURCE_CLOCK_HZ = 26'd100_000_000,
      parameter BUS_BAUD_RATE   = 18'd100_000     
    )
    (         
    input                 clock_input       ,     
    input                 reset_active_low  ,  
             
    // Control Inputs                   
    input                 start_command   ,  
    input                 bit_select      ,  // Selects 16-bit vs 8-bit addressing
    input                 rw_control      ,  // Write=1, Read=0
    input         [15:0]  addr_register   ,  // Internal register map address
    input         [ 7:0]  data_out_buffer ,  // Payload to transmit
    output    reg [ 7:0]  data_in_buffer  ,  // Received payload
    output    reg         txn_complete    ,  // Operation finished flag
    output    reg         bus_ack         ,  // Acknowledge from slave
    output    reg         scl_line        ,  // Clock pin
    inout                 sda_line        ,  // Data pin
                             
    // Outputs                 
    output    reg         drv_clk         
    
 );

/* State Machine Definition */
// State Encoding
localparam STATE_WAIT     = 3'd0;
localparam STATE_SADDR    = 3'd1;
localparam STATE_ADR_HB   = 3'd2;
localparam STATE_ADR_LB   = 3'd3;
localparam STATE_WDATA    = 3'd4;
localparam STATE_R_SETUP  = 3'd5;
localparam STATE_RDATA    = 3'd6;
localparam STATE_STOPPED  = 3'd7;

reg [3:0] curr_state;
reg [3:0] next_state;
reg sda_out;

always @(posedge clock_input or negedge reset_active_low) begin
    if (!reset_active_low) begin
        curr_state <= STATE_WAIT;
    end else begin
        curr_state <= next_state;
    end
end

// Next State Logic
always @(*) begin
    case(curr_state)
        STATE_WAIT: 
            if(start_command) next_state = STATE_SADDR;
            else              next_state = STATE_WAIT;

        STATE_SADDR: 
            if(txn_complete) next_state = (bit_select) ? STATE_ADR_HB : STATE_ADR_LB;
            else              next_state = STATE_SADDR;

        STATE_ADR_HB: 
            if(txn_complete) next_state = STATE_ADR_LB;
            else              next_state = STATE_ADR_HB;

        STATE_ADR_LB: 
            if(txn_complete) begin
                if(!rw_control) next_state = STATE_WDATA;
                else            next_state = STATE_R_SETUP;
            end
            else              next_state = STATE_ADR_LB;

        STATE_WDATA: 
            if(txn_complete) next_state = STATE_STOPPED;
            else              next_state = STATE_WDATA;

        STATE_R_SETUP: 
            if(txn_complete) next_state = STATE_RDATA;
            else              next_state = STATE_R_SETUP;

        STATE_RDATA: 
            if(txn_complete) next_state = STATE_STOPPED;
            else              next_state = STATE_RDATA;

        STATE_STOPPED: 
            if(txn_complete) next_state = STATE_WAIT;
            else              next_state = STATE_STOPPED;

        default: next_state = STATE_WAIT;
    endcase
end

// ... Additional waveform generation logic for SCL/SDA toggling ...


Tags: Verilog i2c-protocol fpga-design eeprom digital-circuits

Posted on Sat, 23 May 2026 19:51:53 +0000 by Jackomo0815