18 EEPROM读写

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 协议规定

  1. 在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,所以数据总线(SDA)在时钟(SCL)为低电平的时候才能改变。
    在这里插入图片描述
  2. 在时钟(SCL)为高电平的时候,数据总线(SDA)由高到低的跳变为总线起始信号
  3. 在时钟(SCL)为高电平的时候,数据总线(SDA)由低到高的跳变为总线停止信号
    在这里插入图片描述
  4. 当传输器件将 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 字节存储器地址器件单字节写时序图
在这里插入图片描述
单字节存储器地址写单字节数据过程:

  1. 主机设置 SDA 为输出;
  2. 主机发起起始信号;
  3. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  4. 主机设置 SDA 为三态门输入,读取从机应答信号;
  5. 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
  6. 主机设置 SDA 为三态门输入,读取从机应答信号;
  7. 读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据;
  8. 设置 SDA 为三态门输入,读取从机应答信号;
  9. 读取应答信号成功,主机产生 STOP 位,终止传输。
    双字节存储器地址写单字节数据过程:
  10. 主机设置 SDA 为输出;
  11. 主机发起起始信号;
  12. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  13. 主机设置 SDA 为三态门输入,读取从机应答信号;
  14. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
  15. 主机设置 SDA 为三态门输入,读取从机应答信号;
  16. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
  17. 设置 SDA 为三态门输入,读取从机应答信号;
  18. 读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据;
  19. 设置 SDA 为三态门输入,读取从机应答信号;
  20. 读取应答信号成功,主机产生 STOP 位,终止传输。

I2C 连续写时序

注意:
I2C 连续写时序仅部分器件支持

连续写是主机连续写多个字节数据到从机,这个和单字节写操作类似,连续多字节写操作也是分为 1 字节存储器地址器件和 2 字节存储器地址器件的写操作,下图分别是 1 字节存储器地址器件和 2 字节存储器地址器件的连续写时序图
在这里插入图片描述
单字节存储器地址器件连续多字节写数据过程:

  1. 主机设置 SDA 为输出;
  2. 主机发起起始信号;
  3. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  4. 主机设置 SDA 为三态门输入,读取从机应答信号;
  5. 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
  6. 主机设置 SDA 为三态门输入,读取从机应答信号;
  7. 读取应答信号成功,主机设置 SDA 为输出,传输待写入的第 1 个数据;
  8. 设置 SDA 为三态门输入,读取从机应答信号;
  9. 读取应答信号成功后,主机设置 SDA 为输出,传输待写入的下一个数据;
  10. 设置 SDA 为三态门输入,读取从机应答信号;n 个数据被写完,转到步骤 11,若数据未被写完,转到步骤 9;
  11. 读取应答信号成功,主机产生 STOP 位,终止传输。
    双字节存储器地址器件连续多字节写数据过程:
  12. 主机设置 SDA 为输出;
  13. 主机发起起始信号;
  14. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  15. 主机设置 SDA 为三态门输入,读取从机应答信号;
  16. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
  17. 主机设置 SDA 为三态门输入,读取从机应答信号;
  18. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
  19. 设置 SDA 为三态门输入,读取从机应答信号;
  20. 读取应答信号成功,主机设置 SDA 为输出,传输待写入的第 1 个数据;
  21. 设置 SDA 为三态门输入,读取从机应答信号;
  22. 读取应答信号成功后,主机设置 SDA 为输出,传输待写入的下一个数据;
  23. 设置 SDA 为三态门输入,读取从机应答信号;n 个数据被写完,转到步骤 13,若数据未被写完,转到步骤 11;
  24. 读取应答信号成功,主机产生 STOP 位,终止传输。

I2C 单字节读时序

单字节读操作分为 1 字节存储器地址器件单字节数据读操作和 2 字节存储器地址器件单字节数据读操作。下图分别为不同情况的时序图
在这里插入图片描述
单字节存储器地址读取单字节数据过程:

  1. 主机设置 SDA 为输出;
  2. 主机发起起始信号;
  3. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  4. 主机设置 SDA 为三态门输入,读取从机应答信号;
  5. 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
  6. 主机设置 SDA 为三态门输入,读取从机应答信号;
  7. 读取应答信号成功,主机发起起始信号;
  8. 主机传输器件地址字节,其中最低位为 1,表明为读操作;
  9. 设置 SDA 为三态门输入,读取从机应答信号;
  10. 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
  11. 产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
  12. 主机产生 STOP 位,终止传输。
    双字节存储器地址读取单字节数据过程:
  13. 主机设置 SDA 为输出;
  14. 主机发起起始信号;
  15. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  16. 主机设置 SDA 为三态门输入,读取从机应答信号;
  17. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
  18. 主机设置 SDA 为三态门输入,读取从机应答信号;
  19. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
  20. 设置 SDA 为三态门输入,读取从机应答信号;
  21. 读取应答信号成功,主机发起起始信号;
  22. 主机传输器件地址字节,其中最低位为 1,表明为读操作;
  23. 设置 SDA 为三态门输入,读取从机应答信号;
  24. 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
  25. 主机设置 SDA 输出,产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
  26. 主机产生 STOP 位,终止传输

I2C 连续读时序

连续读是主机连续从从机读取多个字节数据,这个和单字节读操作类似,连续多字节读操作也是分为 1 字节存储器地址器件和 2 字节存储器地址器件的读操作,下图分别为字节存储器地址器件和 2 字节存储器地址器件连续多字节读的时序图。
在这里插入图片描述
单字节存储器地址器件连续多字节读取数据过程:

  1. 主机设置 SDA 为输出;
  2. 主机发起起始信号;
  3. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  4. 主机设置 SDA 为三态门输入,读取从机应答信号;
  5. 读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
  6. 主机设置 SDA 为三态门输入,读取从机应答信号;
  7. 读取应答信号成功,主机发起起始信号;
  8. 主机传输器件地址字节,其中最低位为 1,表明为读操作;
  9. 设置 SDA 为三态门输入,读取从机应答信号;
  10. 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的第 1 个字节的数据;
  11. 主机设置 SDA 输出,发送一位应答信号;
  12. 设置 SDA 为三态门输入,读取 SDA 总线上的下一个字节的数据;若 n 个字节数据读完成,跳转到步骤 13,若数据未读完,跳转到步骤 11;
  13. 主机设置 SDA 输出,产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
  14. 主机产生 STOP 位,终止传输。
    双字节存储器地址器件连续多字节读取数据过程:
  15. 主机设置 SDA 为输出;
  16. 主机发起起始信号;
  17. 主机传输器件地址字节,其中最低位为 0,表明为写操作;
  18. 主机设置 SDA 为三态门输入,读取从机应答信号;
  19. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据高字节;
  20. 主机设置 SDA 为三态门输入,读取从机应答信号;
  21. 读取应答信号成功,主机设置 SDA 为输出,传输地址数据低字节;
  22. 设置 SDA 为三态门输入,读取从机应答信号;
  23. 读取应答信号成功,主机发起起始信号;
  24. 主机传输器件地址字节,其中最低位为 1,表明为读操作;
  25. 设置 SDA 为三态门输入,读取从机应答信号;
  26. 读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的第 1 个字节的数据;
  27. 主机设置 SDA 输出,发送一位应答信号;
  28. 设置 SDA 为三态门输入,读取 SDA 总线上的下一个字节的数据;若 n 个字节数据读完成,跳转到步骤 15,若数据未读完,跳转到步骤 13;
  29. 主机设置 SDA 输出,产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
  30. 主机产生 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]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/846434.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

32位与64位程序下函数调用的异同——计科学习中缺失的内容

前言 今天&#xff0c;通过一个有趣的案例&#xff0c;从反编译的角度看一下C语言中函数参数是如何传递的。 创建main.c文件&#xff0c;将下面实验代码拷贝到main.c文件中。 # main.c #include <stdio.h>int test(int a, int b, int c, int d, int e, int f, int g, …

Docker最新超详细版教程通俗易懂

文章目录 一、Docker 概述1. Docker 为什么出现2. Docker 的历史3. Docker 能做什么 二、Docker 安装1. Docker 的基本组成2. 安装 Docker3. 阿里云镜像加速4. 回顾 hello-world 流程5. 底层原理 三、Docker 的常用命令1. 帮助命令2. 镜像命令dokcer imagesdocker searchdocker…

解锁数据宝藏:高效查找算法揭秘

代码下载链接&#xff1a;https://gitee.com/flying-wolf-loves-learning/data-structure.git 目录 一、查找的原理 1.1 查找概念 1.2 查找方法 1.3平均查找长度 1.4顺序表的查找 1.5 顺序表的查找算法及分析 1.6 折半查找算法及分析 1.7 分块查找算法及分析 1.8 总结…

pytorch学习笔记5

transform 本质上作用是将图片通过transform这个这个工具箱获取想要的结果 tensor就是一个包含神经网络需要的一些理论基础的参数 from torch.utils.tensorboard import SummaryWriter from torchvision import transforms from PIL import Image #tensor数据类型 #通过tra…

1985-2020 年阿拉斯加和育空地区按植物功能类型划分的模型表层覆盖率

ABoVE: Modeled Top Cover by Plant Functional Type over Alaska and Yukon, 1985-2020 1985-2020 年阿拉斯加和育空地区按植物功能类型划分的模型表层覆盖率 简介 文件修订日期&#xff1a;2022-05-31 数据集版本: 1.1 本数据集包含阿拉斯加和育空地区北极和北方地区按…

DPDK基础组件二(igb_uio、kni、rcu)

The Linux driver implementer’s API guide — The Linux Kernel documentation 一、igb_uid驱动 参考博客:https://zhuanlan.zhihu.com/p/543217445 UIO(Userspace I/O)是运行在用户空间的I/O技术 代码位置:dpdk----/kernel/linux/igb_uio目录 igb_uio 是 dpdk 内部实…

学习数据分析思维的共鸣

在这篇文章中&#xff0c;我将分享自己在成长过程中对数据分析思维的领悟&#xff0c;从《数据分析思维-产品经理的成长笔记》这本书引发的共鸣&#xff0c;到数据分析在不同岗位的广泛应用&#xff0c;再到如何将学习与快乐联系起来&#xff0c;以及沟通在数据分析中的重要性。…

cocos入门4:项目目录结构

Cocos Creator 项目结构教程 Cocos Creator 是一个功能强大的游戏开发工具&#xff0c;它为开发者提供了直观易用的界面和强大的功能来快速创建游戏。在使用 Cocos Creator 开发游戏时&#xff0c;合理地组织项目结构对于项目的可维护性和扩展性至关重要。以下是一个关于如何设…

设计模式(十)结构型模式---享元模式(flyweight)

文章目录 享元模式简介结构UML图具体实现UML图代码实现 享元模式简介 享元模式&#xff08;fly weight pattern&#xff09;主要是通过共享对象来减少系统中对象的数量&#xff0c;其本质就是缓存共享对象&#xff0c;降低内存消耗。享元模式将需要重复使用的对象分为两个状态…

7-14 字节序(Endianness)---PTA实验C++

一、题目描述 “内存寻址的最小单位是字节”——明白。 “每个字节有唯一的编号&#xff0c;称为地址”——明白。 “C中int通常为四个字节”——了解。 “int x 1;最低字节是1还是0&#xff1f;——纳尼&#xff1f; 事实上&#xff0c;这里有点小小分歧&#xff1a; 多字…

IDEA 学习之 命令行太长问题

现象 Error running App Command line is too long. In order to reduce its length classpath file can be used. Would you like to enable classpath file mode for all run configurations of your project?解决办法 办法一 .idea\workspace.xml ——> <compone…

软件开发整体介绍

黑马程序员瑞吉外卖 文章目录 一、软件开发流程二、角色分工三、软件环境 一、软件开发流程 二、角色分工 三、软件环境

GraphQL(2):使用express和GraphQL编写helloworld

1 安装express、graphql以及express-graphql 在项目的目录下运行一下命令。 npm init -y npm install express graphql express-graphql -S 2 新建helloworld.js 代码如下&#xff1a; const express require(express); const {buildSchema} require(graphql); const grap…

leetcode146.LRU缓存,从算法题引入,全面学习LRU和链表哈希表知识

leetcode146. LRU 缓存 题目链接 请你设计并实现一个满足 LRU (最近最少使用) 缓存约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关…

【QT】父子按钮同时响应点击事件

QPushButton如何响应点击事件 QPushButton::event(QEvent *e) 。可以看到在QPushButton中的event函数中并没有鼠标点击相关的操作&#xff0c;那么我们去QAbstractButton::event中寻找 damn it!。依然没有那我们去QWidget::event中寻找 damn it! 只有mousePressEvent mouseR…

libcef.dll丢失的解决方法-多种libcef.dll亲测有效解决方法分享

libcef.dll是Chromium Embedded Framework (CEF)的核心动态链接库&#xff0c;它为开发者提供了一个将Chromium浏览器嵌入到本地桌面应用程序中的解决方案。这个库使得开发者能够利用Chromium的强大功能&#xff0c;如HTML5、CSS3、JavaScript等&#xff0c;来创建跨平台的应用…

罕见!史诗级“大堵船”

新加坡港口的停泊延误时间已延长至7天&#xff0c;积压的集装箱数量达到惊人的450000标准箱&#xff0c;远超新冠疫情暴发时期的数轮高点。业内认为&#xff0c;近期东南亚恶劣的天气情况加剧了该区域港口拥堵。 5月31日&#xff0c;上海航运交易所&#xff08;下称“航交所”…

重生奇迹MU召唤师如何学习狂暴术?

一、了解狂暴术的基本信息 狂暴术是一种非常强大的技能&#xff0c;可以让召唤师的攻击力和防御力大幅度提高&#xff0c;但同时也会增加自身的伤害。在使用狂暴术之前&#xff0c;召唤师需要仔细考虑自己的状态和对手的情况。 二、学习狂暴术的方法 1.通过任务学习 在游戏…

Docker安装与使用 --学习笔记

一、概述 Docker是什么? Docker是一种工具&#xff0c;类似于一个虚拟箱子&#xff0c;可以把软件和它运行所需要的环境打包放进这个箱子里。这样&#xff0c;无论这个箱子放到哪里&#xff0c;软件都能像在原来的地方一样运行&#xff0c;不会因为换了地方就出问题。 假设…

【Java】一文看懂Thread 线程池的 7 种创建方式、任务队列及自定义线程池(代码示例)

本文摘要&#xff1a;【Java】Thread 线程池的 7 种创建方式及自定义线程池&#xff08;代码示例版&#xff09; &#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专…