1. FIFO IP核
FIFO全称是“first-in,first-out”,即先进先出,是一种组织和操作数据结构的方法。在硬件应用中,FIFO一般由一些读写指针,存储和控制的逻辑组成。存储可能是SRAM(static random access memory,静态随机存取存储器),触发器(flip-flops),锁存器(latch)等 [1]。FIFO通常会使用一些标志,比如full(满),empty(空),almost full(将满),almost empty(将空)等。在FIFO两端的数据读写行为可以处于相同或不同的时钟域,可因此将FIFO分类为同步FIFO和异步FIFO。异步FIFO经常被用来处理异步时钟域的多位数据的交流。
这里使用的开发板是基于Xilinx Artix 7系列,对应的开发工具是Vivado 2018.2,使用的FIFO IP核版本是FIFO Generator v13.2。
当前的FIFO IP核支持3种接口,分别是Native,AXI Memory Mapped和AXI Stream。如图一所示。这里使用Native。Native接口对数据缓冲,数据宽度转化,以及时钟域去耦合都进行了优化 [2]。
图一 FIFO 接口类型
图二是Native接口的信号框图。黑色的信号线表示必选项,蓝色和灰色的都是自选项。
图二 Native接口信号框图 [2]
FIFO 有四种可选的存储类型,分别为Built-in FIFO,Block RAM(random access memory,随机存取存储器),Shift Register和Distributed RAM。它们分别的特点如图三所示。Independent Clocks表示异步FIFO,Common Clock表示同步FIFO。加上独立或相同的时钟域,共有7种存储配置,如图四所示。
图三 FIFO存储类型 [2]
图四 FIFO存储配置 [2]
标准FIFO(standard FIFO)的读数据端会有一个时钟周期的延迟。而First-word Fall-Through(FWFT)可以使FIFO读数据端无时钟周期延迟。其作用原理为在不发出读操作的情况下向前浏览FIFO中可用的数据。换而言之,只要当FIFO中有数据,第一个可读的数据就会自动出现在数据输出线上 [2]。为了由浅入深地学习,先在这里使用Common clock distributed RAM的标准FIFO 端口。
ECC(通常指Error Correscting Code,这里指Error Injection and Correction,错误注入纠正)是一种有效的逻辑重构算法,可以在不增加,减少或替换逻辑门的情况下,在有可行的位置上构建相应的逻辑 [3]。
Block RAM和FIFO Macros中的Embedded Register Support有嵌入的输出寄存器来提高性能和增加流水线寄存器到宏。
图五所示为FIFO的数据端口设置,包括读写数据的宽度和存储深度,这里将读写数据设置为1个字节的宽度,FIFO存储深为256。Block RAM不支持 non-symmetric aspect ratios(非对称长宽比),也就是读写两端的数据宽度必须相等,无法做数据宽度的转换。
图五 FIFO数据端口设置
握手标志(valid,underflow,wr_ack ,和overflow)可以用来提供更多关于读写操作状态的信息,都为可选项。这里加上读数据端输出的Valid。Valid和数据输出是同步的,且其是单比特信号。在选项中, valid和write acknowledge都可以被设为active high/active low(当有数据输出时,valid被拉高/拉低)。这里选valid为active high。如图六所示。
图六 FIFO握手设置
图七是对FIFO设置的总结,可以看到是同步FIFO,存储类型为Distributed RAM,数据宽度为1个字节,深度为256。自选项将满/将空/编程满/编程空都没有添加。握手标志添加了valid。因为是标准的FIFO,读数据端有一个时钟周期的延时。
图七 FIFO设置总结
2. 仿真
接下来在仿真文件里例化刚刚生成的FIFO IP核,并简单的进行赋值。
module buffer_top_tb( ); //make a 50 MHz clock reg clk = 0; always #10 clk = ~clk; reg rst; reg [ 7: 0 ] fifo_in; reg fifo_wr = 0; reg rd_en = 0; wire [ 7: 0 ] fifo_out; wire valid; wire fifo_full; wire fifo_empty; initial begin rst = 0; fifo_wr = 0; rd_en = 0; fifo_in = 8'h60; end always @ ( posedge clk ) begin //这里的1 ns延时是为了模拟现实情况下D触发器(flip-flop)的D端到Q端用的时间 fifo_wr <= #1 1; fifo_in <= #1 fifo_in + 1; rd_en <= #1 1; end fifo_generator_0 your_instance_name ( .clk ( clk ), // input wire clk .din ( fifo_in ), // input wire [7 : 0] din .wr_en ( fifo_wr ), // input wire wr_en .full ( fifo_full ), // output wire full .rd_en ( rd_en ), // input wire rd_en .dout ( fifo_out ), // output wire [7 : 0] dout .empty ( fifo_empty ), // output wire empty .valid ( valid ), // output wire valid .srst ( rst ) // input wire srst ); endmodule
仿真图如图八所示。因为是同步FIFO(common clock),所以clk既是写数据时钟,也是读数据时钟。可以看到写使能(wr_en),读使能(rd_en),以及FIFO写入的数据(fifo_in)都是在时钟上升沿出现后1 ns才被立起来。这符合上文编写的仿真逻辑。在写入数据延迟一个时钟上升沿(于30 ns处),且读使能信号立起来后(于50 ns处),FIFO读数据(fifo_out)被立起来。这也符合上文对标准FIFO IP核的设定,即在FIFO IP核设置总结(图七)最后一栏的“read latency(from rising edge of read clock)”。
图八 同步标准FIFO仿真图
图九是FIFO IP核文档上的典型标准同步FIFO的读写图。可以看出,这里的读写使能,甚至FIFO读出来的数据都在时钟上升沿后有些微的延迟。同样也是在写入数据后(这里是写使能信号被立起来)延迟一个时钟周期,且读使能信号立起来后,FIFO才读出数据(dout由无效变为D0)。其典型读写行为与上文自主仿真结果相同。
图九 Standard FIFO Common Clock 读写图 [2]
3. 参考文献
[1]”FIFO (computing and electronics) | Wikiwand”, Wikiwand, 2021. [Online]. Available: https://www.wikiwand.com/en/FIFO_(computing_and_electronics). [Accessed: 12- Jan- 2021].
[2]Xilinx.com, 2021. [Online]. Available: https://www.xilinx.com/support/documentation/ip_documentation/fifo_generator/v13_2/pg057-fifo-generator.pdf. [Accessed: 13- Jan- 2021].
[3]Ching-Yi Huang, Daw-Ming Lee, Chun-Chi Lin and Chun-Yao Wang, “Error Injection & Correction: An efficient formal logic restructuring algorithm”, 2012 International SoC Design Conference (ISOCC), 2012. Available: 10.1109/isocc.2012.6407071 [Accessed 13 January 2021].
上面介绍BRAM不支持非对称长宽比,好像写错了,应该说的是DRAM