简介
相信能看到此篇文字的朋友,对FIFO的作用已经不陌生了。
FIFO 的英文全称是 First In First Out,即先进先出。 常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。看到这有人要问了,这与我之前写的RAM有什么区别呢。
它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。
分类
- 根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO。
- 同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。
- 异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。
用途
- 同步 FIFO 常用于同步时钟的数据缓存, 异步 FIFO 常用于跨时钟域的数据信号的传递
思路
- FIFO 的宽度: FIFO 一次读写操作的数据位 N
- FIFO 的深度: FIFO 可以存储多少个宽度为 N 位的数据
- 空标志: empty。 FIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO中读出数据而造成无效数据的读出。
- 将空标志: almost_ empty。 FIFO 即将被读空。
- 满标志: full。 FIFO 已满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出。
- 将满标志: almost_full。 FIFO 即将被写满。
- 读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
- 写时钟:写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
“almost_ empty”和“almost_full”这两个信号分别被看作“empty”和“full”的警告信号,他们距离真正的空(empty)和满(full)都一个时钟的延时
代码
- 本设计中,采用的是Xilinx的一个8bit*256的异步双口FIFO。
IP_FIFO,顶层文件:
module ip_fifo(
input sys_clk,
input sys_rstn
);
wire fifo_wr_en; //FIFO写使能信号
wire fifo_rd_en; //FIFO读使能信号?
wire [7:0] fifo_din; //fifo输入信号线?
wire [7:0] fifo_dout; //fifo输出信号线?
wire almost_full; //fifo将满
wire almost_empty; //fifo将空
wire fifo_full; //fifo满?
wire fifo_empty; //fifo空?
wire [7:0] fifo_wr_data_count; // FIFO写时钟域计数?
wire [7:0] fifo_rd_data_count; // FIFO读时钟域计数
//例化FIFO核
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_din), // input wire [7 : 0] din
.dout (fifo_dout), // output wire [7 : 0] dout
.wr_en (fifo_wr_en), // input wire wr_en
.rd_en (fifo_rd_en), // input wire rd_en
.full (fifo_full), // output wire full
.empty (fifo_empty), // output wire empty
.almost_full (almost_full), // output wire almost_full
.almost_empty (almost_empty), // output wire almost_empty
.rd_data_count (fifo_rd_data_count), // output wire [7 : 0] rd_data_count
.wr_data_count (fifo_wr_data_count) // output wire [7 : 0] wr_data_count
);
//例化写FIFO模块
fifo_wr
u_fifo_wr (
.clk (sys_clk),
.rstn (sys_rstn),
.fifo_wr_en (fifo_wr_en),//fifo写请求
.fifo_wr_data (fifo_din),//fifo写数据
.almost_empty (almost_empty), //fifo将空信号
.almost_full (almost_full) //fifo将满信号
);
//例化读FIFO模块
fifo_rd
u_fifo_rd(
.clk (sys_clk),
.rst_n (sys_rstn),
.fifo_rd_en (fifo_rd_en), //fifo读请求
.fifo_rd_data (fifo_dout), //fifo读数据
.almost_empty (almost_empty), //fifo将空信号
.almost_full (almost_full) //fifo读满信号
);
endmodule
fifo_wr_control:写控制
module fifo_wr(
input wire clk,
input wire rstn,
input wire almost_empty, //将空信号
input wire almost_full, //将满信号
output reg fifo_wr_en, //FIFO写使能
output reg [7:0] fifo_wr_data //FIFO写入数据
);
//reg define
reg [1:0] state; //动作状态
reg almost_empty_d0; //almost_empty延迟1拍
reg almost_empty_d1; //almost_empty延迟2拍
reg [3:0] delay_cnt; //延迟计数器
//main code
//将fifo空信号同步到写时钟域中
always@(posedge clk) begin
if(!rstn) begin
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
//读出FIFO的数据
always@(posedge clk) begin
if(!rstn) begin
fifo_wr_en <= 1'b0;
fifo_wr_data <= 8'd0;
state <= 2'd0;
delay_cnt <= 4'd0;
end
else begin
case(state)
2'd0:begin
if(almost_empty_d1) begin //检测到FIFO将要被读空
state <= 2'd1; //就进入到延时状态
end
else begin
state <= state;
end
end
2'd1: begin
if(delay_cnt == 4'd10) begin //延时10拍
delay_cnt <= 4'd0; //FIFO IP核内部信号的更新存在延时,等待10拍
state <= 2'd2; //开始写操作
fifo_wr_en <= 1'b1; //打开写使能
end
else begin
delay_cnt <= delay_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
fifo_rd_control:读FIFO控制
module fifo_rd(
input wire clk, // 时钟信号
input wire rst_n, // 复位信号
input [7:0] fifo_rd_data, //从FIFO读出的数据
input almost_full, //FIFO将满信号
input almost_empty, //FIFO将空信号
output reg fifo_rd_en //FIFO读使能
);
//reg define
reg [1:0] state; //fsm
reg almost_full_d0; //almost_full延迟一拍
reg almost_full_d1; //almost_full延迟两拍
reg [3:0] delay_cnt; //延迟计数器
//--------------------main--------------------------//
//因为FIFO_full是写钟域的所以要将其同步到读时钟域中
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
almost_full_d0 <= 1'b0;
almost_full_d1 <= 1'b1;
end
else begin
almost_full_d0 <= almost_full;
almost_full_d1 <= almost_full_d0;
end
end
//读出FIFO的数据
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
fifo_rd_en <= 1'b0;
state <= 2'd0;
delay_cnt <= 4'd0;
end
else begin
case(state)
2'd0: begin
if(almost_full_d1) begin //检测到FIFO被写满
state <= 2'd1; //检测到进入延时状态
end
else begin
state <= state;
end
end
2'd1: begin
if(delay_cnt == 4'd10) begin //延迟10拍,等待FIFO IP核内部更新
delay_cnt <= 4'd10;
state <= 2'd2;
end
else begin
delay_cnt <= delay_cnt + 4'd1;
end
end
2'd2: begin
if(almost_empty) begin //检测到FIFO将被读空
fifo_rd_en <= 1'b0; //Read enable close
state <= 2'd0; //fsm into idle
end
else begin //FIFO not empty
fifo_rd_en <= 1'b1; //fifo read enbale
end
end
default: begin
state <= 2'd0; //defalut is idle
end
endcase
end
end
endmodule
Testbench:
module tb_fifo();
//input
reg system_clk;
reg sys_rst_n;
//UUT
ip_fifo
u_ip_fifo(
.sys_clk (system_clk),
.sys_rstn (sys_rst_n)
);
always begin
#20
system_clk = ~system_clk;
end
initial begin
//input
sys_rst_n = 1'b0;
system_clk = 1'b0;
#100;
sys_rst_n = 1'b1;
end
endmodule