FIFO存储器件空满标志产生探究


设计难点

在探究如何产生FIFO的空满标志前,先来解决一个问题 : FIFO存储器件的空满标志产生有什么难点?

  1. 亚稳态问题

    在数字集成电路中,触发器要满足setup/hold的时间要求。当一个信号被寄存器锁存时,如果信号和时钟之间不满足这个要求,Q端的值是不确定的,并且在未知的时刻会固定到高电平或低电平,这个过程称为亚稳态. 对于我们主要关注的异步FIFO器件, 读写操作分别在两个时钟域中进行, 自然, 亚稳态问题对FIFO的空满标志产生有很大影响.

    亚稳态问题

    从图中我们可以看到, 在CLK自低到高后, Q对应的从0到1过程中, 首先在Tco时间内处于0, Tmet时间内又处于0-1震荡的过程中. 如果在Tco或者Tmet之间读取信号, 此时获取的Q值就是毫无意义的.同时对于异步FIFO, 读取数据需要跨时钟域, 很容易就出现了在Tco或者Tmet时读取信息的情况, 自然会导致读取出错.

  2. 能否及时产生空满标志

    空满标志产生时延

    容易想到, 产生空满标志需要新增很多如同步,比较等等环节的电路, 得到的空满标志的时间肯定是慢于导致空满的读写操作的, 中间会有一个时间间隔t, 如果这个时间间隔t大于一次读写需要的时间, 可能会导致在空/满后,依然有读/写操作被成功完成, 导致数据的覆盖.

    所以该如何解决这个问题呢?尽力提高空满标志产生的速度吗?

空满标志产生方法迭代

空满标志产生电路是异步FIFO中最难设计的一部分, 我们很难一次性做到最好, 不如我们从基础的思路开始, 逐步迭代, 看看我们最终设计的异步FIFO空满标志产生电路是什么.

第一种思路: 基于脉冲同步

基于脉冲同步的电路

如图, 在读写信号产生时, 读写信号分作两路, 一路将自己这端的计数器相应-1或者+1, 一路经过脉冲同步后将对面端计数器+1或者-1, 空满信号就在于两边的计数器值是否是0或者Depth了.

但是, 理想很丰满, 现实很骨感, 这个结构其实是不工作的. 问题的关键还是在于跨时钟域, 用另一个时钟域的信号来操作本时钟域的器件, 后果很严重.

第二种思路: 直接比较读写指针

直接比较读写指针

如上图, 主要思路是每次将对面的读写指针数据经过同步读出, 然后分别比较读指针和写指针. 然而问题来了! 好像无论是空还是满, 读写指针的值都是一样的啊! 这怎么区分是满了还是空了?

我们再来思考下读写指针是怎么变化的. 读写指针在最开始都是0, 指向初始位置, 然后双方各自按各自的速率增长, 到了Buffer最大处, 指针再次+1后, 就回到了最开始的位置, 即0. 所以很明显的, 我们知道了为什么无论是空还是满读写指针的值都是一样的了: 因为我们的指针是不完整的指针, 单看我们现在的指针, 怎么知道区分读写指针各自从最大到0变了几回呢? 指针加上复位次数才是我们需要的完整指针嘛. 所以给我们的指针加上复位了几次信息就好了. 这就好像有两个人在操场上跑步, 我们除了需要记录这两个人现在位于操场哪里, 还需要记录他们分别已经跑了多少圈才能比较他们现在是否跑的距离一样.

慢着! 我们只需要区分读写指针就行了, 而读写指针之间不可能相差两个轮回, 不需要完整记录所有的复位次数啊. 所以一般我们给指针增加一个额外的bit, 每次从最大值处+1时, 都给这个bit+1, 这时再比较指针就能区分是空还是满了. 显然, 如果两个指针额外的bit不相等, 说明两个指针折回的次数不相等, 这时处于满的状态, 如果额外的bit相等, 处于空的状态.

到目前为止, 我们好像还没有思考过我们之前提到的设计难点, 亚稳态问题如何解决呢? 两个时钟域的指针经过同步后进行比较, 后面的步骤都建立在同步不出错上, 可是,如果因为亚稳态问题, 我们用于比较的指针并非原本的指针, 比较岂不是又失去了意义.

为了克服异步情况下亚稳态带来的问题, 我们使用格雷码计数.

第二种思路增强: 采用格雷码计数

格雷码和二进制码最大区别在于 : 格雷码(循环二进制单位距离码)是任意两个相邻数的代码只有一位二进制数不同的编码.

采用格雷码计数

每次同步时, 格雷码表示的地址只发生一位bit改变, 所以就算出错, 也只有这位改变的bit出错, 出错也只是变成了没改变前的地址, 只相差一位, 带来的后果也不至于太严重.

还有一个问题, 我们的地址中还有一位作为折回的次数的表示, 如果带上这一位, 地址还是每次只改变一位吗? 不是的, 比如一个长度为8的FIFO, 在从最大到0的变化中, 由0100 变为 1000, 这时发生了两位bit变化, 为了克服这个问题, 我们可以给格雷码增加一位,用新的格雷码中第一位表示折回次数:

增加一位的格雷码

不过这相应的也带来一个问题, 需要在电路中增加很多格雷码转二进制码的电路. 这些新的电路又增加了转换的时延, 我们好像没有解决之前提到的需要减少时延的设计难点, 反而更恶化了. 接下来怎么改进呢?

第三种思路: 异步比较

异步比较

这种结构的思路是不经过同步, 直接通过异步比较器比较读写指针, 用由此产生的afull_n信号和aempty_n信号作为设置全和空触发器的信号.

异步比较法的关键是用异步比较的结果——信号的下降沿作为最终比较结果的复位信号.同时增加的两级锁存器也增加了一个时钟周期的延时输出,这样在满有效状态下,同时来自读写时钟的读写指令存在潜在的亚稳态问题就可以避免。在与传统的先将地址信号同步然后进行同步比较的方法相比,异步比较法简单、高效、节省版图面积,而且实现起来更简单。

这里的关键在于异步比较器如何实现, 如果实现起来很复杂, 这只不过是把之前的方法重复一遍罢了. 实际上这个方法是我从 Simulation and Synthesis Techniques for Asynchronous FIFO Design with Asynchronous Pointer Comparisons这篇论文里看到的, 如果想更仔细地了解这种架构, 可以参考这篇论文.

one more thing

空满标志产生迭代我们以及走完了, 但是, 好像还有一个问题没有解决啊: 空满标志能否及时产生? 为了解决这个问题, 全靠空满标志是不够的, 我们又引入了几乎满和几乎空标志, 来提前提醒读写端控制速度.