FIFO系列:FPGA中FIFO的应用
完整工程已上传至CSDN:下载链接
- 同步FIFO设计
- 异步FIFO设计
- Vivado FIFO IP核的调用
一、概述
FIFO
📄FIFO(First In First Out)是一种先进先出的数据缓存器,先进入FIFO的数据会先输出。FIFO通常可分为同步FIFO和异步FIFO两类。
FIFO 设计的关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。
FIFO 本质上是由 (伪双口)RAM 加读写控制逻辑构成的。
FIFO的优点:没有读写地址线,顺序写入、读出数据,数据地址由内部读写指针自动+1完成,使用起来简单方便。
FIFO的缺点:不能像RAM或ROM那样读取或写入某个指定的地址。
同步FIFO
📄同步FIFO:其读写受同一时钟控制,一般用于数据的缓冲、传输速率匹配等。
参数 | 意义 |
---|---|
时钟 | 读写采用同一时钟 |
FIFO的宽度 | FIFO一次读写的数据位宽 |
FIFO的深度 | FIFO中存储的数据个数 |
空标志 | FIFO已空时发出空信号,以阻止读操作继续读取无效数据 |
满标志 | FIFO已满时发出满信号,以阻止写操作继续向FIFO中写数据而导致FIFO溢出 |
二、工作原理
2.1 工作时序
- 在写使能有效且FIFO非满时,对FIFO进行写;写地址由自增产生,将数据写入该地址 ,初始为0。
- 在读使能有效且FIFO非空时,对FIFO进行读;读地址由自增产生,将数据从该地址读出,初始为0。
2.2 空满判断
📄FIFO空满判断主要有计数器法和高位扩展法两种,这里先说明计数器法。
计数器法:添加一个计数器data_cnt
指示FIFO中数据的个数:
- 当写使能有效且FIFO非满时data_cnt+1;
- 当读使能有效且FIFO非空时data_cnt-1;
- 当读写使能同时有效时data_cnt不变;
- 当data_cnt==0时,FIFO读空,产生空标志;
- 当data_cnt==FIFO深度时,FIFO写满,产生满标志。
三、程序
`timescale 1ns / 1ps
module fifo_syn
#(parameter FIFO_WIDTH=8,
parameter FIFO_DEPTH=16)
(
input clk ,
input rstn ,
input wr_en ,
input rd_en ,
input [FIFO_WIDTH-1:0] din ,
output empty ,
output full ,
output reg [FIFO_WIDTH-1:0] dout
);
reg [$clog2(FIFO_DEPTH)-1:0] wr_addr;
reg [$clog2(FIFO_DEPTH)-1:0] rd_addr;
reg [$clog2(FIFO_DEPTH):0] data_cnt;//由于FIFO赋值和地址自增相差1周期,需多计数1周期
reg [FIFO_WIDTH-1:0] fifo [0:FIFO_DEPTH-1];
assign empty=(data_cnt==0) ? 1'b1 : 1'b0;
assign full=(data_cnt==FIFO_DEPTH) ? 1'b1: 1'b0;
//写FIFO
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
wr_addr<='b0;
end
else if(wr_en&&!full)begin
wr_addr<=wr_addr+1'b1;
fifo[wr_addr]<=din;
end
else begin
wr_addr<=wr_addr;
end
end
//读FIFO
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
rd_addr<='b0;
dout<='b0;
end
else if(rd_en&&!empty)begin
rd_addr<=rd_addr+1'b1;
dout<=fifo[rd_addr];
end
else begin
rd_addr<=rd_addr;
end
end
//数据状态变化
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
data_cnt<='b0;
end
else begin
case({wr_en,rd_en})
2'b10:
if(data_cnt!=FIFO_DEPTH)//空满时计数器不变
data_cnt<=data_cnt+1'b1;
2'b01:
if(data_cnt!=0)
data_cnt<=data_cnt-1'b1;
default:
data_cnt<=data_cnt;
endcase
end
end
endmodule
四、仿真
📄仿真中主要是产生读写使能信号和要写入的数据。这里编写几个简单的任务来产生读写使能信号,为保证数据稳定,在时钟的下降沿赋值。
`timescale 1ns / 1ps
module tb();
parameter FIFO_WIDTH=8;
reg clk;
reg rstn;
reg wr_en;
reg rd_en;
reg [FIFO_WIDTH-1:0] din;
wire empty;
wire full;
wire [FIFO_WIDTH-1:0] dout;
initial begin
clk=1'b0;
rstn=1'b0;
wr_en=1'b0;
rd_en=1'b0;
din='b0;
#15;
rstn=1'b1;
repeat(20)begin
wr_only;
end
repeat(20)begin
rd_only;
end
repeat(5)begin
wr_only;
end
repeat(5)begin
wr_rd;
end
end
always #10 clk=~clk;
//只写的任务
task wr_only;
begin
@(negedge clk)begin
wr_en=1'b1;
rd_en=1'b0;
din={$random}%(2^FIFO_WIDTH);//生成0~2^FIFO_WIDTH-1的随机数
end
end
endtask
//只读的任务
task rd_only;
begin
@(negedge clk)begin
rd_en=1'b1;
wr_en=1'b0;
end
end
endtask
//读写的任务
task wr_rd;
begin
@(negedge clk)begin
rd_en=1'b1;
wr_en=1'b1;
din={$random}%(2^FIFO_WIDTH);
end
end
endtask
fifo_syn fifo_syn_u
(
. clk (clk),
. rstn (rstn),
. wr_en (wr_en),
. rd_en (rd_en),
. din (din),
. empty (empty),
. full (full),
. dout (dout)
);
endmodule
{$random}%(2^FIFO_WIDTH)表示产生范围为0—2 ^ FIFO_WIDTH-1的随机数。
有关$random更详细的用法,可参考这篇文章: $random的用法详解