【不止IP】First In First Out,FIFO核的使用
一、Vivado FIFO IP核的使用方法和注意事项
1、fifo类型主要分两种,即同步fifo和异步fifo。
当使用异步fifo时,尤其要注意一点,复位信号rst要和wr_clk保持同步,否则将无法对fifo进行有效复位,会出现写不进数等不正常的情况。
所以当复位信号为异步信号,且与wr_clk不同源时,要用wr_clk拍两下rst信号。
always @ (posedge wr_clk) begin rst_fifo0 <= aclr; rst_fifo1 <= rst_fifo0; rst_fifo2 <= rst_fifo1; rst_fifo3 <= rst_fifo2; end
以下介绍的fifo使用方法主要适用于同步fifo。
2、fifo核的两种读出模式:standard fifo、first word fall through,它们的功能和操作上有一些区别。
(1)Standard FIFO(标准FIFO):
在标准FIFO中,数据输入(写入)和数据输出(读取)是独立的操作。写入和读取操作是异步进行的,即它们可以在任何时刻进行。
在读取操作之前,FIFO必须至少有一个完整的数据字来供读取。否则,读取操作将被阻塞直到有数据可用。
标准FIFO可以支持数据的双向传输,即数据可以同时从FIFO读取和写入。
当选择Standard模式的时候,在读使能信号有效的下一个周期才能读出第一个有效的数据。
(2)First Word Through (FWT) FIFO(首字通过FIFO):
在FWT FIFO中,写入操作和读取操作之间存在一种特殊的关系。当第一个数据字从写入端输入FIFO时,它会立即通过FIFO进行读取。
在FWT FIFO中,读取操作是优先于写入操作的。当有数据可用时,读取操作优先获得数据,而不需要等待FIFO缓冲区填充完整。
FWT FIFO也支持数据的双向传输,即可以在读取操作和写入操作同时进行。
当选择FWT模式的时候,在读使能信号有效的第一个周期就能能读出第一个有效的数据; 这是因为在这种模式下,FIFO提前把数据已经准备到了数据输出总线上,等待都使能有效就输出到数据输出端口(组合逻辑),但在这种模式下,valid信号将会在复位后就保持有效,这一点要特别注意;
总结起来,标准FIFO和首字通过FIFO之间的主要区别在于数据的读取时机和操作的优先级。标准FIFO对读取和写入操作是异步的,而FWT FIFO在写入第一个数据时即可立即读取,读取操作具有优先级。
2、在配置IP核时,一般不需要”Enable safety circuit“。
3、在配置IP核时,一般不需要设置all most full/all most empty,使用半空半满的方式读写fifo。
二、FIFO的设计与开发技巧
1、FIFO的读写方式
正如前面提到的,一般不需要设置all most full/all most empty,使用半空半满的方式读写fifo。除了这种方法之外,还有非空即读的方式,乒乓FIFO(double buffer)的方式等。
(1)半空半满读写
用fifo的计数器控制,写半满时开始读,读半空时开始写,其它时间既读又写。
// 利用状态机对fifo进行半空半满的读写 module fifo_example; // 三段式状态机 always @(posedge clk100m) begin if(Rst == 1'b1) curr_state <= #TCQ_STA_IDLE; else curr_state <= #TCQ_next_state; end // Calculate the next state always @ ( * ) begin case(curr_state) STA_IDLE:begin if (full == 1'b0) // fifo IP核的性质,初始化状态full为1 next_state <= STA_WRITING; else next_state <= STA_IDLE; end STA_WRITING:begin if (wr_data_count > 8'd200) // 已写入200个数 next_state <= STA_READING; else next_state <= STA_WRITING; end STA_READING:begin if (rd_data_count < 6'd20) // 可读取的数小于20 next_state <= STA_WRITING; else next_state <= STA_READING; end default :begin next_state <= STA_IDLE; end endcase end // state output always @(posedge clk100m) begin if (Rst == 1'b1) begin wr_en <= #TCQ 1'b0; rd_en <= #TCQ 1'b0; din <= #TCQ 8'd0; din_r <= #TCQ 8'd0; end else case (curr_state) STA_IDLE: begin wr_en <= #TCQ 1'b0; rd_en <= #TCQ 1'b0; din <= #TCQ 8'd0; din_r <= #TCQ 8'd0; end STA_WRITING:begin wr_en <= #TCQ 1'b1; rd_en <= #TCQ 1'b0; if (din == 8'd255) din <= #TCQ 8'd0; else din <= #TCQ din + 8'd1; din_r <= #TCQ din; end STA_READING:begin wr_en <= #TCQ 1'b0; rd_en <= #TCQ 1'b1; end endcase end endmodule //fifo_example
(2)非空即读,即当empty拉低后就读出,如果只用一个异步时钟FIFO也能实现这个效果,但有可能导致数据断流,对于要保证fifo无缝衔接的设计,这种方法不可靠,所以推荐使用乒乓fifo的方式读写。需要乒乓fifo的场景如下:①异步fifo跨时钟域;②效率第一,非空即读;③要求不间断写(存),不间断读;④数据流的接口。
要注意异步fifo的读取速度要比写入速度快,否则就要添加更多的fifo来进行乒乓操作。
2、fifo的empty信号和full信号
对于fifo的empty信号,如果rden没有赋值(红色,悬空),那么empty信号在拉低后也会进入红色,因为“不知道有没有被读出”。
fifo的empty没有正常拉低时(一般发生于仿真过程中),可能是因为复位信号太短,或者复位后间隔时间不够长就写入数据;适当延长复位时间和工作到来时间。
初始化的fifo full信号是拉高的,写入数据要等full拉低后。
3、fifo的特殊应用场景
(1)以UDP转SDRAM的场景为例,PHY芯片过来的时钟、数据、有效信号等引脚灌入fifo进行隔离,把两个时钟域隔开,避免用该时钟直接作为UDP模块的时钟,否则会因为该时钟扇出太大,导致程序不稳定,例如改了两行代码程序就不能正常运行了。用fifo进行两个时钟域的隔离还有一个好处,无论前端时钟有多快都无所谓,只当隔离fifo满了以后再读出新的时钟,即“网速自适应”。