EEPROM 简介
EEPROM (Electrically Erasable Progammable Read Only Memory,E2PROM)即电可擦除可编程只读存储器,是一种常用的非易失性存储器(掉电数据不丢失),EEPROM 有多种类型的产品,此次实验使用的是ATMEL 公司生产的 AT24C 系列的 AT24C64 这一型号。AT24C64 具有高可靠性,可对所存数据保存 100 年,并可多次擦写,擦写次数达一百万次。
AT24C64 采用两线串行接口的双向数据传输协议——I2C 协议实现读写操作,其存储容量为 64Kbit,内部分成 256 页,每页 32 字节,共有 8192 个字节,且其读写操作都是以字节为基本单位,写入的数据是先写入到缓存中,需要等一段时间才会同步到ROM中(可以通过发设备地址来确认同步是否完成,完成后会响应ACK,否则响应NAK),对于 AT24C64 其缓存大小为 32B ,因此一次可写入的最大长度为 32 字节。
I2C 协议解析
I2C 即 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司(现在的 NXP 半导体公司)在八十年代初设计出来的一种简单、双向、二线制总线标准,多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。
在物理层上,I2C 接口只需要两条总线线路,即 SCL(串行时钟线)、SDA(串行数据线),I2C 总线是半双工的,所以任意时刻只能有一个主机。每个连接在总线上的 I2C 器件都有一个唯一的器件地址,在通信的时候就是靠这个地址来通信的。I2C 传输速率标准模式下可以达到 100kb/s,快速模式下可以达到 400kb/s,高速模式下可达 3.4Mbit/s。总线上的主设备与从设备之间以字节(8 位)为单位进行双向的数据传输。
I2C 总线上的每一个设备都可以作为主设备或者从设备,但同一时刻最多只能有一个主设备,且每一个从设备都会对应一个唯一的地址(可以从 I2C 器件数据手册得知),主从设备之间就是通过这个地址来确定与哪个器件进行通信。通常情况下,主从器件的角色是确定的,也就是说从机一直工作在从机模式,如下是常见的 I2C总线物理拓扑结构图。
I2C 协议规定
- 在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,所以数据总线(SDA)在时钟(SCL)为低电平的时候才能改变。
- 在时钟(SCL)为高电平的时候,数据总线(SDA)由高到低的跳变为总线起始信号
- 在时钟(SCL)为高电平的时候,数据总线(SDA)由低到高的跳变为总线停止信号
- 当传输器件将 8 位数据输出完后,会将数据总线(SDA)释放(即设置为输入),然后等待接收器件应答(低电平 0 表示应答,1 表示非应答),此时的时钟仍然是主机提供的。
I2C 器件地址
每个 I2C 器件都有一个器件地址,有些 I2C 器件的器件地址是固定的,而有些 I2C 器件的器件地址由一个固定部分和一个可编程的部分构成,这是因为很可能在一个系统中有几个同样的器件,器件地址的可编程部分能最大数量的使这些器件连接到 I2C 总线上,例如 EEPROM 器件,为了增加系统的 EEPROM 容量,可能需要多个 EEPROM。器件可编程地址位一般由它管脚决定,比如 EEPROM 器件一般会留下 3 个管脚用于设置可编程地址位。但有些 I2C 器件在出厂时器件地址就设置好了,用户不可以更改(如实时时钟 PCF8563 的器件地址为固定的 7’h51)。
对于 AT24C64 而言,其器件地址为 1010 加 3 位的可编程地址,3 位可编程地址由器件上的 3 个管脚A2、A1、A0(见图 31.2.2)的硬件连接决定。当硬件电路上分别将这三个管脚连接到 GND 或 VCC 时,就可以设置不同的可编程地址。
进行数据传输时,主机首先向总线上发出开始信号,对应开始位 S,然后按照从高到低的位序发送器件地址,一般为 7bit,第 8bit 位为读写控制位 R/W,该位为 0 时表示主机对从机进行写操作,当该位为 1时表示主机对从机进行读操作,然后接收从机响应。对于 AT24C64 来说,其传输器件地址格式如下图所示。
I2C 存储器地址
一般而言,每个兼容 I2C 协议的器件,内部总会有可供读写的寄存器或存储器,例如,EEPROM 存储器,内部就是顺序编址的一系列存储单元;型号为 OV7670 的 CMOS 摄像头(OV7670 的该接口叫 SCCB 接口,其实质也是一种特殊的 I2C 协议,可以直接兼容 I2C 协议),其内部就是一系列编址的可供读写的寄存器。因此,要对一个 I2C 器件中的存储单元(包括寄存器和存储器)进行读写时,必须要先指定存储单元的地址。该地址为一个或两个字节长度,具体长度由器件内部的存储单元的数量决定,当存储单元数量不超过一个字节所能表示的最大数量(256)时,用一个字节表示,超过一个字节所能表示的最大数量时,就需要用两个字节来表示,例如同是 E2PROM 存储器,AT24C02 的存储单元容量为 2Kbit=256Byte,用一个字节地址即可寻址所有的存储单元,而 AT24C64 的存储单元容量为64Kb=8KB,需要 13 位的地址位,而 I2C 又是以字节为单位进行传输的,所以需要用两个字节地址来寻址整个存储单元。
I2C 单字节写时序
下图分别是 1 字节存储器地址器件和 2 字节存储器地址器件单字节写时序图
单字节存储器地址写单字节数据过程:
- 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机产生 STOP 位,终止传输。
双字节存储器地址写单字节数据过程: - 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机产生 STOP 位,终止传输。
I2C 连续写时序
注意:
I2C 连续写时序仅部分器件支持
连续写是主机连续写多个字节数据到从机,这个和单字节写操作类似,连续多字节写操作也是分为 1 字节存储器地址器件和 2 字节存储器地址器件的写操作,下图分别是 1 字节存储器地址器件和 2 字节存储器地址器件的连续写时序图
单字节存储器地址器件连续多字节写数据过程:
- 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输待写入的第 1 个数据;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功后,主机设置 SDA 为输出,传输待写入的下一个数据;
- 设置 SDA 为三态门输入,读取从机应答信号;n 个数据被写完,转到步骤 11,若数据未被写完,转到步骤 9;
- 读取应答信号成功,主机产生 STOP 位,终止传输。
双字节存储器地址器件连续多字节写数据过程: - 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输待写入的第 1 个数据;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功后,主机设置 SDA 为输出,传输待写入的下一个数据;
- 设置 SDA 为三态门输入,读取从机应答信号;n 个数据被写完,转到步骤 13,若数据未被写完,转到步骤 11;
- 读取应答信号成功,主机产生 STOP 位,终止传输。
I2C 单字节读时序
单字节读操作分为 1 字节存储器地址器件单字节数据读操作和 2 字节存储器地址器件单字节数据读操作。下图分别为不同情况的时序图
单字节存储器地址读取单字节数据过程:
- 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 1,表明为读操作;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
- 产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
- 主机产生 STOP 位,终止传输。
双字节存储器地址读取单字节数据过程: - 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 1,表明为读操作;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
- 主机设置 SDA 输出,产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
- 主机产生 STOP 位,终止传输
I2C 连续读时序
连续读是主机连续从从机读取多个字节数据,这个和单字节读操作类似,连续多字节读操作也是分为 1 字节存储器地址器件和 2 字节存储器地址器件的读操作,下图分别为字节存储器地址器件和 2 字节存储器地址器件连续多字节读的时序图。
单字节存储器地址器件连续多字节读取数据过程:
- 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 1,表明为读操作;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的第 1 个字节的数据;
- 主机设置 SDA 输出,发送一位应答信号;
- 设置 SDA 为三态门输入,读取 SDA 总线上的下一个字节的数据;若 n 个字节数据读完成,跳转到步骤 13,若数据未读完,跳转到步骤 11;
- 主机设置 SDA 输出,产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
- 主机产生 STOP 位,终止传输。
双字节存储器地址器件连续多字节读取数据过程: - 主机设置 SDA 为输出;
- 主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 0,表明为写操作;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
- 主机设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机发起起始信号;
- 主机传输器件地址字节,其中最低位为 1,表明为读操作;
- 设置 SDA 为三态门输入,读取从机应答信号;
- 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的第 1 个字节的数据;
- 主机设置 SDA 输出,发送一位应答信号;
- 设置 SDA 为三态门输入,读取 SDA 总线上的下一个字节的数据;若 n 个字节数据读完成,跳转到步骤 15,若数据未读完,跳转到步骤 13;
- 主机设置 SDA 输出,产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
- 主机产生 STOP 位,终止传输。
I2C 控制器功能拆分
通过前面的读写时序可以发现,每次读写过程都可以由开始、发送(包括读取对方应答)、接收(包括回复对方应答)、停止这 4 过个操作组合而来,因此 I2C 控制器只需要实现这 4 个操作即可,三层模块提供 I2C 控制其提供的这 4 中操作即可完成 I2C 从设备的读写。
EEPROM 原理图
代码编写
代码一共分为3个模块,分别是 I2C 驱动模块、EEPROM 驱动模块、EEPROM 读写测试模块,其功能如下:
I2C 驱动模块;提供 I2C 总线收发数据的功能,生成模块可以提供此模块在总线上测试起始和结束信号,并进行数据收发。
EEPROM 驱动模块;基于 I2C驱动模块实现 EEPROM 的一些基本操作,如读、写等。
EEPROM 读写测试模块;利用 EEPROM 驱动模块提供的 EEPROM 基本操作进行读、写测试,测试过程中状态 LED 常灭,测试出错状态 LED闪烁,测试完成状态 LED 常亮。
I2C 驱动模块
module i2c_driver #(parameter I2C_CLK_PERIOD = 125 //I2C时钟周期,以系统时钟为参考
)
(input sys_rst_n, //系统复位input sys_clk, //系统时钟input [7:0] ctrl_cmd, //i2c控制命令input ctrl_start, //i2c控制器启动信号output ctrl_idle, //i2c控制器空闲信号output reg ctrl_done, //i2c控制器完成信号input [7:0] tx_data, //需要发送的数据output reg tx_ack, //数据发送完成后从设备返回的应答output reg [7:0] rx_data, //接收到的数据input rx_ack, //数据接收完成后响应给从机的应答output reg i2c_scl, //i2c时钟输出output reg i2c_sda_o, //i2c数据输出input i2c_sda_i, //i2c数据输入output reg i2c_sda_t //i2c数据方向控制,1输入,0输出
);//时钟周期的一半
localparam I2C_CLK_PERIOD_HALF = I2C_CLK_PERIOD / 2;//状态机的状态
localparam IDLE_STATE = 8'b0000001; //空闲状态
localparam GEN_STA_STATE = 8'b0000010; //产生起始信号
localparam WR_DATA_STATE = 8'b0000100; //写数据状态,写完8bit后还要读ACK
localparam RD_DATA_STATE = 8'b0001000; //读数据状态,读完8bit后还要生成ACK
localparam GEN_STO_STATE = 8'b0010000; //产生停止信号//指令集
localparam GEN_STA_CMD = 7'b0000010; //产生起始信号
localparam WR_DATA_CMD = 7'b0000100; //写数据状态,写完8bit后还要读ACK
localparam RD_DATA_CMD = 7'b0001000; //读数据状态,读完8bit后还要生成ACK
localparam GEN_STO_CMD = 7'b0010000; //产生停止信号//I2C时钟周期计数器
reg [32:0] clk_period_count;//传输bit计数
reg [7:0] bit_cnt;//发送移位寄存器
reg [7:0] tx_data_reg;
//从机响应的ACK
reg tx_ack_reg;//接收移位寄存器
reg [7:0] rx_data_reg;
//发送给从机的ACK
reg rx_ack_reg;//上一刻的状态
reg [7:0] prev_state;
//当前状态
reg [7:0] current_state;
//下一刻的状态
reg [7:0] next_state;
//当前状态结束标志,切换到下一个状态
reg state_done;//I2C总线空闲标志
reg bus_idle_flag;//记录上一时刻的状态
//状态跳转
always @(posedge sys_clk) beginif(!sys_rst_n) beginprev_state <= IDLE_STATE;current_state <= IDLE_STATE;endelse beginprev_state <= current_state;current_state <= next_state;end
end//根据当前状态确定下一刻状态
always @(*) begincase(current_state)IDLE_STATE: beginif((state_done == 1'b0) && (ctrl_start == 1'b1) && (ctrl_cmd == GEN_STA_CMD))next_state = GEN_STA_STATE;else if((state_done == 1'b0) && (ctrl_start == 1'b1) && (ctrl_cmd == WR_DATA_CMD))next_state = WR_DATA_STATE;else if((state_done == 1'b0) && (ctrl_start == 1'b1) && (ctrl_cmd == RD_DATA_CMD))next_state = RD_DATA_STATE;else if((state_done == 1'b0) && (ctrl_start == 1'b1) && (ctrl_cmd == GEN_STO_CMD))next_state = GEN_STO_STATE;elsenext_state = IDLE_STATE;endGEN_STA_STATE: beginif(state_done == 1'b1)next_state = IDLE_STATE;elsenext_state = GEN_STA_STATE;endWR_DATA_STATE: beginif(state_done == 1'b1)next_state = IDLE_STATE;elsenext_state = WR_DATA_STATE;endRD_DATA_STATE: beginif(state_done == 1'b1)next_state = IDLE_STATE;elsenext_state = RD_DATA_STATE;endGEN_STO_STATE: beginif(state_done == 1'b1)next_state = IDLE_STATE;elsenext_state = GEN_STO_STATE;enddefault: beginnext_state = IDLE_STATE;endendcase
end//进行时钟周期计数
always @(posedge sys_clk) beginif(!sys_rst_n)clk_period_count <= 32'b0;else begincase(current_state)IDLE_STATE: beginclk_period_count <= 32'b0;endGEN_STA_STATE, WR_DATA_STATE, RD_DATA_STATE, GEN_STO_STATE: begin//产生启动信号,按I2C_CLK_PERIOD进行计数//发送数据,发送完成后读取ACK,按I2C_CLK_PERIOD进行计数//接收数据,接收完成后生成ACK,按I2C_CLK_PERIOD进行计数//产生停止信号,按I2C_CLK_PERIOD进行计数if(state_done == 1'b0) beginif(clk_period_count < (I2C_CLK_PERIOD - 1))clk_period_count <= clk_period_count + 32'b1;elseclk_period_count <= 32'b0;endenddefault: beginclk_period_count <= 32'b0;endendcaseend
end//进行传输bit计数
always @(posedge sys_clk) beginif(!sys_rst_n)bit_cnt <= 8'b0;else begincase(current_state)IDLE_STATE: beginbit_cnt <= 8'b0;endGEN_STA_STATE, GEN_STO_STATE: begin//产生开始信号和停止信号时第一个时钟周期用于将IO设置为初始状态,低二个时钟周期输出开始或结束信号if(clk_period_count == (I2C_CLK_PERIOD - 1)) beginif(bit_cnt < (2 - 1))bit_cnt <= bit_cnt + 8'b1;endendWR_DATA_STATE, RD_DATA_STATE: begin//发送数据,8bit数据加1bit应答//接收数据,8bit数据加1bit应答if(clk_period_count == (I2C_CLK_PERIOD - 1)) beginif(bit_cnt < (9 - 1))bit_cnt <= bit_cnt + 8'b1;endenddefault: beginbit_cnt <= 8'b0;endendcaseend
end//锁存需要发送的数据
always @(posedge sys_clk) beginif(!sys_rst_n) begintx_data_reg <= 8'b0;rx_ack_reg <= 1'b0;endelse if(current_state == IDLE_STATE) begin//暂存需要发送的数据if(next_state == WR_DATA_STATE)tx_data_reg <= tx_data;//暂存需要发送的ACKif(next_state == RD_DATA_STATE)rx_ack_reg <= rx_ack;end
end//控制数据传输
always @(posedge sys_clk) beginif(!sys_rst_n) beginbus_idle_flag <= 1'b1;i2c_scl <= 1'b1;i2c_sda_o <= 1'b1;i2c_sda_t <= 1'b1;tx_ack_reg <= 1'b0;rx_data_reg <= 8'b0;state_done <= 1'b0;endelse begincase(current_state)IDLE_STATE: beginif(bus_idle_flag == 1'b1) begini2c_scl <= 1'b1;i2c_sda_o <= 1'b1;i2c_sda_t <= 1'b1;tx_ack_reg <= 1'b0;rx_data_reg <= 8'b0;endstate_done <= 1'b0;endGEN_STA_STATE: beginif(state_done == 1'b0) begin//设置总线为忙if((bit_cnt == 0) && (clk_period_count == 0))bus_idle_flag <= 1'b0;//第一个时钟周期SCL和SDA均输出高,进入准备状态//第二个时钟周期SCL保持高,SDA前半个周期为高后半个周期为低,产生开始信号if(bit_cnt <= (2 - 2)) begin//SCL为高i2c_scl <= 1'b1;//SDA为输出i2c_sda_t <= 1'b0;//SDA为输出高i2c_sda_o <= 1'b1;endelse begin//SCL为高i2c_scl <= 1'b1;//SDA为输出i2c_sda_t <= 1'b0;//前半个周期SDA为高,后半个周期SDA为低if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1)) i2c_sda_o <= 1'b1;elsei2c_sda_o <= 1'b0;end//启动信号结束if((bit_cnt == (2 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1)))state_done <= 1'b1;endendWR_DATA_STATE: beginif(state_done == 1'b0) beginif(bus_idle_flag == 1'b0) beginif(bit_cnt <= (9 - 2)) begin//写8bit数据//SDA为输出i2c_sda_t <= 1'b0;//SDA输出数据寄存器中的值i2c_sda_o <= tx_data_reg[7 - bit_cnt];//前半个周期SCL为低,后半个周期SCL为高if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1)) i2c_scl <= 1'b0;elsei2c_scl <= 1'b1;endelse begin//读从机应答//SDA为输入i2c_sda_t <= 1'b1;//前半个周期SCL为低,后半个周期SCL为高if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1))i2c_scl <= 1'b0;elsei2c_scl <= 1'b1;//在时钟周期3/4处采样SDA输入if(clk_period_count == (I2C_CLK_PERIOD - I2C_CLK_PERIOD / 4 - 1))tx_ack_reg <= i2c_sda_i;end//写操作结束if((bit_cnt == (9 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1)))state_done <= 1'b1;endelsestate_done <= 1'b1;endendRD_DATA_STATE: beginif(state_done == 1'b0) beginif(bus_idle_flag == 1'b0) beginif(bit_cnt <= (9 - 2)) begin//读8bit数据//SDA为输入i2c_sda_t <= 1'b1;//前半个周期SCL为低,后半个周期SCL为高if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1))i2c_scl <= 1'b0;elsei2c_scl <= 1'b1;//在时钟周期3/4处采样SDA输入if(clk_period_count == (I2C_CLK_PERIOD - I2C_CLK_PERIOD / 4 - 1))rx_data_reg[7 - bit_cnt] <= i2c_sda_i;endelse begin//写从机应答//SDA为输出i2c_sda_t <= 1'b0;//SDA输出应答i2c_sda_o <= rx_ack_reg;//前半个周期SCL为低,后半个周期SCL为高if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1)) i2c_scl <= 1'b0;elsei2c_scl <= 1'b1;end//读操作结束if((bit_cnt == (9 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1))) state_done <= 1'b1;endelsestate_done <= 1'b1;endendGEN_STO_STATE: beginif(state_done == 1'b0) begin//第一个时钟周期SCL和SDA均输出低,进入准备状态//第二个时钟周期SCL保持高,SDA前半个周期为低后半个周期为高,产生停止信号if(bit_cnt <= (2 - 2)) begin//SCL为低i2c_scl <= 1'b0;//SDA为输出i2c_sda_t <= 1'b0;//SDA为输出低i2c_sda_o <= 1'b0;endelse begin//SCL为高i2c_scl <= 1'b1;//SDA为输出i2c_sda_t <= 1'b0;//前半个周期SDA为低,后半个周期SDA为高if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1)) i2c_sda_o <= 1'b0;elsei2c_sda_o <= 1'b1;end//停止信号结束if((bit_cnt == (2 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1)))state_done <= 1'b1;//设置总线为空闲if((bit_cnt == (2 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1)))bus_idle_flag <= 1'b1;endenddefault: beginif(bus_idle_flag == 1'b1) begini2c_scl <= 1'b1;i2c_sda_o <= 1'b1;i2c_sda_t <= 1'b1;tx_ack_reg <= 1'b0;rx_data_reg <= 8'b0;endstate_done <= 1'b0;endendcaseend
end//输出完成信号和数据
always @(posedge sys_clk) beginif(!sys_rst_n) beginctrl_done <= 1'b0;tx_ack <= 1'b0;rx_data <= 8'b0;endelse begincase(current_state)IDLE_STATE: beginif(prev_state != IDLE_STATE) beginctrl_done <= 1'b1;tx_ack <= tx_ack_reg;rx_data <= rx_data_reg;endelsectrl_done <= 1'b0;enddefault: beginctrl_done <= 1'b0;endendcaseend
end//空闲标志输出
assign ctrl_idle = ((current_state == IDLE_STATE) && (state_done == 1'b0) && (sys_rst_n == 1'b1)) ? 1'b1 : 1'b0;endmodule
EEPROM 读写控制模块
module eeprom_driver #(parameter I2C_CLK_PERIOD = 125, //I2C时钟周期,以系统时钟为参考parameter EEPROM_MEM_ADDR_BYTES = 2 //内存地址字节数
)
(input sys_rst_n, //系统复位input sys_clk, //系统时钟input eeprom_start, //启动信号input [7:0] eeprom_cmd, //操作命令input [6:0] slave_addr, //从机地址input [15:0] mem_addr, //操作的存储器地址input [7:0] wr_data_len, //写入长度input [7:0] wr_data, //需要写入的数据output reg wr_data_req, //写入数据请求input [7:0] rd_data_len, //读取长度output [7:0] rd_data, //读取到的数据output rd_data_flag, //读取输出标志output eeprom_idle, //空闲标志output reg eeprom_error_flag, //EEPROM错误标志output i2c_scl, //i2c时钟输出output i2c_sda_o, //i2c数据输出input i2c_sda_i, //i2c数据输入output i2c_sda_t //i2c数据方向控制,1输入,0输出
);//状态机的状态
localparam IDLE_STATE = 8'h01; //空闲状态
localparam READ_STATE = 8'h02; //读状态
localparam WRITE_STATE = 8'h04; //写状态//EEPROM操作命令
localparam READ_CMD = 8'h02; //读命令
localparam WRITE_CMD = 8'h04; //写命令//i2c指令集
localparam GEN_STA_CMD = 7'b0000010; //产生起始信号
localparam WR_DATA_CMD = 7'b0000100; //写数据状态,写完8bit后还要读ACK
localparam RD_DATA_CMD = 7'b0001000; //读数据状态,读完8bit后还要生成ACK
localparam GEN_STO_CMD = 7'b0010000; //产生停止信号//当前状态
reg [7:0] current_state;
//下一刻的状态
reg [7:0] next_state;
//当前状态结束标志,切换到下一个状态
reg state_done;//I2C操作启动计数
reg [7:0] i2c_opt_begin_count;
//I2C操作完成计数
reg [7:0] i2c_opt_end_count;
//I2C错误处理启动计数
reg [7:0] i2c_error_begin_count;
//I2C错误处理完成计数
reg [7:0] i2c_error_end_count;//i2c控制命令
reg [7:0] i2c_ctrl_cmd;
//i2c控制器启动信号
reg i2c_ctrl_start;
//i2c控制器空闲信号
wire i2c_ctrl_idle;
//i2c控制器完成信号
wire i2c_ctrl_done;//I2C总线发送的数据
reg [7:0] i2c_tx_data;
//数据发送完成后从设备返回的应答
wire i2c_tx_ack;//I2C总线接收到的数据
wire [7:0] i2c_rx_data;
//数据接收完成后发送给从设备的应答
reg i2c_rx_ack;//状态跳转
always @(posedge sys_clk) beginif(!sys_rst_n)current_state <= IDLE_STATE;elsecurrent_state <= next_state;
end//根据当前状态确定下一刻状态
always @(*) begincase(current_state)IDLE_STATE: beginif((state_done == 1'b0) && (eeprom_start == 1'b1) && (eeprom_cmd == READ_CMD))next_state = READ_STATE;else if((state_done == 1'b0) && (eeprom_start == 1'b1) && (eeprom_cmd == WRITE_CMD))next_state = WRITE_STATE;elsenext_state = IDLE_STATE;endREAD_STATE: beginif(state_done == 1'b1)next_state = IDLE_STATE;elsenext_state = READ_STATE;endWRITE_STATE: beginif(state_done == 1'b1)next_state = IDLE_STATE;elsenext_state = WRITE_STATE;enddefault:next_state = IDLE_STATE;endcase
end//空闲标志输出
assign eeprom_idle = ((current_state == IDLE_STATE) && (state_done == 1'b0)) ? 1'b1 : 1'b0;//控制I2C传输
always @(posedge sys_clk) beginif(!sys_rst_n) begini2c_ctrl_cmd <= 8'b0;i2c_ctrl_start <= 1'b0;i2c_tx_data <= 8'b0;i2c_rx_ack <= 1'b0;endelse begincase(current_state)IDLE_STATE: begini2c_ctrl_cmd <= 8'b0;i2c_ctrl_start <= 1'b0;i2c_tx_data <= 8'b0;i2c_rx_ack <= 1'b0;endREAD_STATE: beginif(state_done == 1'b0) beginif((i2c_tx_ack == 1'b0) && (eeprom_error_flag == 1'b0)) begin//发送状态回复ACKif((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin//产生开始信号if(i2c_opt_begin_count == 0) begini2c_ctrl_cmd <= GEN_STA_CMD;i2c_ctrl_start <= 1'b1;end//发送设备地址+写标志if(i2c_opt_begin_count == 1) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= {slave_addr, 1'b0};i2c_ctrl_start <= 1'b1;end//发送内存地址if(EEPROM_MEM_ADDR_BYTES == 2) begin//发送内存地址高字节if(i2c_opt_begin_count == 2) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= mem_addr[15:8];i2c_ctrl_start <= 1'b1;end//发送内存地址低字节if(i2c_opt_begin_count == 3) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= mem_addr[7:0];i2c_ctrl_start <= 1'b1;endendelse begin//发送内存地址if(i2c_opt_begin_count == 2) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= mem_addr[7:0];i2c_ctrl_start <= 1'b1;endend//产生停止信号if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 2)) begini2c_ctrl_cmd <= GEN_STO_CMD;i2c_ctrl_start <= 1'b1;end//再次产生开始信号if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 3)) begini2c_ctrl_cmd <= GEN_STA_CMD;i2c_ctrl_start <= 1'b1;end//发送设备地址+读标志if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 4)) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= {slave_addr, 1'b1};i2c_ctrl_start <= 1'b1;end//读数据if((i2c_opt_begin_count >= (EEPROM_MEM_ADDR_BYTES + 5)) && (i2c_opt_begin_count < (EEPROM_MEM_ADDR_BYTES + 5 + rd_data_len))) begini2c_ctrl_cmd <= RD_DATA_CMD;//控制输出给从机的应答信号,读取最后byte时输出NAKif(i2c_opt_begin_count < (EEPROM_MEM_ADDR_BYTES + 5 + rd_data_len - 1))i2c_rx_ack <= 1'b0;elsei2c_rx_ack <= 1'b1;i2c_ctrl_start <= 1'b1;end//产生停止信号if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 5 + rd_data_len)) begini2c_ctrl_cmd <= GEN_STO_CMD;i2c_ctrl_start <= 1'b1;endendelse if(i2c_ctrl_idle == 1'b0)i2c_ctrl_start <= 1'b0;endelse begin//发送状态回复NCKif((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin//产生停止信号if(i2c_error_begin_count == 0) begini2c_ctrl_cmd <= GEN_STO_CMD;i2c_ctrl_start <= 1'b1;endendelse if(i2c_ctrl_idle == 1'b0)i2c_ctrl_start <= 1'b0;endendendWRITE_STATE: beginif(state_done == 1'b0) beginif((i2c_tx_ack == 1'b0) && (eeprom_error_flag == 1'b0)) begin//发送状态回复ACKif((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin//产生开始信号if(i2c_opt_begin_count == 0) begini2c_ctrl_cmd <= GEN_STA_CMD;i2c_ctrl_start <= 1'b1;end//发送设备地址+写标志if(i2c_opt_begin_count == 1) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= {slave_addr, 1'b0};i2c_ctrl_start <= 1'b1;end//发送内存地址if(EEPROM_MEM_ADDR_BYTES == 2) begin//发送内存地址高字节if(i2c_opt_begin_count == 2) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= mem_addr[15:8];i2c_ctrl_start <= 1'b1;end//发送内存地址低字节if(i2c_opt_begin_count == 3) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= mem_addr[7:0];i2c_ctrl_start <= 1'b1;endendelse begin//发送内存地址if(i2c_opt_begin_count == 2) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= mem_addr[7:0];i2c_ctrl_start <= 1'b1;endend//写数据if((i2c_opt_begin_count >= (EEPROM_MEM_ADDR_BYTES + 2)) && (i2c_opt_begin_count < (EEPROM_MEM_ADDR_BYTES + 2 + wr_data_len))) begini2c_ctrl_cmd <= WR_DATA_CMD;i2c_tx_data <= wr_data;i2c_ctrl_start <= 1'b1;end//产生停止信号if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 2 + wr_data_len)) begini2c_ctrl_cmd <= GEN_STO_CMD;i2c_ctrl_start <= 1'b1;endendelse if(i2c_ctrl_idle == 1'b0)i2c_ctrl_start <= 1'b0;endelse begin//发送状态回复NCKif((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin//产生停止信号if(i2c_error_begin_count == 0) begini2c_ctrl_cmd <= GEN_STO_CMD;i2c_ctrl_start <= 1'b1;endendelse if(i2c_ctrl_idle == 1'b0)i2c_ctrl_start <= 1'b0;endendenddefault: begini2c_ctrl_cmd <= 8'b0;i2c_ctrl_start <= 1'b0;i2c_tx_data <= 8'b0;i2c_rx_ack <= 1'b0;endendcaseend
end//数据请求输出
always @(posedge sys_clk)beginif(!sys_rst_n)wr_data_req <= 1'b0;else if((current_state == WRITE_STATE) && (i2c_tx_ack == 1'b0) && (i2c_opt_begin_count >= (EEPROM_MEM_ADDR_BYTES + 1)) && (i2c_opt_begin_count < (EEPROM_MEM_ADDR_BYTES + 1 + wr_data_len))) beginif((i2c_ctrl_idle == 1'b0) && (i2c_ctrl_start == 1'b1))wr_data_req <= 1'b1;elsewr_data_req <= 1'b0;endelsewr_data_req <= 1'b0;
end//输出读取到的数据
assign rd_data = i2c_rx_data;
assign rd_data_flag = ((current_state == READ_STATE) && (i2c_opt_end_count >= (EEPROM_MEM_ADDR_BYTES + 5)) && (i2c_opt_end_count < (EEPROM_MEM_ADDR_BYTES + 5 + rd_data_len))) ? i2c_ctrl_done : 1'b0;//错误检测
always @(posedge sys_clk) beginif(!sys_rst_n) begineeprom_error_flag <= 1'b0;endelse if(current_state != IDLE_STATE) begin//从机响应NAKif(i2c_tx_ack == 1'b1)eeprom_error_flag <= 1'b1;endelseeeprom_error_flag <= 1'b0;
end//I2C操作启动计数
always @(posedge sys_clk) beginif(!sys_rst_n) begini2c_opt_begin_count <= 0;i2c_error_begin_count <= 0;endelse if(current_state != IDLE_STATE) beginif(eeprom_error_flag == 1'b0) beginif((i2c_ctrl_idle == 1'b0) && (i2c_ctrl_start == 1'b1))i2c_opt_begin_count <= i2c_opt_begin_count + 1;endelse beginif((i2c_ctrl_idle == 1'b0) && (i2c_ctrl_start == 1'b1))i2c_error_begin_count <= i2c_error_begin_count + 1;endendelse if(current_state == IDLE_STATE) begini2c_opt_begin_count <= 0;i2c_error_begin_count <= 0;end
end//I2C操作完成计数
//I2C错误处理完成计数
always @(posedge sys_clk) beginif(!sys_rst_n) begini2c_opt_end_count <= 0;i2c_error_end_count <= 0;endelse if(current_state != IDLE_STATE) beginif(eeprom_error_flag == 1'b0) beginif(i2c_ctrl_done == 1'b1)i2c_opt_end_count <= i2c_opt_end_count + 1;endelse beginif(i2c_ctrl_done == 1'b1)i2c_error_end_count <= i2c_error_end_count + 1;endendelse if(current_state == IDLE_STATE) begini2c_opt_end_count <= 0;i2c_error_end_count <= 0;end
end//状态结束检测
always @(posedge sys_clk) beginif(!sys_rst_n)state_done <= 1'b0;else begincase(current_state)IDLE_STATE: beginstate_done <= 1'b0;endREAD_STATE: begin//读存储器操作完成或出错if(i2c_opt_end_count == (EEPROM_MEM_ADDR_BYTES + 6 + rd_data_len) || (i2c_error_end_count == 1))state_done <= 1'b1;endWRITE_STATE: begin//写存储器操作完成或出错if(i2c_opt_end_count == (EEPROM_MEM_ADDR_BYTES + 3 + wr_data_len) || (i2c_error_end_count == 1))state_done <= 1'b1;enddefault: beginstate_done <= 1'b0;endendcaseend
end//例化i2c_driver模块
i2c_driver #(.I2C_CLK_PERIOD(I2C_CLK_PERIOD)
)
tb_i2c_driver_inst0 (.sys_rst_n(sys_rst_n),.sys_clk(sys_clk),.ctrl_cmd(i2c_ctrl_cmd),.ctrl_start(i2c_ctrl_start),.ctrl_idle(i2c_ctrl_idle),.ctrl_done(i2c_ctrl_done),.tx_data(i2c_tx_data),.tx_ack(i2c_tx_ack),.rx_data(i2c_rx_data),.rx_ack(i2c_rx_ack),.i2c_scl(i2c_scl),.i2c_sda_o(i2c_sda_o),.i2c_sda_i(i2c_sda_i),.i2c_sda_t(i2c_sda_t)
);endmodule
EEPROM 读写测试模块
module eeprom_rw_test #(parameter I2C_CLK_PERIOD = 1250, //I2C时钟周期,以系统时钟为参考parameter ALARM_LED_PERIOD = 25'd25_000_000,parameter WAIT_TIME = 250000
)
(input sys_rst_n, //系统复位input sys_clk, //系统时钟output i2c_scl, //i2c时钟输出inout i2c_sda, //i2c数据output reg alarm_led //状态指示灯
);//EEPROM操作命令
localparam READ_CMD = 8'h02; //读命令
localparam WRITE_CMD = 8'h04; //写命令//状态机的状态
localparam IDLE_STATE = 8'h01; //空闲状态
localparam READ_STATE = 8'h02; //读状态
localparam WAIT_STATE = 8'h04; //等待状态
localparam WRITE_STATE = 8'h08; //写状态//错误指示
reg error_flag;
//警示LED闪烁计数器
reg [31:0] led_count;//写入计数
reg [7:0] write_count;
//读取计数
reg [7:0] read_count;//等待写入计时计数器
reg [31:0] wait_count;//当前状态
reg [7:0] current_state;
//下一刻的状态
reg [7:0] next_state;
//对应状态结束标志,应切换到下一个状态,一个bit对应一个状态
reg [7:0] state_done;
//状态启动标志
reg [7:0] state_start;//启动信号
reg eeprom_start;
//操作命令
reg [7:0] eeprom_cmd;
//操作的存储器地址
reg [15:0] mem_addr;//写入长度
reg [7:0] wr_data_len;
//需要写入的数据
reg [7:0] wr_data;
//写入数据请求
wire wr_data_req;//读取长度
reg [7:0] rd_data_len;
//读取到的数据
wire [7:0] rd_data;
//读取输出标志
wire rd_data_flag;//空闲标志
wire eeprom_idle;
//EEPROM错误标志
wire eeprom_error_flag;//根据错误标志控制led闪烁或常亮
//操作未完成熄灭
//操作完成常亮
//发生错误时闪烁
always @(posedge sys_clk) beginif(!sys_rst_n) beginalarm_led <= 1'b0;led_count <= 32'b0;endelse if(error_flag == 1'b1) beginif(led_count >= (ALARM_LED_PERIOD - 1)) beginalarm_led <= ~alarm_led;led_count <= 32'b0;endelseled_count <= led_count + 32'b1;endelse if((current_state == IDLE_STATE) && (state_done != 8'h0)) beginalarm_led <= 1'b1;led_count <= 32'b0;endelse begin alarm_led <= 1'b0;led_count <= 32'b0;end
end//状态跳转
always @(posedge sys_clk)beginif(!sys_rst_n)current_state <= IDLE_STATE;elsecurrent_state <= next_state;
end//根据当前状态确定下一刻状态
always @(*)begincase(current_state)IDLE_STATE: beginif((state_done == 16'h0) && (error_flag == 1'b0))next_state = WRITE_STATE;elsenext_state = IDLE_STATE;endWRITE_STATE: beginif(error_flag == 1'b1)next_state = IDLE_STATE;else if(state_done & WRITE_STATE)next_state = WAIT_STATE;elsenext_state = WRITE_STATE;endWAIT_STATE: beginif(error_flag == 1'b1)next_state = IDLE_STATE;else if(state_done & WAIT_STATE)next_state = READ_STATE;elsenext_state = WAIT_STATE;endREAD_STATE: beginif(error_flag == 1'b1)next_state = IDLE_STATE;else if(state_done & READ_STATE)next_state = IDLE_STATE;elsenext_state = READ_STATE;enddefault:next_state = IDLE_STATE;endcase
end//写入等待过程计时
always @(posedge sys_clk)beginif(!sys_rst_n) wait_count <= 0;else beginif(current_state == WAIT_STATE) beginif(wait_count < (WAIT_TIME - 1))wait_count <= wait_count + 1;endelsewait_count <= 0;end
end//控制EEPROM读写
always @(posedge sys_clk)beginif(!sys_rst_n) begineeprom_start <= 1'b0;eeprom_cmd <= 8'b0;mem_addr <= 16'b0;wr_data_len <= 8'b0;rd_data_len <= 8'b0;state_done <= 8'h0;state_start <= 8'h0;endelse begincase(current_state)WRITE_STATE: begin//启动写操作if((eeprom_idle == 1'b1) && (!(state_start & WRITE_STATE)) && (eeprom_start == 1'b0)) begineeprom_start <= 1'b1;eeprom_cmd <= WRITE_CMD;mem_addr <= 16'h0000;wr_data_len <= 8'd8;state_start <= state_start | WRITE_STATE;end//写操作完成if((eeprom_idle == 1'b1) && (state_start & WRITE_STATE) && (eeprom_start == 1'b0)) beginif(!(state_done & WRITE_STATE))state_done <= state_done | WRITE_STATE;end//操作已经启动,复位启动标志if((eeprom_idle == 1'b0) && (eeprom_start == 1'b1))eeprom_start <= 1'b0;endWAIT_STATE: begin//等待EEPROM写入结束if(wait_count >= (WAIT_TIME - 1))state_done <= state_done | WAIT_STATE;endREAD_STATE: begin//启动读flash操作if((eeprom_idle == 1'b1) && (!(state_start & READ_STATE)) && (eeprom_start == 1'b0)) begineeprom_start <= 1'b1;eeprom_cmd <= READ_CMD;mem_addr <= 24'h0000;rd_data_len <= 8'd8;state_start <= state_start | READ_STATE;end//读操作完成if((eeprom_idle == 1'b1) && (state_start & READ_STATE) && (eeprom_start == 1'b0)) beginif(!(state_done & READ_STATE))state_done <= state_done | READ_STATE;end//操作已经启动,复位启动标志if((eeprom_idle == 1'b0) && (eeprom_start == 1'b1))eeprom_start <= 1'b0;enddefault: begineeprom_start <= 1'b0;eeprom_cmd <= 8'b0;mem_addr <= 16'b0;endendcaseend
end//生成写入的数据
always @(posedge sys_clk)beginif(!sys_rst_n) beginwr_data <= 8'h0;write_count <= 8'h0;endelse if(current_state == WRITE_STATE) beginif(wr_data_req == 1'b1) begin//生成写入数据wr_data <= write_count[7:0] + 1;//写入计数write_count <= write_count + 8'h1;endendelse beginwr_data <= 0;write_count <= 0;end
end//处理读取的数据
//处理EEPROM错误
always @(posedge sys_clk)beginif(!sys_rst_n) beginerror_flag <= 1'b0;read_count <= 8'h0;endelse begin//处理EEPROM错误if(eeprom_error_flag == 1'b1)error_flag <= 1'b1;//处理读取的数据if(current_state == READ_STATE) beginif(rd_data_flag == 1'b1) begin//检查接收的数据if(rd_data != (read_count[7:0] + 1))error_flag <= 1'b1;//接收计数read_count <= read_count + 8'h1;endendend
end//用IOBUF将SDA合并
//对于inout类型的端口最好显示的调用IOBUF硬核
IOBUF u_IOBUF_inst0(.IO(i2c_sda), //连接到外部IO引脚.T(i2c_sda_t), //输入输出控制,0输出(将I输出到IO),1输入(将IO设置为高阻态,此时O为IO输入电平).I(i2c_sda_o), //IOBUF输入,应接用户逻辑输出.O(i2c_sda_i) //IOBUF输出,应接用户逻辑输入
);//例化eeprom_driver
eeprom_driver #(.I2C_CLK_PERIOD(1250),.EEPROM_MEM_ADDR_BYTES(2)
)
tb_eeprom_driver_inst0(.sys_rst_n(sys_rst_n),.sys_clk(sys_clk),.eeprom_start(eeprom_start),.eeprom_cmd(eeprom_cmd),.slave_addr(7'b1010000),.mem_addr(mem_addr),.wr_data_len(wr_data_len),.wr_data(wr_data),.wr_data_req(wr_data_req),.rd_data_len(rd_data_len),.rd_data(rd_data),.rd_data_flag(rd_data_flag),.eeprom_idle(eeprom_idle),.eeprom_error_flag(eeprom_error_flag),.i2c_scl(i2c_scl),.i2c_sda_o(i2c_sda_o),.i2c_sda_i(i2c_sda_i),.i2c_sda_t(i2c_sda_t)
);endmodule
仿真激励文件
在仿真过程中需要用到AT24C64的仿真激励模型,可以通过AT24C64仿真激励模型下载地址进行下载,完成的仿真激励文件如下:
`timescale 1ns / 1psmodule tb_eeprom_rw_test( );reg sys_rst_n; //系统复位
reg sys_clk; //系统时钟wire i2c_scl; //i2c时钟输出
wire i2c_sda; //i2c数据wire alarm_led; //状态指示灯//产生仿真信号序列
initial beginsys_rst_n = 1'b0;sys_clk = 1'b0;#2000sys_rst_n = 1'b1;
end//产生时钟
always #10 sys_clk = ~sys_clk;//将SDA数据线上拉
pullup(i2c_sda);//例化eeprom_rw_test
eeprom_rw_test #(.I2C_CLK_PERIOD(1250),.ALARM_LED_PERIOD(25'd25_000_000)
)
tb_eeprom_rw_test_inst0(.sys_rst_n(sys_rst_n),.sys_clk(sys_clk),.i2c_scl(i2c_scl),.i2c_sda(i2c_sda),.alarm_led(alarm_led)
);//例化e2prom仿真模型
M24LC64 u_M24LC64_inst0(.A0(0),.A1(0),.A2(0),.WP(0),.SDA(i2c_sda),.SCL(i2c_scl),.RESET(~sys_rst_n)
);endmodule
引脚约束
#时序约束
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]#IO 管脚约束
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN F13 IOSTANDARD LVCMOS33} [get_ports i2c_scl]
set_property -dict {PACKAGE_PIN A19 IOSTANDARD LVCMOS33} [get_ports i2c_sda]
set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS15} [get_ports alarm_led]