FIFO系列:FPGA中FIFO的应用
完整工程已上传至优快云:下载链接
- 同步FIFO设计
- 异步FIFO设计
- Vivado FIFO IP核的调用
一、概述
☀️异步FIFO是指读写受两个不同的时钟控制,一般用于数据的跨时钟域传递以及不同数据宽度的数据接口。
对于单bit信号跨时钟域可以使用简单的打拍进行同步,而对于多bit信号的跨时钟域传递,就需要使用异步FIFO了。
☀️异步FIFO的主要参数和同步FIFO基本相同,主要的区别的是异步FIFO有读时钟rd_clk和写时钟wr_clk两个时钟。
参数 | 意义 |
---|---|
读时钟 | —— |
写时钟 | —— |
FIFO的宽度 | FIFO一次读写的数据位宽 |
FIFO的深度 | FIFO中存储的数据个数 |
空标志 | FIFO已空时发出空信号,以阻止读操作继续读取无效数据 |
满标志 | FIFO已满时发出满信号,以阻止写操作继续向FIFO中写数据而导致FIFO溢出 |
二、空满判断
2.1 高位扩展法
☀️由于其读写时钟不同,无法用计数器法进行判断。
☀️高位扩展法:将读写地址变量扩展出一个最高位作为判断位,判断位和地址组成读指针变量rd_ptr和写指针变量wr_ptr
对于二进制地址数据,判断方法:
- 对于空标志,rd_ptr和wr_ptr所经过的路径相同,反应在变量数值上就是rd_ptr=wr_ptr ;
- 对于满标志,wr_ptr需要比rd_ptr多走一圈,反应在变量数值上就是rd_ptr和wr_ptr的最高位不同,而其它位相同。
2.2 读写指针同步
☀️由于读写过程由不同时钟控制,所以在比较读写指针前需要先对其进行时钟域同步。如果直接使用二进制数据进行同步的话,会出现多位数据同时跳变,这样就增大了发生错误的概率。
☀️使用格雷码可以降低竞争冒险以及亚稳态发生的概率,相邻的两个格雷码之间只有1位不同。即使发生亚稳态,最坏的情况也只是指针仍是其本身,把上一次的数据覆盖掉,而不会发生地址超前或滞后而导致空满判断错误。所以在同步之前需要先将二进制数据转化为格雷码。
异步FIFO中使用格雷码无法避免亚稳态,它的作用是即使发生亚稳态,FIFO也能正常判断空满。
2.3 二进制数和格雷码之间的转换
格雷码除了最高位外具有镜像对称的特点。
具体的转化公式如下:
格雷码转二进制:
二进制转格雷码:
☀️我们将二进制地址指针转化为对应的格雷码wr_gray和rd_gray,然后进行时钟同步:
- 在写时钟上升沿,将rd_gray打两拍得到rd_gray_d2。
- 在读时钟上升沿,将wr_gray打两拍得到wr_gray_d2。
☀️在转化为格雷码后,空满标志的判断标准将不同于二进制:
- 空标志:rd_gray==wr_gray_d2。
- 满标志:wr_gray和rd_gray_d2的最高位和次高位相反,而其它各位相等。
例如:对于深度为8的FIFO,其地址宽度为3,当rd_ptr的二进制数为0001时,如果是满状态的话wr_ptr应该为1001,参考上表,对应的格雷码分别为0001和1101,即最高位和次高位相反,其它各位相等。
三、异步FIFO的深度计算
☀️在跨时钟域数据传递用到异步FIFO时,往往需要计算FIFO的理论最小深度,而实际FIFO深度通常要大于计算值。
☀️现考虑这样的场景,假设模块A以一定的时钟频率不间断地向FIFO中写数据,而模块B以慢于A的时钟频率不间断地从FIFO中读取数据,如果系统一直工作,那么FIFO中的数据会越累积越多,需要FIFO的深度就是无穷大的,这并不现实。所以只有在突发传输过程中讨论FIFO的深度才有意义。要确定FIFO的深度,就要计算出在突发读写这段时间内有多少数据没有被读走。
突发传输:短时间内进行相对高带宽的数据传输
这里举一个简单的题:
问:
一个8bit位宽的FIFO,输入时钟是100MHz,输出时钟是50MHz,设计一个读写包文的缓存是2Kbit,两个包文间的发送时间间隔足够大,求异步FIFO的最小读写深度。
答:
- 发送包文的突发长度是 250 B y t e 250Byte 250Byte,每个地址存取1个 B y t e Byte Byte
- 输入时钟周期为 10 n s 10ns 10ns,输出时钟周期为 20 n s 20ns 20ns
- 前一模块发送包文,写入FIFO所用的时间为 2000 / 8 ∗ 10 = 2500 n s 2000/8*10=2500 ns 2000/8∗10=2500ns
- 在这段时间内,后一模块接收包文,从FIFO中读出读取的数据量为 2500 / 20 = 125 B y t e 2500/20=125Byte 2500/20=125Byte
- 异步FIFO理论最小深度为 250 B y t e − 125 B y t e = 125 B y t e 250Byte-125Byte=125Byte 250Byte−125Byte=125Byte
有关FIFO深度计算更细致的讲解,可参考该文章:FIFO深度计算
四、程序
module FIFO_asyn
#(parameter FIFO_WIDTH=8,
parameter FIFO_DEPTH=16)
(
input wclk ,
input rclk ,
input wr_rstn,
input rd_rstn,
input wr_en ,
input rd_en ,
input [FIFO_WIDTH-1:0] din ,
output empty ,
output full ,
output reg [FIFO_WIDTH-1:0] dout
);
localparam ADDR_LEN=$clog2(FIFO_DEPTH);
localparam PTR_LEN=ADDR_LEN+1;
//读写地址
wire [ADDR_LEN-1:0] wr_addr;
wire [ADDR_LEN-1:0] rd_addr;
//读写指针
reg [PTR_LEN-1:0] wr_ptr;
reg [PTR_LEN-1:0] rd_ptr;
//读写指针对应的格雷码
wire [PTR_LEN-1:0] wr_gray;
wire [PTR_LEN-1:0] rd_gray;
//打拍寄存器变量
reg [PTR_LEN-1:0] wr_gray_d1;
reg [PTR_LEN-1:0] wr_gray_d2;
reg [PTR_LEN-1:0] rd_gray_d1;
reg [PTR_LEN-1:0] rd_gray_d2;
//FIFO
reg [FIFO_WIDTH-1:0] fifo [0:FIFO_DEPTH-1];
//读写指针为1bit判断位和读写地址的拼接
assign wr_addr=wr_ptr[ADDR_LEN-1:0];
assign rd_addr=rd_ptr[ADDR_LEN-1:0];
//二进制转格雷码
assign wr_gray=(wr_ptr>>1)^wr_ptr;
assign rd_gray=(rd_ptr>>1)^rd_ptr;
//空满判断
assign empty=(wr_gray_d2==rd_gray) ? 1'b1:1'b0;
assign full=(wr_gray=={~rd_gray_d2[PTR_LEN-1:PTR_LEN-2],rd_gray_d2[PTR_LEN-3:0]})? 1'b1: 1'b0;
//写FIFO
always@(posedge wclk or negedge wr_rstn)begin
if(!wr_rstn)begin
wr_ptr<='b0;
end
else if(wr_en&&!full)begin
fifo[wr_addr]<=din;
wr_ptr<=wr_ptr+1'b1;
end
else begin
wr_ptr<=wr_ptr;
end
end
//读FIFO
always@(posedge rclk or negedge rd_rstn)begin
if(!rd_rstn)begin
rd_ptr<='b0;
dout<='b0;
end
else if(rd_en&&!empty)begin
dout<=fifo[rd_addr];
rd_ptr<=rd_ptr+1'b1;
end
else begin
rd_ptr<=rd_ptr;
dout<=dout;
end
end
//将写指针同步到读时钟域
always@(posedge rclk or negedge rd_rstn)begin
if(!rd_rstn)begin
wr_gray_d1<='b0;
wr_gray_d2<='b0;
end
else begin
wr_gray_d1<=wr_gray;
wr_gray_d2<=wr_gray_d1;
end
end
//将读指针同步到写时钟域
always@(posedge wclk or negedge wr_rstn)begin
if(!wr_rstn)begin
rd_gray_d1<='b0;
rd_gray_d2<='b0;
end
else begin
rd_gray_d1<=rd_gray;
rd_gray_d2<=rd_gray_d1;
end
end
endmodule
五、仿真
`timescale 1ns / 1ps
module tb();
parameter FIFO_WIDTH=8;
reg wclk;
reg rclk;
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
wclk=1'b0;
rclk=1'b0;
rstn=1'b0;
wr_en=1'b0;
rd_en=1'b0;
din='b0;
#15;
rstn=1'b1;
repeat(16)wr_only;
repeat(16)rd_only;
repeat(5)wr_rd;
#30;
wr_en=1'b0;
rd_en=1'b0;
$stop;
end
always #10 wclk=~wclk;
always #20 rclk=~rclk;
//只写的任务
task wr_only;
begin
@(negedge wclk)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 rclk)begin
rd_en=1'b1;
wr_en=1'b0;
end
end
endtask
//读写的任务
task wr_rd;
begin
@(negedge rclk)begin
rd_en=1'b1;
end
@(negedge wclk)begin
wr_en=1'b1;
din={$random}%(2^FIFO_WIDTH);
end
end
endtask
FIFO_asyn FIFO_asyn_u
(
. wclk (wclk),
. rclk (rclk),
. wr_rstn (rstn),
. rd_rstn (rstn),
. wr_en (wr_en),
. rd_en (rd_en),
. din (din),
. empty (empty),
. full (full),
. dout (dout)
);
endmodule