Back to Templates
I2C 主机控制器I2C Master Controller
基于 alexforencich/verilog-i2c 的简化 I2C 主机 FSM,支持 START/STOP/ACK 完整协议流程。
I2Cmasterverilog
Open Source Notice
Source Repository: https://github.com/alexforencich/verilog-i2c
License: MIT
Author: Alex Forencich
Copyright (c) 2015-2017 Alex Forencich
Key Points
- - FSM implements I2C protocol state machine
- - Prescaler generates SCL clock from system clock
- - Open-drain SDA/SCL emulation via tri-state
- - 7-bit addressing with R/W bit
RTL Source
// Copyright (c) 2015-2017 Alex Forencich
// SPDX-License-Identifier: MIT
// Source: https://github.com/alexforencich/verilog-i2c
// Simplified I2C Master FSM for educational use
// Based on i2c_master module from verilog-i2c project
// 中文:简化 I2C 主机 FSM,基于 alexforencich/verilog-i2c 项目
`timescale 1ns / 1ps
// I2C Master FSM Module
// 中文:I2C 主机控制器,实现 START/STOP/ACK 完整协议
// Implements I2C protocol: START, data transfer, ACK/NACK, STOP
module i2c_master_fsm #(
parameter CLK_FREQ = 50_000_000, // System clock frequency (Hz)
parameter I2C_FREQ = 100_000 // I2C clock frequency (Hz)
)(
input wire clk, // System clock
input wire rst, // Synchronous reset (active high)
// Control interface
input wire start, // Start transaction
input wire [6:0] slave_addr, // 7-bit slave address
input wire rw, // 0=write, 1=read
input wire [7:0] tx_data, // Data to transmit
output reg [7:0] rx_data, // Received data
output reg busy, // Transaction in progress
output reg done, // Transaction complete
output reg error, // NACK or error occurred
// I2C bus interface
output reg scl_o, // SCL output (active low via tri-state)
inout wire sda // SDA bidirectional (active low via tri-state)
);
// Prescaler calculation for I2C clock generation
// Quarter period in clock cycles
localparam PRESCALE = (CLK_FREQ / (4 * I2C_FREQ)) - 1;
// FSM state encoding
localparam [3:0] S_IDLE = 4'd0, // Idle state
S_START = 4'd1, // Generate START condition
S_ADDR = 4'd2, // Send address byte
S_ADDR_ACK= 4'd3, // Wait for ACK after address
S_TX = 4'd4, // Send data byte
S_TX_ACK = 4'd5, // Wait for ACK after data
S_RX = 4'd6, // Receive data byte
S_RX_ACK = 4'd7, // Send ACK/NACK after receive
S_STOP = 4'd8, // Generate STOP condition
S_ERROR = 4'd9; // Error state
reg [3:0] state, next_state;
reg [15:0] prescale_cnt; // Prescaler counter for SCL timing
reg [3:0] bit_cnt; // Bit counter (0-7 for data bytes)
reg scl_en; // SCL enable
reg sda_out; // SDA output value
reg [7:0] shift_reg; // Shift register for serial data
reg sda_oe; // SDA output enable (active = drive low)
reg scl_oe; // SCL output enable (active = drive low)
// SCL quarter-period tick
wire scl_tick = (prescale_cnt >= PRESCALE);
// Sequential: state register and counters
always @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
prescale_cnt <= 0;
bit_cnt <= 0;
shift_reg <= 0;
rx_data <= 0;
busy <= 0;
done <= 0;
error <= 0;
scl_oe <= 0;
sda_oe <= 0;
sda_out <= 1;
end else begin
state <= next_state;
// Prescaler auto-reload
if (scl_tick)
prescale_cnt <= 0;
else
prescale_cnt <= prescale_cnt + 1;
end
end
// Combinational: next-state logic and output control
always @(*) begin
next_state = state;
// Default: hold outputs
scl_oe = scl_oe;
sda_oe = sda_oe;
sda_out = sda_out;
busy = busy;
done = 0;
error = error;
case (state)
S_IDLE: begin
busy = 0;
if (start) begin
next_state = S_START;
busy = 1;
error = 0;
end
end
S_START: begin
// START condition: SDA falls while SCL is high
if (scl_tick) begin
sda_out = 0; // Pull SDA low
sda_oe = 1;
next_state = S_ADDR;
end
end
S_ADDR: begin
// Shift out 7-bit address + R/W bit
if (scl_tick) begin
if (bit_cnt < 7) begin
sda_out = slave_addr[6 - bit_cnt];
bit_cnt = bit_cnt + 1;
end else begin
sda_out = rw;
next_state = S_ADDR_ACK;
end
end
end
S_ADDR_ACK: begin
// Release SDA and check for ACK (slave pulls low)
if (scl_tick) begin
sda_oe = 0; // Release SDA
if (sda == 1) begin
next_state = S_ERROR; // NACK received
end else begin
next_state = S_TX;
end
end
end
S_TX: begin
// Shift out 8 data bits (MSB first)
if (scl_tick) begin
if (bit_cnt < 8) begin
sda_out = tx_data[7 - bit_cnt];
bit_cnt = bit_cnt + 1;
end else begin
next_state = S_TX_ACK;
end
end
end
S_TX_ACK: begin
// Wait for ACK after data byte
if (scl_tick) begin
sda_oe = 0;
if (sda == 1) begin
next_state = S_ERROR;
end else begin
next_state = S_STOP;
end
end
end
S_RX: begin
// Receive 8 data bits (MSB first)
if (scl_tick) begin
sda_oe = 0; // Release SDA for slave to drive
if (bit_cnt < 8) begin
bit_cnt = bit_cnt + 1;
end else begin
next_state = S_RX_ACK;
end
end
end
S_RX_ACK: begin
// Send NACK (master doesn't ACK last byte)
if (scl_tick) begin
sda_out = 1; // NACK
sda_oe = 1;
next_state = S_STOP;
end
end
S_STOP: begin
// STOP condition: SDA rises while SCL is high
if (scl_tick) begin
sda_oe = 0; // Release SDA (pulled high externally)
next_state = S_IDLE;
done = 1;
end
end
S_ERROR: begin
error = 1;
done = 1;
busy = 0;
next_state = S_IDLE;
end
default: next_state = S_IDLE;
endcase
end
// SCL output control (toggle on prescaler ticks)
always @(posedge clk) begin
if (rst) begin
scl_oe <= 0;
end else if (state != S_IDLE && state != S_ERROR) begin
if (scl_tick)
scl_oe <= ~scl_oe; // Toggle SCL
end else begin
scl_oe <= 0;
end
end
// SDA output (active low, open-drain emulation)
assign sda = sda_oe ? 1'b0 : 1'bz;
endmodule