FIFO表示先入先出,是一种存储结构。可满足一下需求:
1、当输入数据速率和输出速率不匹配时。可作为临时存储单元。
2、用于不同时钟域之间的同步。
3、输入数据路径和输出数据路径之间的数据宽度不匹配时,可用于数据宽度调整电路。
同步FIFO
同步FIFO主要是空满信号的产生,
一般情况下写使能并且非满的情况下,写地址加1;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
waddr <= 'b0;
end else begin
if(winc & ~wfull)
waddr <= waddr + 1'b1;
end
end
读使能并且非空的情况下,读地址加1。
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
raddr <= 'b0;
end else begin
if(rinc & ~rempty)
raddr <= raddr + 1'b1;
end
end
因此空和满的信号就要根据读写地址的比较来产生。
当我们只写不读的时候,FIFO总会被填满,因此就产生满信号。当我们一直读的时候,FIFO的数据总会被读完,因此就产生空信号。
通常我们会把写地址和读地址的位宽设置比FIFO深度多1bit,这一bit就是用来判断产生空满的。(假设我们定义FIFO可以存8个数据,那么要设置 :
reg [3:0] waddr;
reg [3:0] raddr;
当waddr[2:0]==raddr[2:0] && waddr[3]==raddr[3](也可以直接写成waddr==raddr)时,说明FIFO里没有能读出的数据,因此产生空信号:
assign empty = (waddr==raddr)? 1:0;
当waddr[2:0]==raddr[2:0] && waddr[3]!=raddr[3]时,说明写地址正好比读地址大一个FIFO的存储深度,因此FIFO已无空位置可写,产生满信号:
assign wfull = (waddr=={~raddr[3],raddr[2:0]})? 1:0;
同步FIFO一般注意这两点就行。
异步FIFO
异步FIFO原理和同步FIFO一样,区别在于由于写时钟和读时钟的异步所带来的多bit数据同步问题。【这也是为什么要用格雷码,格雷码只有1bit数据变化,降低亚稳态概率】。
异步FIFO的读地址和写地址加1的操作和同步一样,只是注意always里时钟的区别:
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
waddr <= 'b0;
end else begin
if(winc && !wfull)
waddr <= waddr + 1'b1;
end
end
always @(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
raddr <= 'b0;
end else begin
if(rinc && !rempty)
raddr <= raddr + 1'b1;
end
end
在把 写地址 同步到 读时钟域 之前,需要将 写地址 转换成格雷码,减小亚稳态概率;同理,在把 读地址 同步到 写时钟域 之前,需要将 读地址 转换成格雷码,减小亚稳态概率。
assign raddr_gray = raddr ^ (raddr >> 1);
assign waddr_gray = waddr ^ (waddr >> 1);
转换完成后,剩下的就是同步问题,只需要打2拍即可。【用写时钟去采样读地址,用读时钟采样写地址。这些地址都是格雷码】
// 写时钟域,采样读地址
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
raddr_wclk <= 'b0;
raddr_wclk_1d <= 'b0;
end else begin
{raddr_wclk, raddr_wclk_1d} <= {raddr_wclk_1d, raddr_gray};
end
end
// 读时钟域,采样写地址
always @(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
waddr_rclk <= 'b0;
waddr_rclk_1d <= 'b0;
end else begin
{waddr_rclk, waddr_rclk_1d} <= {waddr_rclk_1d, waddr_gray};
end
end
至此:我们在写时钟域有了自己的原生的写地址的格雷码(waddr_gray )和同步过来的读地址(raddr_wclk);在读时钟域有了自己的原生的读地址的格雷码(raddr_gray )和同步过来的写地址(waddr_rclk)。
我们可以用其来判断满信号和空信号。由于格雷码和二进制码不同,因此比较方式稍有改变【最高位和次高位取反】:
assign rempty = (raddr_gray_r==waddr_rclk)? 1:0;
assign wfull = (waddr_gray_r=={~raddr_wclk[3:2],raddr_wclk[1:0]})? 1:0;
异步FIFO一般多注意这两点【格雷码和打2拍同步】就行。
另外不知道大家是否还有一个疑问,就是为什么要在读时钟域产生空信号,在写时钟域产生满信号,反过来行不行??
我们想一下:读时钟域所同步的写地址是延时了两个读时钟周期的,也就是说当读时钟得到了用于判断空信号的写地址时,实际上的写地址 是要比 读时钟所同步的写地址 大。因此当判断FIFO为空时,实际上FIFO里可能还有数据,这个数据是在读时钟用打2拍来同步写地址时,写时钟域写进去的数据。同样的道理:写时钟域所同步的读地址是延时了两个写时钟周期的,也就是说当写时钟得到了用于判断满信号的读地址时,实际上的读地址 是要比 写时钟所同步的读地址 大。因此当判断FIFO为满时,实际上FIFO里可能还有空位,这个空位是在写时钟用打2拍来同步读地址时,读时钟域又读出了几个数据所造成的空位。
但是这个并不会造成异步FIFO的功能问题,大不了多写两个数据或者多读两个数据就行。但是如果空满信号在错误的时钟域产生,就会造成异步FIFO功能错误。