目录
四、EEPROM读写系统设计
1. 整体系统概述
2. fifo_ctrl(写数据fifo控制模块)
3. I2C_wr_ctrl(I2C写控制模块)
4. I2C_rd_ctrl(I2C读控制模块)
5. fifo_ctrl2(读数据fifo控制模块)
6. I2C_eeprom_top 顶层模块
五、仿真调试及板级验证
1. 仿真调试
2. 板级验证
本专题EEPROM读写系统(在下一篇博客讲解,包含本篇内容)整体功能实现的工程下载链接如下:
https://download.csdn.net/download/qq_33231534/12503289
整个系统的要求为:首先通过串口助手发送四个字节数据,将四个字节数据存储在fifo中,然后将fifo中的数据读出存储到EEPROM中,EEPROM读写均采用连续读写4个字节数据,当写完数据后,再从EEPROM中将刚刚写入的4字节数据依次读出来存储到fifo中,再从fifo中读出数据发送到出口,在串口助手上显示出来。
其中I2C_ctrl模块在上一篇博客中已经讲解了,uart_rx 和 uart_tx 在串口篇和数据采集系统篇里边也讲的很详细,是通用模块,sync_fifo1 和 sync_fifo2 是同步FIFO模块,可以通过使用IP核,或者自己编写代码,同步fifo模块也是通用模块,在数据采集系统里边有介绍和代码,后期会专门写一下FIFO的博客,这里也不赘述。
fifo_ctrl(写数据fifo控制模块):串口发送的数据由sync_fifo1来保存,而fifo_ctrl模块就是控制串口接收模块和fifo模块的桥梁,保证串口接受的数据能保存在fifo中。
I2C_wr_ctrl(I2C写控制模块):从FIFO中逐个数据取出写入EEPROM。
I2C_rd_ctrl(I2C读控制模块):从EEPROM逐个读出数据存入FIFO中。
fifo_ctrl2(读数据fifo控制模块):将fifo中的数据逐个读出发送到串口发送模块。
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
data_byte | I | 8 | 接收到串口发的数据 |
rx_done | I | 1 | 串口接收数据完成标志 |
wrreq | O | 1 | fifo写请求 |
data_to_fifo | O | 8 | 输出保存到fifo的数据 |
代码如下:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: fifo_ctrl.v
//Last modified Date: 2020/6.6
//Last Version:
//Descriptions: 串口发送的数据由sync_fifo1来保存,而fifo_ctrl模块就
// 是控制串口接收模块和fifo模块的桥梁,保证串口接受的数据能保存在fifo中。
//-------------------------------------------------------------------
module fifo_ctrl(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input [ 7: 0] data_byte ,//接收到串口发的数据
input rx_done ,//串口接收数据完成标志
output reg wrreq ,//fifo写请求
output reg [ 7: 0] data_to_fifo //输出保存到fifo的数据
);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wrreq <= 0;
end
else if(rx_done) begin
wrreq <= 1;
end
else begin
wrreq <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_to_fifo <= 0;
end
else if(rx_done) begin
data_to_fifo <= data_byte;
end
else begin
data_to_fifo <= data_to_fifo;
end
end
endmodule
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
usedw | I | 3 | fifo中数据个数 |
q | I | 8 | fifo读出的数据 |
wr_data_vaild | I | 1 | I2C_ctrl模块中写数据有效信号 |
rdreq | O | 1 | fifo读请求 |
wr_en | O | 1 | I2C_ctrl模块写使能 |
wr_data | O | 8 | 写入eeprom的数据 |
代码如下:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: I2C_wr_ctrl
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: 从FIFO中逐个数据取出写入EEPROM
//-------------------------------------------------------------------
module I2C_wr_ctrl(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input [2:0] usedw ,//fifo中数据个数
input [7:0] q ,//fifo读出的数据
input wr_data_vaild ,//I2C_ctrl模块中写数据有效信号
output reg rdreq ,//fifo读请求
output reg wr_en ,//I2C_ctrl模块写使能
output reg [7:0] wr_data //写入eeprom的数据
);
reg rdreq_r;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdreq_r <= 0;
end
else begin
rdreq_r <= rdreq;
end
end
always @(*)begin
if(usedw>=4 || wr_data_vaild) begin
rdreq = 1;
end
else begin
rdreq = 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_en <= 0;
end
else if(usedw>=4) begin
wr_en <= 1;
end
else begin
wr_en <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_data <= 0;
end
else if(rdreq_r) begin
wr_data <= q;
end
else begin
wr_data <= wr_data;
end
end
endmodule
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
done | I | 1 | I2C_ctrl模块读写数据结束标志 |
rd_data | I | 8 | 从EEPROM读出的数据 |
rd_data_vaild | I | 1 | 从EEPROM读出数据有效位 |
wrreq | O | 1 | fifo写请求 |
I2C_rd_data | O | 8 | 要写入fifo的数据 |
rd_en | O | 1 | I2C_ctrl模块读使能 |
代码如下:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: I2C_rd_ctrl
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: 从EEPROM逐个读出数据存入FIFO中
//-------------------------------------------------------------------
module I2C_rd_ctrl(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input done ,//I2C_ctrl模块读写数据结束标志
input [7:0] rd_data ,//从EEPROM读出的数据
input rd_data_vaild ,//从EEPROM读出数据有效位
output reg wrreq ,//fifo写请求
output reg [7:0] I2C_rd_data ,//要写入fifo的数据
output reg rd_en //I2C_ctrl模块读使能
);
reg flag_rd;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag_rd <= 0;
end
else if(done)begin
flag_rd <= ~flag_rd;
end
else begin
flag_rd <= flag_rd;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_en <= 0;
end
else if(done && flag_rd==0) begin
rd_en <= 1;
end
else begin
rd_en <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
I2C_rd_data <= 0;
end
else if(rd_data_vaild) begin
I2C_rd_data <= rd_data;
end
else begin
I2C_rd_data <= I2C_rd_data;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wrreq <= 0;
end
else if(rd_data_vaild) begin
wrreq <= 1;
end
else begin
wrreq <= 0;
end
end
endmodule
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
usedw | I | 3 | fifo中数据个数 |
q | I | 8 | fifo中读出的数据 |
tx_done | I | 1 | 串口发送一次数据完成标志 |
rdreq | O | 1 | fifo读请求 |
send_en | O | 1 | 串口发送使能信号 |
data_send | O | 8 | 串口发送的数据 |
代码如下:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: fifo_ctrl2.v
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: 将fifo中的数据逐个读出发送到串口发送模块
//-------------------------------------------------------------------
module fifo_ctrl2(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input [2:0] usedw ,//fifo中数据个数
input [7:0] q ,//fifo中读出的数据
input tx_done ,//串口发送一次数据完成标志
output reg rdreq ,//fifo读请求
output reg send_en ,//串口发送使能信号
output reg [7:0] data_send //串口发送的数据
);
reg rdreq_r;
always @(*)begin
if(rst_n==1'b0)begin
rdreq = 0;
end
else if(usedw>=4 || usedw>0&&tx_done) begin
rdreq = 1;
end
else begin
rdreq = 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdreq_r <= 0;
end
else begin
rdreq_r <= rdreq;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_send <= 0;
end
else if(rdreq_r) begin
data_send <= q;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
send_en <= 0;
end
else if(rdreq_r) begin
send_en <= 1;
end
else begin
send_en <= 0;
end
end
endmodule
将以上各个模块例化,信号连接,代码如下:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: I2C_eeprom_top.v
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: EEPROM读写系统顶层模块
//-------------------------------------------------------------------
module I2C_eeprom_top(
input clk ,
input rst_n ,
input rs232_rx ,
output rs232_tx ,
output scl ,
inout sda
);
wire [ 7: 0] data_byte ;
wire rx_done ;
wire wrreq1 ;
wire rdreq1 ;
wire [ 7: 0] data_to_fifo ;
wire [ 2: 0] usedw1 ;
wire [ 7: 0] data_to_eeprom ;
wire wr_en ;
wire wr_data_vaild ;
wire [ 7: 0] wr_data ;
wire done ;
wire [ 7: 0] rd_data ;
wire rd_data_vaild ;
wire wrreq2 ;
wire [ 7: 0] I2C_rd_data ;
wire rd_en ;
wire [ 2: 0] usedw2 ;
wire [ 7: 0] data_to_uart ;
wire rdreq2 ;
wire tx_done ;
wire send_en ;
wire [ 7: 0] data_send ;
UART_Byte_Rx u_UART_Byte_Rx(
.clk (clk) ,//系统时钟50MHz
.rst_n (rst_n) ,//系统复位
.rs232_rx (rs232_rx) ,//串口串行数据发送数据口
.baud_set (3'd1) ,//波特率选择信号
.data_byte (data_byte) ,//并行数据输出
.rx_done (rx_done) //接收1字节数据完成标志,rx_done可以作为输出有效信号使用
);
fifo_ctrl u_fifo_ctrl(
.clk (clk),
.rst_n (rst_n),
.data_byte (data_byte),
.rx_done (rx_done),
.wrreq (wrreq1),
.data_to_fifo (data_to_fifo)
);
sync_fifo
#(.WIDTH (8), //缓存的数据宽度
.DEPTH (5), //缓存的数据深度
.MAX_DEPTH_BIT (3)) //可设置的最大深度位数7,即最大深度为2^7-1
u_sync_fifo1(
.clk (clk), //系统时钟50MHz
.rst_n (rst_n), //系统复位
.wrreq (wrreq1), //写使能
.data (data_to_fifo), //写数据
.rdreq (rdreq1), //读使能
.q (data_to_eeprom), //读数据
.empty (), //空信号
.full (), //满信号
.half (), //半满信号
.usedw (usedw1) //fifo中剩余数据个数
);
I2C_wr_ctrl u_I2C_wr_ctrl(
.clk (clk) ,
.rst_n (rst_n) ,
.usedw (usedw1) ,
.q (data_to_eeprom) ,
.wr_data_vaild (wr_data_vaild) ,
.rdreq (rdreq1) ,
.wr_en (wr_en) ,
.wr_data (wr_data)
);
I2C_ctrl u_I2C_ctrl(
.clk (clk) ,
.rst_n (rst_n) ,
.rd_data_num (6'd4) ,//最大32
.wr_data_num (6'd4) ,//最大32
.word_addr (16'h05) ,
.device_addr (3'b001) ,
.wr_en (wr_en) ,
.wr_data (wr_data) ,
.rd_en (rd_en) ,
.wr_data_vaild (wr_data_vaild) ,
.rd_data (rd_data) ,
.rd_data_vaild (rd_data_vaild) ,
.done (done) ,
.scl (scl) ,
.sda (sda)
);
I2C_rd_ctrl u_I2C_rd_ctrl(
.clk (clk) ,
.rst_n (rst_n) ,
.done (done) ,
.rd_data (rd_data) ,
.rd_data_vaild (rd_data_vaild) ,
.wrreq (wrreq2) ,
.I2C_rd_data (I2C_rd_data) ,
.rd_en (rd_en)
);
sync_fifo
#(.WIDTH (8), //缓存的数据宽度
.DEPTH (5), //缓存的数据深度
.MAX_DEPTH_BIT (3)) //可设置的最大深度位数7,即最大深度为2^7-1
u_sync_fifo2(
.clk (clk), //系统时钟50MHz
.rst_n (rst_n), //系统复位
.wrreq (wrreq2), //写使能
.data (I2C_rd_data), //写数据
.rdreq (rdreq2), //读使能
.q (data_to_uart), //读数据
.empty (), //空信号
.full (), //满信号
.half (), //半满信号
.usedw (usedw2) //fifo中剩余数据个数
);
fifo_ctrl2 u_fifo_ctrl2(
.clk (clk) ,
.rst_n (rst_n) ,
.usedw (usedw2) ,
.q (data_to_uart) ,
.tx_done (tx_done) ,
.rdreq (rdreq2) ,
.send_en (send_en) ,
.data_send (data_send)
);
Uart_Byte_Tx u_Uart_Byte_Tx(
.clk (clk), //系统时钟
.rst_n (rst_n), //系统复位
.send_en (send_en), //发送使能
.data_byte (data_send), //发送的数据
.baud_set (3'd1), //波特率设置
.rs232_tx (rs232_tx), //FPGA将数据转换成串行数据发出
.tx_done (tx_done), //发送数据完毕标志
.uart_state () //串口发送状态,1为忙,0为空闲
);
endmodule
测试时从串口发送4个字节数据,8'b0000_1010 、8'b1000_0101 、8'b0000_1010 、8'b1000_0101 ,然后再重复一次。
其测试代码如下:
`timescale 1 ns/ 1 ns
module I2C_eeprom_top_tb();
// constants
// test vector input registers
reg clk;
reg rs232_rx;
reg rst_n;
//reg treg_sda;
// wires
wire rs232_tx;
wire scl;
wire sda;
parameter clk_period = 20;
// assign statements (if any)
//assign sda = treg_sda;
I2C_eeprom_top i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.rs232_rx(rs232_rx),
.rs232_tx(rs232_tx),
.rst_n(rst_n),
.scl(scl),
.sda(sda)
);
M24LC64 u_M24LC64(
.A0(1'b1),
.A1(1'b0),
.A2(1'b0),
.WP(1'b0),
.SDA(sda),
.SCL(scl),
.RESET(!rst_n)
);
pullup(sda);
initial clk = 0;
always #(clk_period/2) clk = ~clk;
initial begin
#1;
rst_n = 0;
rs232_rx = 1;
#(clk_period*5);
rst_n = 1;
#(clk_period*3);
repeat(2)begin
//0000_1010
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208*4);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
//1000_0101
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208*4);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
end
#(clk_period*500000);
repeat(2)begin
//0000_1010
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208*4);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
//1000_0101
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208*4);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 0;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
rs232_rx = 1;
#(clk_period*5208);
end
#(clk_period*500000);
$stop;
end
endmodule
仿真结果如下:
可以看到串口发送的数据和最后串口接收的数据保持一致。
管脚分配:
由于硬件上没有对 scl 和 sda 信号进行上拉,而I2C协议要求 scl 和 sda 上拉,因此在对管脚分配时可对这两个信号进行弱上拉,具体设置如下:
在分配管脚右侧空白处右击,选择Customize Columns,将 Weak Pull-Up Resistor移到右边,点击OK,再到引脚分配后边将其弱上拉打开,见下图所示:
将jic文件下载到FPGA板子后,打开串口助手,发送四个数据 12 13 12 11,可以看到串口接收到发送的四个数据,验证成功。
测试发现,输入有些数据,接收到会有异常,如下:
在这里,我将别人写好的比较权威的I2C代码移植到我的系统里,测试发现还是这个样子,这里分析了一下,可能是这个测试系统逻辑方面存在不足,但已经通过仿真和部分测试验证了I2C控制器模块的正确性。