本帖最后由 feifeiz 于 2023-5-7 19:20 编辑
一、前言
在上一篇文章中学习了串口发送与接收,通过上位机下发数据给开发板,然后通过开发板又将数据返回,同时在串口接收模块中通过特定的协议点亮LED。本片文章继续学习如何在开发板上实现FIFO。
二、FIFO简介FIFO 的英文全称是First In First Out,即先进先出。一般指的是对数据的存储具有先进先出特性的一个缓存器。常见的应用场景,在典型的串口设计中,串口的发送缓冲区只有一个字节,串口发送一次就需要CPU执行一次,当串口发送的数据比较多的时候,就会频繁的使用CPU,从而导致CPU的执行效率低下,通过使用FIFO的方法,使FIFO先工作完成后CPU才介入。在FPGA的FIFO常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。 同时根据FIFO的时钟域,将FIFO分成了同步FIFO和异步FIFO,他们的区别在于读写时是否使用的是同一个时钟,同步FIFO主要用于同步时钟下的数据缓存,异步FIFO主要应用于跨时域的数据型号传递,入单片机与FPGA通信过程中,使用的不是同一个时钟,通过异步FIFO的方式实现数据信号的传递,还有可能是不同位宽之间的数据传递。
三、实验目标
通过新建FIFO IP核,根据FIFO的状态进行读写数据,当FIFO为空时,向FIFO中写入数据,写满为止,完成FIFO的写入后,将写入的数据读取的出来。
四、程序设计
总体设计,共四个模块,FIFO写入模块,FIFO读模块,顶层设计模块以及FIF IP核,总体实现框图如下.
在顶层模块设计中,例化FIFO IP核,FIFO写模块,FIFO读模块
module ip_fifo(
input sys_clk , // 时钟信号
input sys_rst_n // 复位信号
);
//wire define
wire fifo_wr_en /* synthesis syn_keep=1 */;// FIFO写使能信号
wire fifo_rd_en /* synthesis syn_keep=1 */;// FIFO读使能信号
wire [7:0] fifo_din /* synthesis syn_keep=1 */;// 写入到FIFO的数据
wire [7:0] fifo_dout /* synthesis syn_keep=1 */;// 从FIFO读出的数据
wire almost_full /* synthesis syn_keep=1 */;// FIFO将满信号
wire almost_empty /* synthesis syn_keep=1 */;// FIFO将空信号
wire fifo_full /* synthesis syn_keep=1 */;// FIFO满信号
wire fifo_empty /* synthesis syn_keep=1 */;// FIFO空信号
wire [7:0] fifo_wr_data_count /* synthesis syn_keep=1 */;// FIFO写时钟域的数据计数
wire [7:0] fifo_rd_data_count /* synthesis syn_keep=1 */;// FIFO读时钟域的数据计数
//*****************************************************
//** main code
//*****************************************************
reg almost_empty_d0 ; //almost_empty 延迟一拍
reg almost_empty_d1 ; //almost_empty 延迟两拍
reg almost_empty_syn ; //almost_empty 延迟三拍
reg almost_full_d0 ; //almost_full 延迟一拍
reg almost_full_d1 ; //almost_full 延迟两拍
reg almost_full_syn ; //almost_full 延迟三拍
//因为 almost_empty 信号是属于FIFO读时钟域的
//所以要将其同步到写时钟域中
always@( posedge sys_clk ) begin
if( !sys_rst_n ) begin
almost_empty_d0 <= 1'b0 ;
almost_empty_syn <= 1'b0 ;
almost_empty_d1 <= 1'b0 ;
end
else begin
almost_empty_d0 <= almost_empty ;
almost_empty_d1 <= almost_empty_d0 ;
almost_empty_syn <= almost_empty_d1 ;
end
end
//因为 almost_full 信号是属于FIFO读时钟域的
//所以要将其同步到写时钟域中
always@( posedge sys_clk ) begin
if( !sys_rst_n ) begin
almost_full_d0 <= 1'b0 ;
almost_full_syn <= 1'b0 ;
almost_full_d1 <= 1'b0 ;
end
else begin
almost_full_d0 <= almost_full ;
almost_full_d1 <= almost_full_d0 ;
almost_full_syn <= almost_full_d1 ;
end
end
fifo_generator_0 u_fifo_generator_0 (
.wr_clk (sys_clk ), // input
.wr_rst (~sys_rst_n ), // input
.wr_en (fifo_wr_en ), // input
.wr_data (fifo_din ), // input [7:0]
.wr_full (fifo_full ), // output
.wr_water_level (fifo_wr_data_count), // output [8:0]
.almost_full (almost_full ), // output
.rd_clk (sys_clk ), // input
.rd_rst (~sys_rst_n ), // input
.rd_en (fifo_rd_en ), // input
.rd_data (fifo_dout ), // output [7:0]
.rd_empty (fifo_empty ), // output
.rd_water_level (fifo_rd_data_count), // output [8:0]
.almost_empty (almost_empty ) // output
);
//例化写FIFO模块
fifo_wr u_fifo_wr(
.clk ( sys_clk ), // 写时钟
.rst_n ( sys_rst_n ), // 复位信号
.fifo_wr_en ( fifo_wr_en ), // fifo写请求
.fifo_wr_data ( fifo_din ), // 写入FIFO的数据
.almost_empty ( almost_empty_syn ), // fifo空信号
.almost_full ( almost_full_syn ) // fifo满信号
);
//例化读FIFO模块
fifo_rd u_fifo_rd(
.clk ( sys_clk ), // 读时钟
.rst_n ( sys_rst_n ), // 复位信号
.fifo_rd_en ( fifo_rd_en ), // fifo读请求
.fifo_dout ( fifo_dout ), // 从FIFO输出的数据
.almost_empty ( almost_empty_syn ), // fifo空信号
.almost_full ( almost_full_syn ) // fifo满信号
);
Endmodule
写模块,fifo_wr 模块是通过一个不断进行状态循环的小状态机,如果检测到 FIFO 为空,则先延时 10拍。如果写满,则回到状态 0,即等待 FIFO 被读空,以进行下一轮的写操作。
module fifo_wr(
//mudule clock
input clk , // 时钟信号
input rst_n , // 复位信号
//FIFO interface
input almost_empty, // FIFO将空信号
input almost_full , // FIFO将满信号
output reg fifo_wr_en , // FIFO写使能
output reg [7:0] fifo_wr_data // 写入FIFO的数据
);
//reg define
reg [1:0] state ; //动作状态
reg [3:0] dly_cnt ; //延迟计数器
//*****************************************************
//** main code
//*****************************************************
//向FIFO中写入数据
always @(posedge clk ) begin
if(!rst_n) begin
fifo_wr_en <= 1'b0;
fifo_wr_data <= 8'd0;
state <= 2'd0;
dly_cnt <= 4'd0;
end
else begin
case(state)
2'd0: begin
if(almost_empty) begin //如果检测到FIFO将被读空
state <= 2'd1; //就进入延时状态
end
else
state <= state;
end
2'd1: begin
if(dly_cnt == 10) begin //延时10拍
//原因是FIFO IP核内部状态信号的更新存在延时
//延迟10拍以等待状态信号更新完毕 dly_cnt <= 4'd0;
state <= 2'd2; //开始写操作
fifo_wr_en <= 1'b1; //打开写使能
end
else begin
dly_cnt <= dly_cnt + 4'd1;
end
end
2'd2: begin
if(almost_full) begin //等待FIFO将被写满
fifo_wr_en <= 1'b0; //关闭写使能
fifo_wr_data <= 8'd0;
state <= 2'd0; //回到第一个状态
end
else begin //如果FIFO没有被写满
fifo_wr_en <= 1'b1; //则持续打开写使能
fifo_wr_data <= fifo_wr_data + 1'd1; //且写数据值持续累加
end
end
default : state <= 2'd0;
endcase
end
end
endmodule
五、实验现象
编译工程,生成.sbit文件
连接开发板
下载
进行仿真验证,右击仿真文件,运行仿真,前提需要先装好Modelsim软件 进入仿真页面,并添加需要观测的量 仿真波形如下,当写满 255个数据后,fifo_full 满信号就会拉高。经过延时之后,fifo_rd_en 写使能信 号拉高,经过一拍之后就开始将 fifo 中的数据送到 fifo_dout 端口上。 好好修改一下网页编辑器吧,每次上传都挺难受。
file:///C:/Users/dell/AppData/Local/Temp/msohtmlclip1/01/clip_image014.jpg
|