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 ...