FPGA Basis FIFO
想来想去,今天最开心的事情还是学习了Sunburst Design提供的论文《Simulation and Synthesis Techniques for Asynchronous FIFO Design》。内心很明白,Xilinx的IP以及XPM Macro确实好用,自己忽略了很多设计的细节, 如果真的需要自己设计一个FIFO,估计就凉了。借用大神的一句话,离开了平台,什么是属于你自己的?
下面按照论文的思路来复习FIFO是如何设计的。
介绍
异步FIFO的定义:读写不是同一个时钟的FIFO,称之为异步FIFO
论文中这段话说的挺好的,一个接近正确的设计在99%的时间工作正常,但是这种设计是最难debug的!其实停用有道理的,当认为不会出错的地方隐藏了bug,确实让人抓狂。
用途
在两个时钟域之间安全传输多比特的时候,使用异步FIFO实现。
异步FIFO实现的难点在于:
- 读写指针的控制
- 稳定的产生空满信号
介绍异步FIFO设计之前,作者首先顺带介绍了同步FIFO指针的控制
在同步FIFO中,读写在同一个时钟域,所以在同一个时间只存在以下3种情形:
operation mode | Situation |
---|---|
increment | Write but no Read |
decrement | Read but no Write |
hold | Write and Read / no read and no write |
作者承上启下提出,在异步FIFO设计中不能使用1个指针完成以上操作,因为存在2个时钟域。
异步FIFO的指针设计
下面作者开始重点介绍如何设计异步FIFO的指针设计
write pointer:永远指向下一个被写入的地址,在复位时清零
read pointer:指向当前从FIFO中被读出的内容,在复位时清理,FIFO为空,empty信号有效,读指针对应的数据无效。
当第一个数据写入到FIFO中,empty信号被clear,读指针依旧指向FIFO中的第一个数据,并把FIFO中第一个数据挂到输出端口上,接收逻辑可以不需要使用2个时钟周期完成读操作,有助于效率的提高。
FIFO为空的条件:读写指针相同
FIFO为满的条件:读写指针再次相同,即写指针写满一圈后又要和读指针相同。
通过在读写指针上增加额外1比特,当写指针到底后,MSB做乒乓操作。当读写指正的MSB相同时,表示读写操作经过了相同的权数,否认,必然是读领先写。
用n比特指针,实质上n-1比特用于表示FIFO存储的深度。
FIFO为空的条件MSB相同,且低n-1位相同
FIFO为满的条件MSB不同,且低n-1位相同
比较不同时钟域的读写指针,是时候格雷码出场了,特点
- 格雷码相邻码字只有1比特不同
- 格雷码用于统计2 ( ( (n)))个数的统计
Dual n-bit Gray code counter
同时产生n比特格雷码,起低n-1比特仍为格雷码
为什么要这么设计???
n比特格雷码的读写指针分别需要通过2级寄存器到对方的时钟域,但是其中低n-1比特的格雷码可以直接用来指示存储器的地址。
论文中给出了判断读空写满的代码,看起来非常简洁,美观。
module rptr_empty #(parameter ADDRSIZE = 4)(
output reg rempty ,
output reg [ADDRSIZE-1:0] raddr , // 给RAM的读地址
output reg [ADDRSIZE :0] rptr , //
input [ADDRSIZE :0] rq2_wptr, //经过2级寄存器同步的写指针
input rinc ,
input rclk ,
input rrst_n
);
reg [ADDRSIZE:0] rbin;
reg [ADDRSIZE:0] rgraynext, rbinnext;
//格雷码指针
always@(posedge rclk or negedge rrst_n)
if(!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
//存储空间的读指针
assign raddr = rbin[ADDRSIZE-1:0];
assign rbinnext = rbin + {rinc & ~rempty};
assign rgraynext = (rbinnext>>1)^rbinnext;
//FIFO为空的条件
assign rempty_val = (rgraynext = rq2_wptr);
always@(posedge rclk or negedge rrst_n)
if(!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val;
endmoudle
module wptr_full #(parameter ADDRSIZE = 4)(
output reg wfull ,
output [ADDRSIZE-1:0] waddr ,
output reg [ADDRSIZE :0] wptr ,
input [ADDRSIZE :0] wq2_rptr,//经过2级寄存器同步的写指针
input winc ,
input wclk ,
input wrst_n
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
//格雷码指针
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
//存储空间写地址
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext; //生成格雷码
//------------------------------------------------------------------
// 判断FIFO为满的3个条件
// assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
// (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
// (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
//------------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],
wq2_rptr[ADDRSIZE-2:0]});
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule