FPGA基础学习(11) -- FIFO设计(style#2)
在上一篇FIFO设计(stlye#1)中总结了论文《Simulation and Synthesis Techniques for Asynchronous FIFO Design》提出的FIFO设计的第一种方法,本篇博客总结第二种方法,源自论文《Simulation and Synthesis Techniques for Asynchronous FIFO Design with Asynchronous Pointer Comparisons》。
style#1提出了一种增加补位的格雷码作为指针标识,来判断FIFO是满还是空的方法。 这种方法起源于想要解决“不知道读指针敢上了写指针(空)还是写指针绕了一圈赶上了读指针(满)”的问题。实际上,为了分清空还是满,最重要的就是找准指针的来向。所以style#2就是用读、写指针的最高两位划分成4个象限,来识别读写指针的相对方向,从而正确的判断空满。
1. 空满判断
如下两图所示,分别示意了“即将满”和“即将空”,注意“即将”两字。以指针的最高两位00、01、11、10,分别表示第一、第二、第三和第四象限,在判断空满之前遵循以下两个原则:
- 写指针落后于读指针一个象限,表示即将满;
- 读指针落后于写指针一个象限,表示即将空;
说“即将”是表示指针追赶的方向,先有方向,当两指针相等之后,才会产生真正的空或满信号。
在确定了象限关系之后,会生成dirset_n和dirrst两个信号,用于产生指示指针追赶方向的direction信号,从而确定空满信号,如下图所示。
如下图,为style#2 FIFO的整体结构框图。
2. 分析
实际上style#2理解起来比style#1更简单,但是由于指针是进行的异步比较,并且有一些关键的组合电路,所以论文讨论时序、关键路径以及组合电路的各信号跳变上用了不少篇幅。
如上图所示,是产生full 和empty的电路图,其中曲线是代码async_cmp.v对应的原理图,后面两个两拍寄存器在相应的代码模块中。
2.1 工作原理
- 论文中说“aempty_n在rclk的上升沿有效,在wclk的上升沿失效”,这句好什么意思呢?因为是组合电路,所以要结合前级的时序电路和代码来分析。
先看代码:
module async_cmp #(parameter ADDRSIZE = 4)
(
aempty_n,
afull_n,
wptr,
rptr,
wrst_n
);
parameter N = ADDRSIZE-1;
output aempty_n, afull_n;
input [N:0] wptr, rptr;
input wrst_n;
reg direction;
wire high = 1'b1;
wire dirset_n = ~( (wptr[N]^rptr[N-1]) & ~(wptr[N-1]^rptr[N])); // wptr在rptr后一个象限,快要满了
wire dirclr_n = ~((~(wptr[N]^rptr[N-1]) & (wptr[N-1]^rptr[N])) | ~wrst_n); // wptr在rptr前一个象限,快要空了
/* always @(posedge high or negedge dirset_n or negedge dirclr_n)
if (!dirclr_n)
direction <= 1'b0;
else if (!dirset_n)
direction <= 1'b1;
else
direction <= high; */
always @(negedge dirset_n or negedge dirclr_n)
if (!dirclr_n) direction <= 1'b0;
else direction <= 1'b1;
assign aempty_n = ~((wptr == rptr) && !direction);
assign afull_n = ~((wptr == rptr) && direction);
endmodule
aempty_n表示FIFO空的使能信号,FIFO既然要为空,那么肯定是读指针“追上”写指针,那什么时候追上呢,肯定是在rclk驱动的读指针在rclk的上升沿出变化的值等于wptr,当然前提是要满足direction = 0的条件。
同理,什么时候空失效呢?肯定是写指针又继续抢先一步,写指针的变化是有wclk的上升沿触发的。
那么又有问题?aempty_n跟空信号有关,空信号又是控制读操作的,它必须和读时钟保存紧密的联系,但是aempty_n的失效却跟wclk有关,所以后面两拍rclk驱动寄存器实现了空信号与rclk的同步。注意:aempty_n同时连接寄存器的D端和set端。
同理可以分析afull_n。
只要结合指针的在各自时钟驱动下的变化,再结合象限指示信号dirset_n和dirclr_n,以及direction信号就可以理解各信号的变化。论文中也现象的对各信号的变化展开了分析。
- 有关复位
从电路图可以看出,复位只直接复位full信号,通过使读写指针相同(都置0),从而间接的使empty信号拉高,从而正确表征FIFO复位后为空。
2.2 时序及关键路径
-
如上面象限比较的电路所示,因为两指针是异步是异步时钟驱动,如果多位输入或多或少不同步的变化,可能导致输出有毛刺,但是采用格雷码编码则可以避免这个问题。
-
如下图表示出的有关正确产生empty和full的关键信号。
3. 问题及解答
同样这篇论文中,作者对几个关心的问题做了分析和解答,这也是深入理解style#2 FIFO的途径。
前面分析过aempty_n信号与rempty以及读时钟的基本关系,aempty_n作为两级同步寄存器的输入端和置位端,在rclk的驱动下,产生rempty信号。那么,之前我们又说过aempty_n在rclk的上升沿有效,在wclk的上升沿失效,因为rclk和wclk是异步时钟,在极端情况下,aempty_n刚刚有效(拉低),又迅速失效(拉高),形成一个毛刺(译为反向很短的脉冲,与竞争冒险产生的毛刺区分开),会不会出问题?作者给出了4个场景来说明这个。
- 毛刺没有被第一级同步寄存器捕获,故rempty没有产生。这不会导致问题。PS:对这个问题我是这么理解的,既然wclk上升沿迅速到来,并且读写指针不相等,那说明立马又写入了一个数据,这时候实际的FIFO不为空,并且下一个读操作还没到来,所以不会出现读空异常的情形;
- 毛刺置位了第一级寄存器,但是没有被第二级寄存器捕获,这种情形不可能出现。最终empty会在rclk的驱动下出现在第二级寄存器的输出端,所以也不会出问题。(我不是很能理解);
- 毛刺只置位了第二级寄存器,丢了第一级寄存器,这几乎不可能,只要关键路径的时序满足了设计要求,那么可以在output输出empty,至少会持续一个rclk周期,知道下一次第一级寄存器输出为0,从而失效empty,所以这也不是问题;
- 最有可能的情况是毛刺被两级寄存器都捕获,并因为两级同步的关系,被有效了2个rclk周期(但是要避免亚稳态,下面会讨论),所以这也不是问题。
针对上面四种场景,作者进一步说明的其中的一些时序考虑,如下:
aempty_n的毛刺不会对同步寄存器有影响,因为它本身就是靠rclk驱动的(与同步寄存器属于同时钟域),就算有毛刺,也只能是在rclk上升沿之后产生,并且不会持续到下一个rclk到来。
一个问题:假设写时钟的上升沿与读时钟的上升沿几乎在一起,并失效了aempty_n,导致第一级寄存器出现亚稳态,这会导致什么情况?
移除第二级寄存器的置位信号会违背第二级寄存器的恢复时间,会不会导致第二级寄存器出现亚稳态?作者认为不会,如果置位信号使第二级寄存器输出为高了,那么它的输入端也必为高(置位和输入均与aempty_n有关),所以它不会因为恢复时间而导致输出状态的不确定,即亚稳态(这一点实际上理解不是很深,只能略有感性体会)。
最后一个问题:一个毛刺,它失效于wclk的上升沿,与此同时,它失效时刻与第二级寄存器的rclk上升沿很接近,会不会导致第二级寄存器出现亚稳态?
答案是不会,前提是aempty_n的关键路径满足时序要求。因为aempty_n拉低到稳定状态,肯定只在rclk的第一个上升沿和第二个上升沿之间,所以这个毛刺必然在第二个上升沿之前到来。PS:言外之意就是第二个寄存器不会产生亚稳态。
同样可以这样分析full信号。
4. 仿真
本论文也给出了完整的源码,借用上一篇的测试程序,可以通过仿真图来理解上面分析的几个信号的关系。