从零开始手把手教你用Verilog实现I2C Master控制器(附完整RTL代码与仿真)

张开发
2026/4/30 15:49:20 15 分钟阅读

分享文章

从零开始手把手教你用Verilog实现I2C Master控制器(附完整RTL代码与仿真)
从零构建I2C Master控制器Verilog实战指南与调试技巧在FPGA开发中I2C总线因其简单的两线制结构和多设备支持特性成为连接传感器、存储器和显示设备的首选方案。当项目需要与OLED屏幕、EEPROM或各类传感器通信时一个稳定可靠的I2C Master控制器往往是关键所在。本文将带你从协议原理出发逐步构建完整的Verilog实现并通过仿真验证确保其在实际系统中的可靠性。1. 开发环境准备与项目初始化1.1 工具链选择与配置现代FPGA开发通常使用Xilinx Vivado或Intel Quartus作为主要工具链。对于I2C控制器开发建议创建独立的RTL模块项目# Vivado项目创建示例Tcl命令 create_project i2c_master ./i2c_master -part xc7z020clg400-1 add_files -norecurse i2c_master.v add_files -norecurse i2c_slave_model.v表开发环境关键组件组件推荐版本备注Vivado2022.2支持SystemVerilog-2005语法ModelSim10.7用于功能仿真Python3.8可选用于自动化测试脚本1.2 协议基础回顾I2C协议的核心时序要素包括起始条件SCL高电平时SDA从高到低跳变停止条件SCL高电平时SDA从低到高跳变数据有效性SCL高电平期间SDA必须保持稳定ACK/NACK每个字节传输后的第9个时钟周期注意I2C标准模式时钟频率为100kHz快速模式可达400kHz实际设计需考虑时序余量2. RTL架构设计与状态机实现2.1 模块接口定义完整的I2C Master控制器需要以下关键接口信号module i2c_master ( input wire clk, input wire rst_n, // APB接口 input wire psel, input wire penable, input wire [31:0] paddr, input wire pwrite, input wire [31:0] pwdata, output reg [31:0] prdata, // I2C物理接口 output wire scl_o, output wire scl_en, output wire sda_o, output wire sda_en, input wire sda_i );2.2 主状态机设计I2C传输过程可分解为以下状态stateDiagram [*] -- IDLE IDLE -- START: 收到传输请求 START -- ADDR: 发送起始条件完成 ADDR -- WRITE: 地址写命令 ADDR -- READ: 地址读命令 WRITE -- DATA: 发送数据字节 READ -- DATA: 接收数据字节 DATA -- ACK: 等待从机响应 ACK -- STOP: 收到NACK或最后一字节 ACK -- DATA: 需要继续传输 STOP -- IDLE: 传输结束对应的Verilog状态机实现框架typedef enum logic [3:0] { ST_IDLE, ST_START, ST_ADDR, ST_WRITE, ST_READ, ST_DATA, ST_ACK, ST_STOP } i2c_state_t; always_ff (posedge clk or negedge rst_n) begin if (!rst_n) begin state ST_IDLE; end else begin case (state) ST_IDLE: if (start_transfer) state ST_START; ST_START: if (sda_fall) state ST_ADDR; // 其他状态转换逻辑... endcase end end3. 关键时序实现与调试技巧3.1 时钟生成与同步SCL时钟需要满足协议要求的最小高低电平时间// 时钟分频计算示例 localparam PRESCALE SYSTEM_CLK_FREQ / (5 * TARGET_SCL_FREQ) - 1; reg [15:0] clk_divider; always_ff (posedge clk) begin if (clk_divider PRESCALE) begin scl_tick 1b1; clk_divider 0; end else begin scl_tick 1b0; clk_divider clk_divider 1; end end表常见I2C模式时序参数模式时钟频率最小高电平时间最小低电平时间标准模式100kHz4.0μs4.7μs快速模式400kHz0.6μs1.3μs快速模式1MHz0.26μs0.5μs3.2 常见时序问题排查实际调试中经常遇到的问题包括仲裁丢失多个Master竞争总线时未正确处理冲突时钟拉伸未正确处理Slave的SCL保持请求建立/保持时间违规SDA信号在SCL边沿附近变化调试建议使用逻辑分析仪捕获SCL/SDA信号重点关注起始/停止条件和ACK周期4. 功能验证与测试用例设计4.1 测试平台搭建完整的验证环境应包含I2C Master DUT被测设计I2C Slave行为模型APB接口驱动结果检查器module i2c_tb; // 时钟生成 reg clk 0; always #5 clk ~clk; // 复位生成 reg rst_n 0; initial begin #100 rst_n 1; end // 实例化DUT和Slave模型 i2c_master dut (.*); i2c_slave_model slave (.*); // 测试用例 initial begin // 用例1单字节写入测试 apb_write(ADDR_CTRL, 8h01); // 使能I2C apb_write(ADDR_TXR, 8hA0); // 从机地址写 apb_write(ADDR_CR, CMD_START); // ...其他测试序列 end endmodule4.2 典型测试场景必须覆盖的测试用例包括基本读写测试单字节读写操作验证多字节传输连续读写多个数据字节从机无响应处理NACK情况的正确性总线竞争多Master场景下的仲裁逻辑时钟拉伸Slave延长时钟低电平的情况在最近的一个OLED驱动项目中我们发现当Slave设备需要较长时间处理数据时如EEPROM写入周期必须增加超时检测机制。这促使我们在状态机中增加了等待状态通过计数器防止总线死锁。

更多文章