1. FIFO IP核设置
上一篇FIFO IP核在Vivado 2018.2下的使用(二)中简单的对First Word Fall Through FIFO IP核进行了介绍。以及选用了Common Clock Distributed RAM的时钟和储存类型。最后做了一个简单的仿真,经过分析后,仿真的波形与FIFO IP核文档上的典型波形相符合。
这篇文章会更深一步地学习FIFO读写数据端的时钟为Independent的情况,即异步FIFO。通过上次提到过的FIFO存储配置,如图1所示,可以看到在选用Distributed RAM的情况下,也可以不用Common Clock而是Independent Clock。
图1 FIFO存储配置 [1]
图2为异步FIFO的信号框图。可以看到FIFO将写数据域和读数据域完全隔离开。图中斜体显示的信号为可选项,正常显示的信号为必选项。例如 valid是读数据端的可选信号,wr_en是写信号端的b必选信号。
图2 异步FIFO框图 [1]
如图3所示为FIFO接口类型的调整。FIFO Implementation由 Common Clock Distributed RAM变为了Independent Clocks Distributed RAM
图3 FIFO接口类型
图4,图5FIFO数据端口和握手的设置和(一)一样,数据宽度为一个字节,深度为256。因为Distributed RAM存储类型不支持Non-symmetric Aspect Ratios,所以读写两端的数据宽度必须相等,无法做数据宽度的转换。握手信号目前也是只添加Valid信号,选为Active High,即当读数据端有数据输出时,Valid由低电平变为高电平。
图4 FIFO数据端口设置
图5 FIFO握手设置
图6有新添加的Data Count信号,输出当前FIFO里剩余的数据总数。注意这里有两个Data Count,一个是读数据端时钟域下的信号,另一个是写数据端时钟域下的信号。
图6 FIFO Data Count设置
FIFO IP核生成的总结如图7所示,关于各项设定的详细解释,可参考上文或上一篇文章(上文没有提到的设定都选择默认)。
图7 FIFO 设置总结
2. 读写异步时钟域设计
由于读写端分别由两个不同的时钟域控制,可能会产生一些问题,所以需要对与之前的同步FIFO中不一样的设计逻辑进行分析。
图8 FIFO接口类型
如图8所示,在设置FIFO为异步时,会有一个同步级别(Synchronization Stages)的选项。可选的范围为2-8,也就是说,读写端的指针必须要经过至少2级的同步才能使用。
图9 异步FIFO reset设置
图9为异步FIFO在Native Ports下Initilization的选项。如果勾选Enable Reset Synchronization,则将会在读写端实现异步复位,在例化FIFO IP核后,只会出现一个异步reset(信号rst),相对的,如果选择在读写端实现同步复位(不勾选Enable Reset Synchronization),在例化FIFO IP核后会出现两个在各自时钟域同步的reset(信号wr_rst/rd_rst)。实现异步复位(即勾选Enable Reset Synchronization)后,reset被同步到用于确保FIFO初始化为已知状态的时钟域。这个同步逻辑可以为核心逻辑提供适当的复位时序,从而避免毛刺和亚稳态 [1]。当实现信号wr_rst/rd_rst时(不勾选Enable Reset Synchronization),信号wr_rst/rd_rst被认为是在各自的时钟域下同步。也就是说,只要wr_rst被立起来,写数据端会一直处于复位状态;如果rd_rst被立起来,读数据端会一直处于复位状态。
图9中有个配置Full Flags Reset Value的选项,可为0或者1。这个是指当处于复位状态时, Full flag(标志满)值是为0还是为1。图10是当Full Flags Reset Value为1,rst信号只持续一个周期(读写时钟不同时,按更慢的时钟算)时,异步复位下的时序图。注意reset(或图10中的rst,复位信号)是边沿敏感信号,对电平不敏感 [1]。当rst信号被写数据端时钟的上升沿检测到后,需要3个写时钟才能完成正确的复位同步。而full(满),almost_full(将满), prog_full(可编程满)信号在异步复位信号被拉低后5个写时钟周期后才会被拉低,开始正常接收写操作。在此期间,full(满),almost_full(将满), prog_full(可编程满)信号都被拉高,来保证在复位状态时没有写操作的发生 [1]。
图10 Full Flags Reset Value为1时,异步复位下的时序图 [1]
同步复位时(不勾选Enable Reset Synchronization),FIFO 会分别有读写数据端的复位信号,rd_rst和wr_rst,各自对时钟域下的计数器,输出寄存器进行复位。因为在各自的时钟域下,这些信号都是相对同步的,所以不需要额外的同步逻辑。同步复位也需要至少持续一个周期(各自时钟域下)。为了避免发生不可预料的结果,在复位时,不要进行任何读写的操作。图11所示当读操作复位早于写操作复位时,从复位(读操作)开始到复位(写操作)结束,不进行任意读写操作。如果读操作复位和写操作复位顺序交换,即写操作复位早于读操作复位,与图11类似,从复位(写操作)开始到复位(读操作)结束,不进行任意读写操作。
图12 异步FIFO的时钟域[1]
图12为异步FIFO时钟域的实现(图示为选择Block RAM或Distributed RAM为内存的情况)。图中所示的读写计数器(read/write counter)也就是读写指针。通过二进制数和格雷码的转换来实现跨时钟域下的同步,以及实现用于计算状态标志(比如full,empty等)的逻辑 [1]。可以从图12中看出,有些信号只在相应的时钟域下有效。比如,wr_en只在写时钟域下有效,与读时钟域无关。而读写时钟域是相互独立的,没有任何的频率或者相位联系要求。
在读写端为异步时钟的情况下,空(empty),满(full)信号都是由比较读写计数器产生的。因为读写计数器分别位于读写时钟域,在比较之前需要将其进行同步处理。具体的同步处理由于涉及篇幅较长,在此略过。但是,异步时钟域中的空(empty),满(full)信号被称为Pessimistic Full/Empty(悲观的空/满标志)。之所以异步FIFO下的满标志被称为悲观的,是因为满标志被设立在假设读时钟域没有读操作的情况下,真实情况是如果在写时钟的上升沿将满标志立起来,在读操作和拉低满标志之间可能存在多个时钟周期。这个时候满标志被立起来,但可能FIFO不是真满,满标志被立起来只是由于对于无法准确预测的跨时钟域周期数量的保守行为[1]。同理,异步FIFO下的空标志也是被设立在假设写时钟域没有任何写操作的情况下。即使空标志被立起来,FIFO也不是真空,只是对跨时钟域周期数量不可预测的保守行为。总结来说,异步FIFO下悲观空/满标志对FIFO的功能不影响,可能会牺牲一点性能(可能并没有用完FIFO的存储深度)。
3. 仿真
module buffer_top_tb( ); //sepearte rd_clk and wr_clk reg rd_clk = 0; always #10 rd_clk = ~rd_clk; reg wr_clk = 0; always #28 wr_clk = ~wr_clk; reg rst; reg [ 7: 0 ] fifo_in; reg wr_en = 0; reg rd_en = 0; wire [ 7: 0 ] fifo_out; wire valid; wire fifo_full; wire fifo_empty; wire [ 7: 0 ] rd_data_count; wire [ 7: 0 ] wr_data_count; initial begin rst = 0; wr_en = 0; rd_en = 0; fifo_in = 8'h60; @ ( posedge wr_clk ) rst = 1; #250 @ ( posedge wr_clk ) rst = 0; end always @ ( posedge wr_clk ) begin //这里的1 ns延时是为了模拟现实情况下D触发器(flip-flop)的D端到Q端用的时间 #600; wr_en <= #1 1; // fifo_in <= #1 fifo_in + 1; #500; end always @ ( posedge wr_clk ) begin #50 fifo_in <= #1 fifo_in + 1; end always @ ( posedge rd_clk ) begin #600; rd_en <= #1 1; //这里的1 ns延时是为了模拟现实情况下D触发器(flip-flop)的D端到Q端用的时间 end fifo_generator_0 your_instance_name ( .rst ( rst ), // input wire rst .wr_clk ( wr_clk ), // input wire wr_clk .rd_clk ( rd_clk ), // input wire rd_clk .din ( fifo_in ), // input wire [7 : 0] din .wr_en ( wr_en ), // input wire wr_en .rd_en ( rd_en ), // input wire rd_en .dout ( fifo_out ), // output wire [7 : 0] dout .full ( fifo_full ), // output wire full .empty ( fifo_empty ), // output wire empty .valid ( valid ), // output wire valid .rd_data_count ( rd_data_count ), // output wire [7 : 0] rd_data_count .wr_data_count ( wr_data_count ) // output wire [7 : 0] wr_data_count ); endmodule
仿真图结果如图13所示。可以看到仿真结果中,在rst信号被拉低后的5个时钟周期后,fifo_full信号并没有被拉低(图示大概是在11个时钟周期后才被拉低),与Xilinx的FIFO IP核文档上所描述的不符。通过多次实验,并改变仿真的条件之后发现,即使交换读写端时钟的快慢,延长或者缩短rst信号的持续时间,改变wr_en/rd_en的拉高拉低时间,信号fifo_full仍然没有在预期的时间内被拉低。这里在访问Xilinx官网后仍然无法得出有效的解决方案,所以需要进一步的真实debug抓取信号,或者读者有任何建议,请在评论区留下宝贵的想法。
图13 异步复位仿真图
4. 参考文献
[1]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].
老师,关于异步复位,full信号等是在11个时钟后才拉低有没有可能是因为IP核首先需要对rst信号进行同步处理,同步处理也需要时间