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)必须保持稳定,在时钟(SCL)为低电平的时候,数据总线(SDA)可以改变。
- 在时钟(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) begin
if(!sys_rst_n) begin
prev_state <= IDLE_STATE;
current_state <= IDLE_STATE;
end
else begin
prev_state <= current_state;
current_state <= next_state;
end
end
//根据当前状态确定下一刻状态
always @(*) begin
case(current_state)
IDLE_STATE: begin
if((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;
else
next_state = IDLE_STATE;
end
GEN_STA_STATE: begin
if(state_done == 1'b1)
next_state = IDLE_STATE;
else
next_state = GEN_STA_STATE;
end
WR_DATA_STATE: begin
if(state_done == 1'b1)
next_state = IDLE_STATE;
else
next_state = WR_DATA_STATE;
end
RD_DATA_STATE: begin
if(state_done == 1'b1)
next_state = IDLE_STATE;
else
next_state = RD_DATA_STATE;
end
GEN_STO_STATE: begin
if(state_done == 1'b1)
next_state = IDLE_STATE;
else
next_state = GEN_STO_STATE;
end
default: begin
next_state = IDLE_STATE;
end
endcase
end
//进行时钟周期计数
always @(posedge sys_clk) begin
if(!sys_rst_n)
clk_period_count <= 32'b0;
else begin
case(current_state)
IDLE_STATE: begin
clk_period_count <= 32'b0;
end
GEN_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) begin
if(clk_period_count < (I2C_CLK_PERIOD - 1))
clk_period_count <= clk_period_count + 32'b1;
else
clk_period_count <= 32'b0;
end
end
default: begin
clk_period_count <= 32'b0;
end
endcase
end
end
//进行传输bit计数
always @(posedge sys_clk) begin
if(!sys_rst_n)
bit_cnt <= 8'b0;
else begin
case(current_state)
IDLE_STATE: begin
bit_cnt <= 8'b0;
end
GEN_STA_STATE, GEN_STO_STATE: begin
//产生开始信号和停止信号时第一个时钟周期用于将IO设置为初始状态,低二个时钟周期输出开始或结束信号
if(clk_period_count == (I2C_CLK_PERIOD - 1)) begin
if(bit_cnt < (2 - 1))
bit_cnt <= bit_cnt + 8'b1;
end
end
WR_DATA_STATE, RD_DATA_STATE: begin
//发送数据,8bit数据加1bit应答
//接收数据,8bit数据加1bit应答
if(clk_period_count == (I2C_CLK_PERIOD - 1)) begin
if(bit_cnt < (9 - 1))
bit_cnt <= bit_cnt + 8'b1;
end
end
default: begin
bit_cnt <= 8'b0;
end
endcase
end
end
//锁存需要发送的数据
always @(posedge sys_clk) begin
if(!sys_rst_n) begin
tx_data_reg <= 8'b0;
rx_ack_reg <= 1'b0;
end
else if(current_state == IDLE_STATE) begin
//暂存需要发送的数据
if(next_state == WR_DATA_STATE)
tx_data_reg <= tx_data;
//暂存需要发送的ACK
if(next_state == RD_DATA_STATE)
rx_ack_reg <= rx_ack;
end
end
//控制数据传输
always @(posedge sys_clk) begin
if(!sys_rst_n) begin
bus_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;
end
else begin
case(current_state)
IDLE_STATE: begin
if(bus_idle_flag == 1'b1) begin
i2c_scl <= 1'b1;
i2c_sda_o <= 1'b1;
i2c_sda_t <= 1'b1;
tx_ack_reg <= 1'b0;
rx_data_reg <= 8'b0;
end
state_done <= 1'b0;
end
GEN_STA_STATE: begin
if(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;
end
else 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;
else
i2c_sda_o <= 1'b0;
end
//启动信号结束
if((bit_cnt == (2 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1)))
state_done <= 1'b1;
end
end
WR_DATA_STATE: begin
if(state_done == 1'b0) begin
if(bus_idle_flag == 1'b0) begin
if(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;
else
i2c_scl <= 1'b1;
end
else begin
//读从机应答
//SDA为输入
i2c_sda_t <= 1'b1;
//前半个周期SCL为低,后半个周期SCL为高
if(clk_period_count <= (I2C_CLK_PERIOD_HALF - 1))
i2c_scl <= 1'b0;
else
i2c_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;
end
else
state_done <= 1'b1;
end
end
RD_DATA_STATE: begin
if(state_done == 1'b0) begin
if(bus_idle_flag == 1'b0) begin
if(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;
else
i2c_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;
end
else 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;
else
i2c_scl <= 1'b1;
end
//读操作结束
if((bit_cnt == (9 - 1)) && (clk_period_count == (I2C_CLK_PERIOD - 1)))
state_done <= 1'b1;
end
else
state_done <= 1'b1;
end
end
GEN_STO_STATE: begin
if(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;
end
else 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;
else
i2c_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;
end
end
default: begin
if(bus_idle_flag == 1'b1) begin
i2c_scl <= 1'b1;
i2c_sda_o <= 1'b1;
i2c_sda_t <= 1'b1;
tx_ack_reg <= 1'b0;
rx_data_reg <= 8'b0;
end
state_done <= 1'b0;
end
endcase
end
end
//输出完成信号和数据
always @(posedge sys_clk) begin
if(!sys_rst_n) begin
ctrl_done <= 1'b0;
tx_ack <= 1'b0;
rx_data <= 8'b0;
end
else begin
case(current_state)
IDLE_STATE: begin
if(prev_state != IDLE_STATE) begin
ctrl_done <= 1'b1;
tx_ack <= tx_ack_reg;
rx_data <= rx_data_reg;
end
else
ctrl_done <= 1'b0;
end
default: begin
ctrl_done <= 1'b0;
end
endcase
end
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) begin
if(!sys_rst_n)
current_state <= IDLE_STATE;
else
current_state <= next_state;
end
//根据当前状态确定下一刻状态
always @(*) begin
case(current_state)
IDLE_STATE: begin
if((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;
else
next_state = IDLE_STATE;
end
READ_STATE: begin
if(state_done == 1'b1)
next_state = IDLE_STATE;
else
next_state = READ_STATE;
end
WRITE_STATE: begin
if(state_done == 1'b1)
next_state = IDLE_STATE;
else
next_state = WRITE_STATE;
end
default:
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) begin
if(!sys_rst_n) begin
i2c_ctrl_cmd <= 8'b0;
i2c_ctrl_start <= 1'b0;
i2c_tx_data <= 8'b0;
i2c_rx_ack <= 1'b0;
end
else begin
case(current_state)
IDLE_STATE: begin
i2c_ctrl_cmd <= 8'b0;
i2c_ctrl_start <= 1'b0;
i2c_tx_data <= 8'b0;
i2c_rx_ack <= 1'b0;
end
READ_STATE: begin
if(state_done == 1'b0) begin
if((i2c_tx_ack == 1'b0) && (eeprom_error_flag == 1'b0)) begin
//发送状态回复ACK
if((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin
//产生开始信号
if(i2c_opt_begin_count == 0) begin
i2c_ctrl_cmd <= GEN_STA_CMD;
i2c_ctrl_start <= 1'b1;
end
//发送设备地址+写标志
if(i2c_opt_begin_count == 1) begin
i2c_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) begin
i2c_ctrl_cmd <= WR_DATA_CMD;
i2c_tx_data <= mem_addr[15:8];
i2c_ctrl_start <= 1'b1;
end
//发送内存地址低字节
if(i2c_opt_begin_count == 3) begin
i2c_ctrl_cmd <= WR_DATA_CMD;
i2c_tx_data <= mem_addr[7:0];
i2c_ctrl_start <= 1'b1;
end
end
else begin
//发送内存地址
if(i2c_opt_begin_count == 2) begin
i2c_ctrl_cmd <= WR_DATA_CMD;
i2c_tx_data <= mem_addr[7:0];
i2c_ctrl_start <= 1'b1;
end
end
//产生停止信号
if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 2)) begin
i2c_ctrl_cmd <= GEN_STO_CMD;
i2c_ctrl_start <= 1'b1;
end
//再次产生开始信号
if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 3)) begin
i2c_ctrl_cmd <= GEN_STA_CMD;
i2c_ctrl_start <= 1'b1;
end
//发送设备地址+读标志
if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 4)) begin
i2c_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))) begin
i2c_ctrl_cmd <= RD_DATA_CMD;
//控制输出给从机的应答信号,读取最后byte时输出NAK
if(i2c_opt_begin_count < (EEPROM_MEM_ADDR_BYTES + 5 + rd_data_len - 1))
i2c_rx_ack <= 1'b0;
else
i2c_rx_ack <= 1'b1;
i2c_ctrl_start <= 1'b1;
end
//产生停止信号
if(i2c_opt_begin_count == (EEPROM_MEM_ADDR_BYTES + 5 + rd_data_len)) begin
i2c_ctrl_cmd <= GEN_STO_CMD;
i2c_ctrl_start <= 1'b1;
end
end
else if(i2c_ctrl_idle == 1'b0)
i2c_ctrl_start <= 1'b0;
end
else begin
//发送状态回复NCK
if((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin
//产生停止信号
if(i2c_error_begin_count == 0) begin
i2c_ctrl_cmd <= GEN_STO_CMD;
i2c_ctrl_start <= 1'b1;
end
end
else if(i2c_ctrl_idle == 1'b0)
i2c_ctrl_start <= 1'b0;
end
end
end
WRITE_STATE: begin
if(state_done == 1'b0) begin
if((i2c_tx_ack == 1'b0) && (eeprom_error_flag == 1'b0)) begin
//发送状态回复ACK
if((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin
//产生开始信号
if(i2c_opt_begin_count == 0) begin
i2c_ctrl_cmd <= GEN_STA_CMD;
i2c_ctrl_start <= 1'b1;
end
//发送设备地址+写标志
if(i2c_opt_begin_count == 1) begin
i2c_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) begin
i2c_ctrl_cmd <= WR_DATA_CMD;
i2c_tx_data <= mem_addr[15:8];
i2c_ctrl_start <= 1'b1;
end
//发送内存地址低字节
if(i2c_opt_begin_count == 3) begin
i2c_ctrl_cmd <= WR_DATA_CMD;
i2c_tx_data <= mem_addr[7:0];
i2c_ctrl_start <= 1'b1;
end
end
else begin
//发送内存地址
if(i2c_opt_begin_count == 2) begin
i2c_ctrl_cmd <= WR_DATA_CMD;
i2c_tx_data <= mem_addr[7:0];
i2c_ctrl_start <= 1'b1;
end
end
//写数据
if((i2c_opt_begin_count >= (EEPROM_MEM_ADDR_BYTES + 2)) && (i2c_opt_begin_count < (EEPROM_MEM_ADDR_BYTES + 2 + wr_data_len))) begin
i2c_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)) begin
i2c_ctrl_cmd <= GEN_STO_CMD;
i2c_ctrl_start <= 1'b1;
end
end
else if(i2c_ctrl_idle == 1'b0)
i2c_ctrl_start <= 1'b0;
end
else begin
//发送状态回复NCK
if((i2c_ctrl_idle == 1'b1) && (i2c_ctrl_start == 1'b0)) begin
//产生停止信号
if(i2c_error_begin_count == 0) begin
i2c_ctrl_cmd <= GEN_STO_CMD;
i2c_ctrl_start <= 1'b1;
end
end
else if(i2c_ctrl_idle == 1'b0)
i2c_ctrl_start <= 1'b0;
end
end
end
default: begin
i2c_ctrl_cmd <= 8'b0;
i2c_ctrl_start <= 1'b0;
i2c_tx_data <= 8'b0;
i2c_rx_ack <= 1'b0;
end
endcase
end
end
//数据请求输出
always @(posedge sys_clk)begin
if(!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))) begin
if((i2c_ctrl_idle == 1'b0) && (i2c_ctrl_start == 1'b1))
wr_data_req <= 1'b1;
else
wr_data_req <= 1'b0;
end
else
wr_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) begin
if(!sys_rst_n) begin
eeprom_error_flag <= 1'b0;
end
else if(current_state != IDLE_STATE) begin
//从机响应NAK
if(i2c_tx_ack == 1'b1)
eeprom_error_flag <= 1'b1;
end
else
eeprom_error_flag <= 1'b0;
end
//I2C操作启动计数
always @(posedge sys_clk) begin
if(!sys_rst_n) begin
i2c_opt_begin_count <= 0;
i2c_error_begin_count <= 0;
end
else if(current_state != IDLE_STATE) begin
if(eeprom_error_flag == 1'b0) begin
if((i2c_ctrl_idle == 1'b0) && (i2c_ctrl_start == 1'b1))
i2c_opt_begin_count <= i2c_opt_begin_count + 1;
end
else begin
if((i2c_ctrl_idle == 1'b0) && (i2c_ctrl_start == 1'b1))
i2c_error_begin_count <= i2c_error_begin_count + 1;
end
end
else if(current_state == IDLE_STATE) begin
i2c_opt_begin_count <= 0;
i2c_error_begin_count <= 0;
end
end
//I2C操作完成计数
//I2C错误处理完成计数
always @(posedge sys_clk) begin
if(!sys_rst_n) begin
i2c_opt_end_count <= 0;
i2c_error_end_count <= 0;
end
else if(current_state != IDLE_STATE) begin
if(eeprom_error_flag == 1'b0) begin
if(i2c_ctrl_done == 1'b1)
i2c_opt_end_count <= i2c_opt_end_count + 1;
end
else begin
if(i2c_ctrl_done == 1'b1)
i2c_error_end_count <= i2c_error_end_count + 1;
end
end
else if(current_state == IDLE_STATE) begin
i2c_opt_end_count <= 0;
i2c_error_end_count <= 0;
end
end
//状态结束检测
always @(posedge sys_clk) begin
if(!sys_rst_n)
state_done <= 1'b0;
else begin
case(current_state)
IDLE_STATE: begin
state_done <= 1'b0;
end
READ_STATE: begin
//读存储器操作完成或出错
if(i2c_opt_end_count == (EEPROM_MEM_ADDR_BYTES + 6 + rd_data_len) || (i2c_error_end_count == 1))
state_done <= 1'b1;
end
WRITE_STATE: begin
//写存储器操作完成或出错
if(i2c_opt_end_count == (EEPROM_MEM_ADDR_BYTES + 3 + wr_data_len) || (i2c_error_end_count == 1))
state_done <= 1'b1;
end
default: begin
state_done <= 1'b0;
end
endcase
end
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) begin
if(!sys_rst_n) begin
alarm_led <= 1'b0;
led_count <= 32'b0;
end
else if(error_flag == 1'b1) begin
if(led_count >= (ALARM_LED_PERIOD - 1)) begin
alarm_led <= ~alarm_led;
led_count <= 32'b0;
end
else
led_count <= led_count + 32'b1;
end
else if((current_state == IDLE_STATE) && (state_done != 8'h0)) begin
alarm_led <= 1'b1;
led_count <= 32'b0;
end
else begin
alarm_led <= 1'b0;
led_count <= 32'b0;
end
end
//状态跳转
always @(posedge sys_clk)begin
if(!sys_rst_n)
current_state <= IDLE_STATE;
else
current_state <= next_state;
end
//根据当前状态确定下一刻状态
always @(*)begin
case(current_state)
IDLE_STATE: begin
if((state_done == 16'h0) && (error_flag == 1'b0))
next_state = WRITE_STATE;
else
next_state = IDLE_STATE;
end
WRITE_STATE: begin
if(error_flag == 1'b1)
next_state = IDLE_STATE;
else if(state_done & WRITE_STATE)
next_state = WAIT_STATE;
else
next_state = WRITE_STATE;
end
WAIT_STATE: begin
if(error_flag == 1'b1)
next_state = IDLE_STATE;
else if(state_done & WAIT_STATE)
next_state = READ_STATE;
else
next_state = WAIT_STATE;
end
READ_STATE: begin
if(error_flag == 1'b1)
next_state = IDLE_STATE;
else if(state_done & READ_STATE)
next_state = IDLE_STATE;
else
next_state = READ_STATE;
end
default:
next_state = IDLE_STATE;
endcase
end
//写入等待过程计时
always @(posedge sys_clk)begin
if(!sys_rst_n)
wait_count <= 0;
else begin
if(current_state == WAIT_STATE) begin
if(wait_count < (WAIT_TIME - 1))
wait_count <= wait_count + 1;
end
else
wait_count <= 0;
end
end
//控制EEPROM读写
always @(posedge sys_clk)begin
if(!sys_rst_n) begin
eeprom_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;
end
else begin
case(current_state)
WRITE_STATE: begin
//启动写操作
if((eeprom_idle == 1'b1) && (!(state_start & WRITE_STATE)) && (eeprom_start == 1'b0)) begin
eeprom_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)) begin
if(!(state_done & WRITE_STATE))
state_done <= state_done | WRITE_STATE;
end
//操作已经启动,复位启动标志
if((eeprom_idle == 1'b0) && (eeprom_start == 1'b1))
eeprom_start <= 1'b0;
end
WAIT_STATE: begin
//等待EEPROM写入结束
if(wait_count >= (WAIT_TIME - 1))
state_done <= state_done | WAIT_STATE;
end
READ_STATE: begin
//启动读flash操作
if((eeprom_idle == 1'b1) && (!(state_start & READ_STATE)) && (eeprom_start == 1'b0)) begin
eeprom_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)) begin
if(!(state_done & READ_STATE))
state_done <= state_done | READ_STATE;
end
//操作已经启动,复位启动标志
if((eeprom_idle == 1'b0) && (eeprom_start == 1'b1))
eeprom_start <= 1'b0;
end
default: begin
eeprom_start <= 1'b0;
eeprom_cmd <= 8'b0;
mem_addr <= 16'b0;
end
endcase
end
end
//生成写入的数据
always @(posedge sys_clk)begin
if(!sys_rst_n) begin
wr_data <= 8'h0;
write_count <= 8'h0;
end
else if(current_state == WRITE_STATE) begin
if(wr_data_req == 1'b1) begin
//生成写入数据
wr_data <= write_count[7:0] + 1;
//写入计数
write_count <= write_count + 8'h1;
end
end
else begin
wr_data <= 0;
write_count <= 0;
end
end
//处理读取的数据
//处理EEPROM错误
always @(posedge sys_clk)begin
if(!sys_rst_n) begin
error_flag <= 1'b0;
read_count <= 8'h0;
end
else begin
//处理EEPROM错误
if(eeprom_error_flag == 1'b1)
error_flag <= 1'b1;
//处理读取的数据
if(current_state == READ_STATE) begin
if(rd_data_flag == 1'b1) begin
//检查接收的数据
if(rd_data != (read_count[7:0] + 1))
error_flag <= 1'b1;
//接收计数
read_count <= read_count + 8'h1;
end
end
end
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 / 1ps
module tb_eeprom_rw_test( );
reg sys_rst_n; //系统复位
reg sys_clk; //系统时钟
wire i2c_scl; //i2c时钟输出
wire i2c_sda; //i2c数据
wire alarm_led; //状态指示灯
//产生仿真信号序列
initial begin
sys_rst_n = 1'b0;
sys_clk = 1'b0;
#2000
sys_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]