IP核——FIFO实验
一、实验任务
本节的实验任务是使用 Vivado 生成 FIFO IP 核,并实现以下功能:当 FIFO 为空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;然后从 FIFO 中读出数据,直到 FIFO 被读空为止,以此向大家详细介绍一下 FIFO IP 核的使用方法。
二、程序设计
前置知识
- FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便。
- FIFO在开发板上也是一个RAM资源。
- 注意这里的“即将空”和“即将满”的信号,在“空”或“满”的前一拍拉高,需要一个边沿检测来抓取上升沿。
- 使用状态机来实现读、写状态的跳转。
- 当满或空时,不能立即进行读或写操作,因为可能会出现数据丢失的问题,所以一般在读使能或写使能拉高之前,延迟几个时钟周期。
设计文件
//顶层文件
module ip_fifo (
input sys_clk,
input sys_rst_n
);
wire almost_full;
wire almost_empty;
wire fifo_wr_en;
wire [7:0] fifo_wr_data;
wire [7:0] dout;
wire fifo_rd_en;
wire rd_data_count;
wire wr_data_count;
wire full;
wire empty;
fifo_wr u_fifo_wr (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.almost_full (almost_full ),
.almost_empty(almost_empty),
.fifo_wr_en (fifo_wr_en ),
.fifo_wr_data(fifo_wr_data)
);
fifo_rd u_fifo_rd (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.almost_full (almost_full ),
.almost_empty(almost_empty),
.fifo_rd_data(fifo_rd_data),
.fifo_rd_en (fifo_rd_en )
);
fifo_generator_0 u_fifo_generator_0 (
.wr_clk (sys_clk ), // input wire wr_clk
.rd_clk (sys_clk ), // input wire rd_clk
.din (fifo_wr_data ), // input wire [7 : 0] din
.wr_en (fifo_wr_en ), // input wire wr_en
.rd_en (fifo_rd_en ), // input wire rd_en
.dout (dout ), // output wire [7 : 0] dout
.full (full ), // output wire full
.almost_full (almost_full ), // output wire almost_full
.empty (empty ), // output wire empty
.almost_empty (almost_empty ), // output wire almost_empty
.rd_data_count(rd_data_count), // output wire [7 : 0] rd_data_count
.wr_data_count(wr_data_count) // output wire [7 : 0] wr_data_count
);
endmodule
//读模块
module fifo_rd (
input sys_clk,
input sys_rst_n,
input almost_full , //写模块的即将满信号 在下一个周期满
input almost_empty , //读模块的即将空信号
input [7:0] fifo_rd_data , //读数据
output reg fifo_rd_en //读使能
);
reg almost_full_d0;
reg almost_full_d1;
reg [1:0] state; //状态机的状态
reg [3:0] dly_cnt; //延迟计数器
wire syn;
assign syn = ~almost_full_d1 && almost_full_d0 ;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
almost_full_d0 <= 1'b0;
almost_full_d1 <= 1'b0;
end
else begin
almost_full_d0 <= almost_full;
almost_full_d1 <= almost_full_d0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
fifo_rd_en <= 1'b0;
state <= 2'b0;
dly_cnt <= 4'b0;
end
else case(state)
2'd0 : begin
if(syn)
state <= 2'd1;
else begin
state <= state;
end
end
2'd1 : begin
if(dly_cnt == 4'd10) begin
dly_cnt <= 4'd0;
state <= 2'd2;
end
else begin
dly_cnt <= dly_cnt + 4'd1;
end
end
2'd2 : begin
if (almost_empty) begin
fifo_rd_en <= 1'b0;
state <= 2'd0;
end
else begin
fifo_rd_en <= 1'b1;
end
end
default : state <= 2'd0 ;
endcase
end
endmodule
//写模块
module fifo_wr (
input sys_clk,
input sys_rst_n,
input almost_full , //写模块的即将满信号 在下一个周期满
input almost_empty, //读模块的即将空信号
output reg fifo_wr_en , //写使能
output reg [7:0] fifo_wr_data //写数据
);
reg almost_empty_d0;
reg almost_empty_d1;
reg [1:0] state; //状态机的状态
reg [3:0] dly_cnt; //延迟计数器
wire syn;
assign syn = ~almost_empty_d1 && almost_empty_d0 ;
//抓取即将空的上升沿
//因为 almost_empty 信号是属于 FIFO 读时钟域的
//所以要将其同步到写时钟域中
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
almost_empty_d0 <= 1'b0;
almost_empty_d1 <= 1'b0;
end
else begin
almost_empty_d0 <= almost_empty;
almost_empty_d1 <= almost_empty_d0;
end
end
//状态机控制写:当即将为空时,为防止数据丢失不能立即写,而是延时10个周期
//当写的时候,一直保持写使能为高电平,写满时写使能改为低电平并将状态机恢复初始状态
//一个计数器dly_cnt记延迟的周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
// reset
fifo_wr_en <= 1'b0;
fifo_wr_data <= 8'b0;
state <= 2'b0;
dly_cnt <= 4'b0;
end
else case(state)
2'd0 : begin
if(syn)
state <= 2'd1;
else begin
state <= state;
end
end
2'd1 : begin
if(dly_cnt == 4'd10) begin
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_wr_en <= 1'b0;
fifo_wr_data <= 8'b0;
state <= 2'd0;
end
else begin
fifo_wr_en <= 1'b1;
fifo_wr_data <= fifo_wr_data + 1'd1;
end
end
default : state <= 2'd0 ;
endcase
end
endmodule
tb文件
module tb_ip_fifo ();
//激励信号一般定义为 reg型
reg sys_clk;
reg sys_rst_n;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;
ip_fifo u_ip_fifo (
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n)
);
endmodule
仿真波形