FPGA(八)EEPROM
8.1 EEPROM简介
EEPROM(或E2PROM,Electrically Erasable Programmable read only memory):电可擦除可编程只读存储器。
两种非易失性存储器对比:
- EEPROM:以字节为单位改写;结构复杂,容量小。
- Flash:以扇区为单位擦除;结构简单,容量大。
下面是EEPROM的芯片AT24C04原理图:

A0,A1,A2:定义该芯片的地址,最多支持8个芯片共用I2C总线,因为这里只有一个芯片,直接全部置0;SCL,SDA:I2C总线;WP:写保护,拉高则无法写,这里始终拉低。
8.2 IIC协议
IIC(或I2C,Inter-Integrated Circuit):集成线路总线。两线式串行总线,支持一主多从通信。半双工通信,由数据线(SDA)和时钟线(SCL)构成。有不同的模式 :标准100Kb/s、快速400Kb/s、高速3.4Mb/s。
8.2.1 整体时序
下面是I2C的整体时序:

- ①:空闲状态,SCL和SDA均拉高;
- ②:起始信号,主机开始传输数据,在SCL为高时拉低SDA;
- ③:数据传输状态;
- ④:停止信号,主机停止传输数据,在SCL为高时拉高SDA。
下面是数据传输的时序:

- SDA只能在SCL为低电平时改变,在SCL为高电平时必须保持稳定。
- 发生数据时,先发送8bit数据(8个时钟周期),第8个时钟周期末主机释放SDA。第9个时钟周期从机通过SDA进行应答,拉低表示有效,拉高表示失败,该时钟周期末从机释放SDA让主机继续传输。
- 最先发送的是字节的MSB(最高位)。
8.2.2 器件地址
每个I2C器件都有一个地址,即前面提到的A0,A1,A2。

在传输数据时,第一个字节需要发送器件地址,即下图的绿色部分(7bit),前4位固定为1010,后3位为A0,A1,A2。第8位是读写控制位,表示该轮传输是读(1)还是写(0)。
8.2.3 存储地址
发送完器件地址后需要发送具体读写的地址,即下面的存储地址(字地址)。

有些EEPROM容量比较小,地址一个字节就可以表示,如AT24C02只有2Kb=256B,参加上图单字节地址分布;而大容量EEPROM的地址需要两个字节来表示,如AT24C64需要13位,参加上图双字节地址分布,最前面的3字节不关心。
8.2.4 写时序
写分为单次写(字节写)和连续写(页写)。1页是32字节。
字节写

先发送器件地址,再发送存储地址,最后发送8bit数据。注意每字节的传输都有应答信号,最后主机要发送停止信号。
页写
只有部分I2C设备支持页写操作。

前面和单次写一样,只不过传输完8bit数据后不停止,而是继续发送8bit数据,全部发送完毕后再停止。
1页只有32B,芯片内部有5位的指针,当指向的地址为31时,下一次指针会重新指向0,即覆盖了页内地址0的数据。因此没必要写超过1页的数据。
8.2.5 读时序
有下面3种模式。
当前地址读

指在一次读或写操作后发起读操作。由于I2C器件在读写操作后,其内部的地址指针自动加一,因此当前地址读可以读取下一个字地址的数据。
时序如上,接收完8bit数据后不应答(ACK拉高),发送停止信号。
随机地址读

在读之前通过哑写(Dummy Write)来改变指针地址。
连续地址读
即在“当前地址读”或“连续地址读”之后进行应答,直到读完后才不应答和发送停止位。
8.3 程序设计
8.3.1 系统架构

8.3.2 I2C驱动设计
下图是I2C驱动中状态机的状态转换图。从左边的idle状态开始,大致流程为根据bit_ctrl决定发送几位字地址,然后根据wr_flag决定是执行读还是写操作。

8.3.3 三态门说明
sda是inout端口,需要用如下的语句生成三态门,也可以用IOBUF,参见LCD实验。
sda_dir控制是input还是output,如果是input则赋值为高阻态,数据通过sda_in接收。
assign sda = sda_dir ? sda_out : 1'bz;
assign sda_in = sda;
8.4 实验代码
8.4.1 顶层模块
module top(
input sys_clk , //系统时钟
input sys_rst_n , //系统复位
//eeprom interface
output i2c_scl , //eeprom的时钟线scl
inout i2c_sda , //eeprom的数据线sda
//user interface
output led //led显示
);
//parameter define
parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR)
parameter BIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率 50MHz
parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率 250kHz
parameter L_TIME = 17'd125_000 ; //led闪烁时间参数
//wire define
wire dri_clk ; //I2C操作时钟
wire i2c_exec ; //I2C触发控制
wire [15:0] i2c_addr ; //I2C操作地址
wire [ 7:0] i2c_data_w; //I2C写入的数据
wire i2c_done ; //I2C操作结束标志
wire i2c_ack ; //I2C应答标志 0:应答 1:未应答
wire i2c_rh_wl ; //I2C读写控制
wire [ 7:0] i2c_data_r; //I2C读出的数据
wire rw_done ; //E2PROM读写测试完成
wire rw_result ; //E2PROM读写测试结果 0:失败 1:成功
//e2prom读写测试模块
e2prom_rw u_e2prom_rw(
.clk (dri_clk ), //时钟信号
.rst_n (sys_rst_n ), //复位信号
//i2c interface
.i2c_exec (i2c_exec ), //I2C触发执行信号
.i2c_rh_wl (i2c_rh_wl ), //I2C读写控制信号
.i2c_addr (i2c_addr ), //I2C器件内地址
.i2c_data_w (i2c_data_w), //I2C要写的数据
.i2c_data_r (i2c_data_r), //I2C读出的数据
.i2c_done (i2c_done ), //I2C一次操作完成
.i2c_ack (i2c_ack ), //I2C应答标志
//user interface
.rw_done (rw_done ), //E2PROM读写测试完成
.rw_result (rw_result ) //E2PROM读写测试结果 0:失败 1:成功
);
//i2c驱动模块
i2c_driver #(
.SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址
.CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率
.I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率
) u_i2c_dri(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
//i2c interface
.i2c_exec (i2c_exec ), //I2C触发执行信号
.bit_ctrl (BIT_CTRL ), //器件地址位控制(16b/8b)
.i2c_rh_wl (i2c_rh_wl ), //I2C读写控制信号
.i2c_addr (i2c_addr ), //I2C器件内地址
.i2c_data_w (i2c_data_w), //I2C要写的数据
.i2c_data_r (i2c_data_r), //I2C读出的数据
.i2c_done (i2c_done ), //I2C一次操作完成
.i2c_ack (i2c_ack ), //I2C应答标志
.scl (i2c_scl ), //I2C的SCL时钟信号
.sda (i2c_sda ), //I2C的SDA信号
//user interface
.dri_clk (dri_clk ) //I2C操作时钟
);
//led指示模块
led_alarm #(.L_TIME(L_TIME ) //控制led闪烁时间
) u_led_alarm(
.clk (dri_clk ),
.rst_n (sys_rst_n ),
.rw_done (rw_done ),
.rw_result (rw_result ),
.led (led )
);
endmodule8.4.2 I2C驱动模块
该模块会生成一个SCL的4倍频率的时钟dri_clk,用于驱动相关控制。为什么4分频?参见下图,这样在dri_clk上升沿时能控制SDA信号变化。

module i2c_driver #(
parameter SLAVE_ADDR = 7'b1010000, // EEPROM从机地址
parameter CLK_FREQ = 26'd50_000_000, // 模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000 // IIC_SCL的时钟频率
) (
input clk,
input rst_n,
//i2c interface
input i2c_exec, // I2C触发执行信号
input bit_ctrl, // 控制存储地址的位数,1:16bit,0:8bit
input i2c_rh_wl, // I2C读写控制信号
input [15:0] i2c_addr, // I2C器件内地址,如果bit_ctrl=0则只使用低8位
input [ 7:0] i2c_data_w, // I2C要写的数据
output logic [ 7:0] i2c_data_r, // I2C读出的数据
output logic i2c_done, // I2C一次操作完成
output logic i2c_ack, // I2C应答标志,0:应答 1:未应答
output logic scl, // I2C的SCL时钟信号
inout sda, // I2C的SDA信号
//user interface
output logic dri_clk // 驱动I2C操作的驱动时钟
);
// 状态定义
localparam ST_IDLE = 8'b0000_0001; // 空闲状态
localparam ST_SLADDR = 8'b0000_0010; // 发送器件地址
localparam ST_ADDR16 = 8'b0000_0100; // 发送16位字地址
localparam ST_ADDR8 = 8'b0000_1000; // 发送8位字地址
localparam ST_DATA_WR = 8'b0001_0000; // 写数据(8 bit)
localparam ST_ADDR_RD = 8'b0010_0000; // 发送器件地址读
localparam ST_DATA_RD = 8'b0100_0000; // 读数据(8 bit)
localparam ST_STOP = 8'b1000_0000; // 结束I2C操作
logic sda_dir; // I2C数据(SDA)方向控制,1:输出,0:输入
logic sda_out; // SDA输出信号
logic st_done; // 为高表示状态结束,可以转换到下一个状态
logic wr_flag; // 写标志,来自i2c_rh_wl
logic [ 6:0] cnt; // 计数
logic [ 7:0] data_r; // 读取的数据
logic [15:0] addr_t; // 寄存读/写地址,来自i2c_addr
logic [ 7:0] data_w_t; // 寄存写的数据,来自i2c_data_w
logic [ 7:0] cur_state; // 状态机当前状态
logic [ 7:0] next_state; // 状态机下一状态
wire sda_in; // SDA输入信号
wire [8:0] clk_divide; // 模块驱动时钟的分频系数
// SDA控制
assign sda = sda_dir ? sda_out : 1'bz; // SDA数据输出或高阻
assign sda_in = sda; // SDA数据输入
// 生成I2C的SCL的四倍频率的驱动时钟
localparam CLK_DIVIDE = CLK_FREQ / I2C_FREQ / 8;
logic [9:0] clk_cnt;
always_ff @(posedge clk, negedge rst_n) begin
if (!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if (clk_cnt == CLK_DIVIDE - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
// (三段式状态机)同步时序描述状态转移
always_ff @(posedge dri_clk, negedge rst_n) begin
if (!rst_n)
cur_state <= ST_IDLE;
else
cur_state <= next_state;
end
// 组合逻辑判断状态转移条件
always_comb begin
case (cur_state)
ST_IDLE: begin
if (i2c_exec) next_state = ST_SLADDR;
else next_state = ST_IDLE;
end
ST_SLADDR: begin
if (st_done) begin
if (bit_ctrl) next_state = ST_ADDR16;
else next_state = ST_ADDR8;
end
else next_state = ST_SLADDR;
end
ST_ADDR16: begin
if (st_done) next_state = ST_ADDR8;
else next_state = ST_ADDR16;
end
ST_ADDR8: begin
if (st_done) begin
if (wr_flag) next_state = ST_ADDR_RD;
else next_state = ST_DATA_WR;
end
else next_state = ST_ADDR8;
end
ST_DATA_WR: begin
if (st_done) next_state = ST_STOP;
else next_state = ST_DATA_WR;
end
ST_ADDR_RD: begin
if (st_done) next_state = ST_DATA_RD;
else next_state = ST_ADDR_RD;
end
ST_DATA_RD: begin
if (st_done) next_state = ST_STOP;
else next_state = ST_DATA_RD;
end
ST_STOP: begin
if (st_done) next_state = ST_IDLE;
else next_state = ST_STOP ;
end
default: next_state = ST_IDLE;
endcase
end
// 时序电路描述状态输出
always_ff @(posedge dri_clk, negedge rst_n) begin
// 复位初始化
if (!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1; // 数据线复位为高
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r <= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_w_t <= 1'b0;
end
else begin
st_done <= 1'b0;
cnt <= cnt + 1'b1;
case (cur_state)
ST_IDLE: begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
cnt <= 7'b0;
if (i2c_exec) begin
wr_flag <= i2c_rh_wl;
addr_t <= i2c_addr;
data_w_t <= i2c_data_w;
i2c_ack <= 1'b0;
end
end
ST_SLADDR: begin // 发送器件地址和读写控制位
case (cnt)
7'd1 : sda_out <= 1'b0; // 在scl为高时拉低sda,开始信号
7'd3 : scl <= 1'b0; // 拉低scl
7'd4 : sda_out <= SLAVE_ADDR[6]; // 在scl为低时发送数据
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; // 0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0; // 至此8位数据发送完毕
7'd36: begin
sda_dir <= 1'b0; // 释放sda
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1; // 拉高准备接受应答
7'd38: begin // 接受应答
st_done <= 1'b1;
if(sda_in == 1'b1) // 高电平表示未应答
i2c_ack <= 1'b1; // 拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
endcase
end
ST_ADDR16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; // 传送字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
endcase
end
ST_ADDR8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
endcase
end
ST_DATA_WR: begin // 写数据(8 bit)
case(cnt)
7'd0: begin
sda_out <= data_w_t[7];
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_w_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_w_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_w_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_w_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_w_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_w_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_w_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
endcase
end
ST_ADDR_RD: begin // 写地址以进行读数据
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; // 在scl为高时拉低sda,重新开始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6];// 传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; // 1:读
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
endcase
end
ST_DATA_RD: begin // 读取数据(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1; // 发送非应答
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
endcase
end
ST_STOP: begin // 结束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1;
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1; // 在scl为高时拉高sda,停止信号
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; //向上层模块传递I2C结束信号
end
endcase
end
endcase
end
end
endmodule8.4.3 I2C读写模块
控制i2c_driver依次写入和读出一定数量的字节,判断结果是否正确。
module e2prom_rw(
input clk , //时钟信号
input rst_n , //复位信号
//i2c interface
output reg i2c_rh_wl , //I2C读写控制信号
output reg i2c_exec , //I2C触发执行信号
output reg [15:0] i2c_addr , //I2C器件内地址
output reg [ 7:0] i2c_data_w , //I2C要写的数据
input [ 7:0] i2c_data_r , //I2C读出的数据
input i2c_done , //I2C一次操作完成
input i2c_ack , //I2C应答标志
//user interface
output reg rw_done , //E2PROM读写测试完成
output reg rw_result //E2PROM读写测试结果 0:失败 1:成功
);
//parameter define
//EEPROM写数据需要添加间隔时间,读数据则不需要
parameter WR_WAIT_TIME = 14'd5000; // 写入间隔时间5ms,时钟是dri_clk是1MHz
parameter MAX_BYTE = 16'd256 ; // 读写测试的字节个数
//reg define
reg [1:0] flow_cnt ; //状态流控制
reg [13:0] wait_cnt ; //延时计数器
//EEPROM读写测试,先写后读,并比较读出的值与写入的值是否一致
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flow_cnt <= 2'b0;
i2c_rh_wl <= 1'b0;
i2c_exec <= 1'b0;
i2c_addr <= 16'b0;
i2c_data_w <= 8'b0;
wait_cnt <= 14'b0;
rw_done <= 1'b0;
rw_result <= 1'b0;
end
else begin
i2c_exec <= 1'b0;
rw_done <= 1'b0;
case(flow_cnt)
2'd0 : begin
wait_cnt <= wait_cnt + 1'b1; //延时计数
if(wait_cnt == WR_WAIT_TIME - 1'b1) begin //EEPROM写操作延时完成
wait_cnt <= 1'b0;
if(i2c_addr == MAX_BYTE) begin //256个字节写入完成
i2c_addr <= 1'b0;
i2c_rh_wl <= 1'b1;
flow_cnt <= 2'd2;
end
else begin
flow_cnt <= flow_cnt + 1'b1;
i2c_exec <= 1'b1;
end
end
end
2'd1 : begin
if(i2c_done == 1'b1) begin //EEPROM单次写入完成
flow_cnt <= 2'd0;
i2c_addr <= i2c_addr + 1'b1; //地址0~255分别写入
i2c_data_w <= i2c_data_w + 1'b1; //数据0~255
end
end
2'd2 : begin
flow_cnt <= flow_cnt + 1'b1;
i2c_exec <= 1'b1;
end
2'd3 : begin
if(i2c_done == 1'b1) begin //EEPROM单次读出完成
//读出的值错误或者I2C未应答,读写测试失败
if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
rw_done <= 1'b1;
rw_result <= 1'b0;
end
else if(i2c_addr == MAX_BYTE - 1'b1) begin //读写测试成功
rw_done <= 1'b1;
rw_result <= 1'b1;
end
else begin
flow_cnt <= 2'd2;
i2c_addr <= i2c_addr + 1'b1;
end
end
end
default : ;
endcase
end
end
endmodule8.4.4 LED显示模块
LED灯常亮表示读写测试正确,闪烁表示错误。
module led_alarm #(
parameter L_TIME = 25'd25_000_000
) (
input clk , //时钟信号
input rst_n , //复位信号
input rw_done , //E2PROM读写测试完成
input rw_result , //E2PROM读写测试结果
output reg led
);
//reg define
reg rw_done_flag; //读写测试完成标志
reg [24:0] led_cnt ; //led计数
//读写测试完成标志
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
rw_done_flag <= 1'b0;
else if(rw_done)
rw_done_flag <= 1'b1;
end
//错误标志为1时PL_LED0闪烁,否则PL_LED0常亮
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
led_cnt <= 25'd0;
led <= 1'b0;
end
else begin
if(rw_done_flag) begin
if(rw_result) //读写测试正确
led <= 1'b1; //led灯常亮
else begin //读写测试错误
led_cnt <= led_cnt + 25'd1;
if(led_cnt == L_TIME - 1'b1) begin
led_cnt <= 25'd0;
led <= ~led; //led灯闪烁
end
end
end
else
led <= 1'b0; //读写测试完成之前,led灯熄灭
end
end
endmodule8.4.5 I2C驱动测试模块
进行一次写和一次读。
`timescale 1ns/1ns //定义仿真时间单位1ns和仿真时间精度为1ns
module tb_i2c_dri;
//parameter define
parameter T = 20; //时钟周期为20ns
parameter IIC_WR_CYCYLE = 10_000;
parameter SLAVE_ADDR = 7'b1010000 ; //EEPROM从机地址
parameter CLK_FREQ = 26'd50_000_000; //模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000; //IIC_SCL的时钟频率
//reg define
reg sys_clk; //时钟信号
reg sys_rst_n; //复位信号
reg i2c_exec ;
reg bit_ctrl ;
reg i2c_rh_wl ;
reg [15:0] i2c_addr ;
reg [7:0] i2c_data_w;
reg [3:0] flow_cnt ;
reg [13:0] delay_cnt ;
//wire define
wire [7:0] i2c_data_r;
wire i2c_done ;
wire i2c_ack ;
wire scl ;
wire sda ;
wire dri_clk ;
//给输入信号初始值
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0; //复位
#(T+1) sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高
end
//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;
always @(posedge dri_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
i2c_exec <= 1'b0;
bit_ctrl <= 1'b0;
i2c_rh_wl <= 1'b0;
i2c_addr <= 1'b0;
i2c_data_w <= 1'b0;
flow_cnt <= 1'b0;
delay_cnt <= 1'b0;
end
else begin
case(flow_cnt)
'd0 : flow_cnt <= flow_cnt + 1'b1;
'd1 : begin
i2c_exec <= 1'b1; //拉高触发信号
bit_ctrl <= 1'b1; //地址位选择信号 1: 16位
i2c_rh_wl <= 1'b0; //写操作
i2c_addr <= 16'h0555; //写地址
i2c_data_w <= 8'hAA; //写数据
flow_cnt <= flow_cnt + 1'b1;
end
'd2 : begin
i2c_exec <= 1'b0;
flow_cnt <= flow_cnt + 1'b1;
end
'd3 : begin
if(i2c_done)
flow_cnt <= flow_cnt + 1'b1;
end
'd4 : begin
delay_cnt <= delay_cnt + 1'b1;
if(delay_cnt == IIC_WR_CYCYLE - 1'b1)
flow_cnt <= flow_cnt + 1'b1;
end
'd5 : begin
i2c_exec <= 1'b1;
bit_ctrl <= 1'b1;
i2c_rh_wl <= 1'b1; //读操作
i2c_addr <= 16'h0555;
i2c_data_w <= 8'hAA;
flow_cnt <= flow_cnt + 1'b1;
end
'd6 : begin
i2c_exec <= 1'b0;
flow_cnt <= flow_cnt + 1'b1;
end
'd7 : begin
if(i2c_done)
flow_cnt <= flow_cnt + 1'b1;
end
default:;
endcase
end
end
pullup(sda); // 高阻时上拉
//例化led模块
i2c_driver #(
.SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址
.CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率
.I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率
) u_i2c_dri(
.clk (sys_clk),
.rst_n (sys_rst_n),
.i2c_exec (i2c_exec ),
.bit_ctrl (bit_ctrl ),
.i2c_rh_wl (i2c_rh_wl ),
.i2c_addr (i2c_addr ),
.i2c_data_w (i2c_data_w),
.i2c_data_r (i2c_data_r),
.i2c_done (i2c_done ),
.i2c_ack (i2c_ack ),
.scl (scl ),
.sda (sda ),
.dri_clk (dri_clk )
);
EEPROM_AT24C64 u_EEPROM_AT24C64(
.scl (scl),
.sda (sda)
);
endmodule8.4.6 AT24C64仿真模型
`timescale 1ns/1ns
`define timeslice 1250
module EEPROM_AT24C64(
scl,
sda
);
input scl;
inout sda;
reg out_flag;
reg[7:0] memory[8191:0];
reg[12:0]address;
reg[7:0]memory_buf;
reg[7:0]sda_buf;
reg[7:0]shift;
reg[7:0]addr_byte_h;
reg[7:0]addr_byte_l;
reg[7:0]ctrl_byte;
reg[1:0]State;
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
initial
begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
always@(negedge sda)
begin
if(scl == 1)
begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end
always@(posedge sda)
begin
if(scl == 1)
stop_W_R;
else
begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4
|| ctrl_byte == w3 || ctrl_byte == w2
|| ctrl_byte == w1 || ctrl_byte == w0)
begin
State = 2'b10;
write_to_eeprom;
end
else
State = 2'b00;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask
task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
State = 2'b00;
end
endtask
task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0)
begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl)
begin
#(`timeslice);
out_flag = 1;
sda_buf = 0;
end
@(negedge scl)
begin
#(`timeslice-250);
out_flag = 0;
end
end
endtask
task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1)
begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1;
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule