打印

异步FIFO那点事

[复制链接]
9057|44
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 GoldSunMonkey 于 2011-10-18 18:41 编辑

设计一个FIFO是FPGA设计者遇到的最普遍的问题之一。本文着重介绍怎样设计FIFO——
这是一个看似简单却很复杂的任务。
一开始,要注意,FIFO通常用于时钟域的过渡,是双时钟设计。换句话说,设计工程要处理
(work off)两个时钟,因此在大多数情况下,FIFO工作于独立的两个时钟之间。然而,
我们不从这样的结构开始介绍—我们将从工作在单时钟的一个FIFO特例开始。
虽然工作在同一时钟的FIFO在实际应用中很少用到,但它为更多的复杂设计搭建一个平台,
这是非常有用的。然后再从特例推广到更为普通的FIFO,该系列**包括以下内容:
1.单时钟结构
2.双时钟结构——双钟结构1
3.双时钟结构——双钟结构2
4.双时钟结构——双钟结构3
5.脉冲模式FIFO

相关帖子

沙发
GoldSunMonkey|  楼主 | 2011-10-18 18:26 | 只看该作者
单时钟FIFO特例
FIFO有很多种结构,包括波浪型(ripple)FIFO,移位寄存器型以及其他
一些我们并不关心的结构类型。我们将集中讨论包含RAM存储器的结构类型。
其结构如图1所示。

使用特权

评论回复
板凳
GoldSunMonkey|  楼主 | 2011-10-18 18:27 | 只看该作者
单时钟FIFO特例
FIFO有很多种结构,包括波浪型(ripple)FIFO,移位寄存器型以及其他
一些我们并不关心的结构类型。我们将集中讨论包含RAM存储器的结构类型。
其结构如图1所示。

使用特权

评论回复
地板
GoldSunMonkey|  楼主 | 2011-10-18 18:28 | 只看该作者
通过分析,我们看到图中有一个具有独立的读端口和独立的写端口的RAM存储器。
这样选择是为了分析方便。如果是一个单端口的存储器,还应包含一个仲裁器保证
同一时刻只能进行一项操作(读或写),我们选择双口RAM(无需真正的双口RAM,
因为我们只是希望有一个简单的相互独立的读写端口)是因为这些实例非常接近实际情况。
读、写端口拥有又两个计数器产生的宽度为log2(array_size)的互相独立的读、写地址。
数据宽度是一个非常重要的参数将在在稍后的结构选择时予以介绍,而现在我们不必
过分的关心它。为了一致,我们称这些计数器为“读指针”(read pointer)和“写指针”
(write pointer)。写指针指向下一个将要写入的位置,读指针指向下一个将要读取的位置。
每次写操作使写指针加1,读操作使读指针加1。
我们看到最下面的模块为“状态”(stauts) 模块。
这个模块的任务实给FIFO提供“空”(empty)和“满”(full)信号。
这些信号告诉外部电路FIFO已经达到了临界条件:如果出现“满”信号,
那么FIFO为写操作的临界状态,如果出现“空”信号,则FIFO为读操作的临界状态。
写操作的临界状态(“full is active”)表示FIFO已经没有空间来存储更多的数据,
读操作的临界表示FIFO没有更多的数据可以读出。status模块还可告诉FIFO中“满”或“空”位置的数值。这是由指针的算术运算来完成了。

使用特权

评论回复
5
GoldSunMonkey|  楼主 | 2011-10-18 18:31 | 只看该作者
实际的“满”或“空”位置计算并不是为FIFO自身提供的。它是作为一个
报告机构给外部电路用的。但是,“满”和“空”信号在FIFO中却扮演着
非常重要的角色,它为了能实现读与写操作各自的独立运行而阻塞性的管
理数据的存取。这种阻塞性管理的重要性不是将数据复写(或重读),而
是指针位置可以控制整个FIFO,并且使读、写操作改变着指针数值。如果
我们不阻止指针在临界状态下改变状态,FIFO还能都一边“吃”着数据一
边“产生”数据,这简直是不可能的。
进一步分析:DPRAM若能够寄存读出的信号,这意味着存储器的输出数据已
被寄存。如果这样的话,读指针将不得不设计成“read 并加1 ”,也就是说
在FIFO输出数据有效之前,必须提供一个明确的读信号。另一方面,如果
DPRAM没有寄存输出,一旦写入有效数据就可以读出;先读数据,然后
使指针加1。这将影响到从FIFO读出数据和实现空/满计算的逻辑。由于
简化的缘故,我们仅论述DPRAM没有提供索锁存输出的情况。同理,将其
推广到寄存输出的DPRAM并不是很复杂。
从功能上看,FIFO工作原理如下所述:复位时,读、写指针均为0。
这是FIFO的空状态,空标志为高电平,(我们用高电平表示空标志)
此时满标志为低电平。当FIFO出现空标志时,不允许读操作,只能允许写操作。
写操作写入到位置0,并使写指针加1。此时,空标志变为低电平。假设没有发生读
操作而且随后的一段时间FIFO中只有写操作。一定时间后,写指针的值等于array_size-1。
这就意味着在存储器中,要写入数据的最后一个位置就是下一个位置。
在这种情况下,写操作将写指针变为0,并将输出满标志。
注意,在这种情况下,写指针和读指针是相等的,但是FIFO已满,而不是空。
这意味着“满”或“空”的决定并不是仅仅基于指针的值,而是基于引起指
针值相等的操作。如果指针值相等的原因是复位或者读操作,FIFO认为是空;
如果原因是写操作,那么FIFO认为是满。
现在,假设我们开始一系列的读操作,每次读操作都将增加读指针的值,
直到读指针的位置等于array_size-1。在该点,从这个位置读出的FIFO输
出总线上的数据是有效的。随后的逻辑读取这些数据并提供一个读信号
(在一个时钟周期内有效)。这将导致读指针再次等于写指针
(在两个指针走完存储器一圈后)。然而,由于这次相等是由于
一个读操作,将会输出空标志。
因此,我们将得到如下的空标志:写操作无条件的清除空标志。
Read pointer=(array_size-1) , 读操作置空标志。
以及如下的满标志:读操作无条件的清除满标志,
Write pointer= (array_size-1), 写操作置满标志。
然而,这是一个特殊的例子,由于一般情况下,读操作在FIFO不是空的情
况下就开始了(读操作逻辑不需要等待FIFO变满),因此这些条件不得不修改来存储读指针和写指针的每一个值。

使用特权

评论回复
6
GoldSunMonkey|  楼主 | 2011-10-18 18:31 | 只看该作者
有这样一个想法,那就是我们可以将存储器组织成一个环形列表。
因此,如果写指针与读指针差值大于1或更多,就进行读操作,
FIFO为空,这种工作方式对于用无符号(n-bit)结构来描述的
临界状态非常适合。同样的,如果读指针与写指针的差值大于1,
就进行写操作,直到FIFO为满。
这将带来如下的条件:
写操作无条件的清除空标志。
write_pointer=(read_pointer+1),读操作置空。
读操作无条件的清除满标志,
read_pointer= (write_pointer+1),写操作置满。
注意,读操作和写操作同时都在使其指针增加,
但不改变空标志和满标志的状态。在空或满的临界状态同时读操作和写操作都是不允许的。
综上所述,我们现在能够定义FIFO的status模块,
这里提供了用VHDL编写的代码,由于是同步的,很容易转换成Verilog HDL代码。

使用特权

评论回复
7
GoldSunMonkey|  楼主 | 2011-10-18 18:32 | 只看该作者
library IEEE, STD;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.std_logic_unsigned.all;
entity status is
port (reset : in std_logic;
clk : in std_logic;
fifo_wr : in std_logic;
fifo_rd : in std_logic;
valid_rd : out std_logic;
valid_wr : out std_logic;
rd_ptr : out std_logic_vector(4 downto 0);
wr_ptr : out std_logic_vector(4 downto 0);
empty : out std_logic;
full : out std_logic
);
end status;
architecture status_A of status is
signal rd_ptr_s : std_logic_vector(4 downto 0);
signal wr_ptr_s : std_logic_vector(4 downto 0);
signal valid_rd_s : std_logic;
signal valid_wr_s : std_logic;
begin
empty_P : process(clk, reset)
begin
if (reset = '1') then
empty <= '1';
elsif (clk'event and clk = '1') then
if (fifo_wr = '1' and fifo_rd = '1') then
-- do nothing
null;
elsif (fifo_wr = '1') then
-- write unconditionally clears empty
empty <= '0';
elsif (fifo_rd = '1' and (wr_ptr_s = rd_ptr_s + '1')) then
-- set empty
empty <= '1';
end if;
end if;
end process;
full_P : process(clk, reset)
begin
if (reset = '1') then
full <= '0';
elsif (clk'event and clk = '1') then
if (fifo_rd = '1' and fifo_wr = '1') then
-- do nothing
null;
elsif (fifo_rd = '1') then
-- read unconditionally clears full
full <= '0';
elsif (fifo_wr = '1' and (rd_ptr_s = wr_ptr_s + '1')) then
-- set full
full <= '1';
end if;
end if;
end process;
valid_rd_s <= '1' when (empty = '0' and fifo_rd = '1');
valid_wr_s <= '1' when (full = '0' and fifo_wr = '1');
wr_ptr_s_P : process(clk, reset)
begin
if (reset = '1') then
wr_ptr_s_P <= (others => '0');
elsif (clk'event and clk = '1') then
if (valid_wr_s = '1') then
wr_ptr_s <= wr_ptr_s + '1';
end if;
end if;
end process;
rd_ptr_s_P : process(clk, reset)
begin
if (reset = '1') then
rd_ptr_s_P <= (others => '0');
elsif (clk'event and clk = '1') then
if (valid_rd_s = '1') then
rd_ptr_s <= rd_ptr_s + '1';
end if;
end if;
end process;
rd_ptr <= rd_ptr_s;
wr_ptr <= wr_ptr_s;
end status_A;

使用特权

评论回复
8
GoldSunMonkey|  楼主 | 2011-10-18 18:33 | 只看该作者
电路图如图2所示:
细心的兄弟会注意到图2中产生满或空标志需要同时用到两个指针。
在双时钟设计的情况下,希望用读指针处理(work off) 读时钟,
写指针处理(work off) 写时钟。这会引起不希望发生的毛刺问题
——自己可以去试一试,看一看。这些问题以及一些解决方案将
在后续的该系列**中提及。

使用特权

评论回复
9
GoldSunMonkey|  楼主 | 2011-10-18 18:34 | 只看该作者
在先前的该系列**中,我们看到了怎样用双端口、无寄存器输出的RAM设计同步FIFO。
这部分我们将探讨同样的概念,并将其推广到怎样产生具有相互独立、自由工作的读、
写时钟的FIFO。拥有自由工作时钟简化了很多问题,但是这导致了一个特殊情况下的解决方法。
普通情况下不对时钟进行假设,甚至不假设其自由工作。
我将在本系列**的最后一部份讨论最普通的情况。

使用特权

评论回复
10
GoldSunMonkey|  楼主 | 2011-10-18 18:35 | 只看该作者
如果你看过先前的**,你会发现只有status模块工作在两个时钟。存储器没有寄存输出,
所以它确实不需要用读时钟;即使它是寄存输出,也可毫无问题的运行于读时钟上。
Status模块本质的功能是对两个指针进行操作,而且这两个指针工作在不同的时钟域。
这也是真正的困难所在。如果打算用写时钟来取样读指针或用读时钟来取样写指针,
将不可避免的遇到一个问题:亚稳态。它将导致空/满标志的计算错误,并导致设计的失败。

使用特权

评论回复
11
GoldSunMonkey|  楼主 | 2011-10-18 18:35 | 只看该作者
1 亚稳态
接下来我们将系统地开始探讨亚稳态,并解决由亚稳态产生的问题。
首先我们来了解一下什么是亚稳态。亚稳态是一种物理现象的名称,
它发生在一个事件试图取样[1](sample)另一事件的时候。
亚稳态可以描述如下:假设一个信号在t = 0时刻瞬间从0变为1,
那么信号在t = 0时刻的值究竟是多少?是0还是1,或者在两者之间?
在亚稳态中,这个问题被定义的两个时刻回避了,分别是0-和0+。
在t = 0-时刻,规定信号的取值为0,t = 0+ 时刻规定信号的取值为1。
显然,0- = 0 - 0,0+ =0 + 0。注意,这仅仅是一个数学定义,
如果你正在用实际的电路做同样的事,输出将有可能是逻辑0(0伏)
或者逻辑1(5伏),或者是介于0 ~5伏中间的某个值。正如在数学中描述的一样,
物理系统中一个事件取样另一个事件产生了不可预知的结果。
不可预知性也就意味着另一个迹象——亚稳态很危险。

使用特权

评论回复
12
GoldSunMonkey|  楼主 | 2011-10-18 18:36 | 只看该作者
1.1 时间分辨率(Resolution Time 翻转时间?)
当一个事件取样一个稳定值时(或者一个能稳定一段时间的值),取样值就随这个稳定值而变化。假设在D触发器情况下,就是Q值随D值变化。
这段能够稳定取样的时间用相关的取样事件来定义,称之为时间分辨率(翻转时间?)。也就是我们所熟悉的“clock-to Q time”,或tcq。
如果遇到触发器的setup time[2] 和hold time[2],这将是cell设计者保证输入能够正确变为输出的时间。亚稳态影响物理系统的时间分辨率,
同样也影响输出值。在“不稳定平衡”情况下考虑这些问题,就像“山上的球”(或者球面上的球体)你不知道它会向哪个方向滚,这个球
就处于不稳定平衡状态。如果球完全不受干扰,它有可能一直呆在原地,但是微小的晃动会使球滚到山的一边或另一边。这将无法计算球从
山上滚下的距离,或者无法计算球从山的哪一边滚下来。这就是亚稳态的一个准确的例子——你无法预知物理系统输出的值将会变成什么样,
多久会变化,并且相当危险。换句话说,输出永远保持一个有限非零概率的亚稳态。在现实中,尽管很少有这种情况发生,但20倍的clock-to Q
时间是一个合理的时间分辨率数值。在理论上,当取样操作接近被取样事件的时候,时间分辨率是无限的渐进曲线。

使用特权

评论回复
13
GoldSunMonkey|  楼主 | 2011-10-18 18:37 | 只看该作者
1.2 MTBF(平均无故障时间)与可靠性
如果一个设计中包含同步组件,无论是否愿意它都会出现亚稳态。亚稳态无法彻底消除,因此我们所做的就是计算错误概率以及在时间上来描述它。
让我们来看一下,假设这里有一个物理系统亚稳态错误发生的概率为1/1000。换句话说,每一千次采样就会因为亚稳态发生一次错误。这也意味着,
每一千次,输出就会在下一个时钟沿到来时,无法变化。如果时钟频率为1KHz,那么每秒都会有一次错误出现,MTBF值就为1秒。当然,这个假设
过于简单;MTBF是一种故障概率的统计度量,并且需要更为复杂、经验化、实验化的数据来计算。对于触发器来说,这种关系依赖于电路自身的物
理常数和时钟频率,记住亚稳态本身与时钟没有任何关系,但是它和MTBF相关[3]。自然的,我们会说一个可靠性好的电路具有很高的MTBF值。
1.3 同步
由于亚稳态无法彻底避免,在设计电路时一定要——
·很好的处理错误。
·将错误发生的概率降到最低。

使用特权

评论回复
14
GoldSunMonkey|  楼主 | 2011-10-18 18:38 | 只看该作者
本帖最后由 GoldSunMonkey 于 2011-10-18 18:42 编辑

首先要求,设计与设计之间要有很大的区别,它并不在本文介绍的范围。第二个要求,使用“同步”技术。这种技术由两个触发器简单的组合
在一起如图1所示。仅当Q1的出现非常接近时钟沿的时候,Q2才会进入亚稳态。如果在亚稳态情况下我们将20倍的tcq作为时间分辨率,那么
时钟周期将为tclk = 20tcq+ tsetup。这说明经过20倍的clock-to Q时间,输出仍然随输入改变的概率大大减小。因此,在时钟沿到来时Q2没
有被改变的概率接近P2,这里P是第一级输出没有在时钟沿到来时随输入而改变的概率。这称为两级同步。当使用这个时钟频率下概率来计算
MTBF时, MTBF值会提高很多。如果愿意的话,可以通过三级同步进一步增加MTBF值。但这在实际中很少需要。
如图2所示,可以在电路中增加冗余的同步来很好的抵御亚稳态。在三冗余和等同于两冗余的状态下,最终的输出大部分(三分之二)可以计算
出来。在这个实例中,小尺寸的布局布线与器件的差异说明了如果一个同步器产生亚稳态错误,其他的两级也会产生亚稳态错误,所有的概率
将随之改变。这种技术仅在要求非常苛刻时候的用到。欲更多了解亚稳态与同步的知识,
请参阅Grosse,Debora的“Keep metastabiliy from killing your digital design”

使用特权

评论回复
15
GoldSunMonkey|  楼主 | 2011-10-18 18:42 | 只看该作者
2 采样计数器
2.1 同步:解决可靠性问题
现在我们回到有关FIFO的问题上来。如果要用时钟取样计数器的值,这相对于计数器时钟来说是异步的。因此,到最后不得不考虑计数器到底在哪
个范围变化,假定从 FFFF到0000。每个单独的位(bit)都处于亚稳态。这种变化意味着有可能读数为0000到ffff之间(包含两者)的任何可能的值。
当然这也说明该情况下FIFO将无法工作。同步可以保存处于亚稳态时的计数器取样,尽管看似很离谱但仍然可以得到取样值。换句话说,仅靠计数器
同步是不够的。
重要的是我们必须确保不是所有的计数器位(bits)同时改变。实际上,不得不保证每一次计数器的增加正好改变一位。这说明计数器变化时出现错
误的只可能有一位。如果计数器准备开始工作,那么至少需要一位的变化,这就是我们所能做的最好的办法。我们所需要的是用格雷码来表示的计数
器。这是因为格雷码是最小距离码[4],相邻码元之间的只有1位不同。
让我们来分析一下格雷码(GRAY)对于FIFO的指针设计有什么作用。首先,同步意味着计数器的取样值很少处于亚稳态,其次,我们取样的值最多
只会有一位发生错误。这就是说计数器的真实值从N-1变到N,那么无论是否发生错误读取的数不是N-1就是N,而不会是其它的值。由于在变化的那
一时刻,必须确定输出的值是多少,这对于读出计数器值来说是完全正确的举动。只要能够确定读出的值是旧还是新就可以了。出现其它值则是不对的。
如果进一步考虑,将会发现如果在改变值的瞬间取样计数器的值,两个答案(N-1,N)对于计数器的值都是正确的。

使用特权

评论回复
16
GoldSunMonkey|  楼主 | 2011-10-18 18:43 | 只看该作者
2.2 保守的[5]报告——很好的处理错误
了解了这么多,接下来分析一下怎样将这些知识用于FIFO的读写指针操作。人们通常希望知道FIFO是否为满。如果它满了,必须阻止写操作再次发生。这很关键,
因为当FIFO已满时,必须停止写指针加1。将(格雷码的)读指针与写时钟同步。因为每当同步读指针的时候,实际的读指针可能会变为不同的值。这意味着读指
针可能会是一个失效的值。如果是这样,从写操作的角度考虑会发生少读现象(相比实际情况),如果条件吻合,FIFO为满。实际上,FIFO可能未满,因为有可
能读操作发生,而从写操作的角度是“看不到”的。然而,我们只要阻止额外的写操作就OK了。如果当FIFO真的满了时我们不去阻止写操作将会出现错误。

使用特权

评论回复
17
GoldSunMonkey|  楼主 | 2011-10-18 18:44 | 只看该作者
同样的从读操作的角度看——实际上当FIFO 中还有一些数据时,读操作一方看到“被延迟的”写操作,可能会认为FIFO为空。这种情况读操作被阻止直到写操作
“变得可被读操作一方所看见”,它将不允许进一步的读操作。
上述被称为保守的报告。简而言之,当FIFO未满时,对于写操作一方报告称FIFO已满,当FIFO未空时,报告对读操作一方称FIFO已空。这种现象好比FIFO动态的
缩小了一点,这毫无坏处。在字节计算的情况下,我们用同样的技术,提供写操作一方的字数计算和读操作一方的字数计算。写操作一方计算的字数可能大于FIFO
中的真实字数。这已令人相当满意了,因为影响它的仅仅是允许其阻止下一步的写操作。同理,读操作一方字数计算会少于实际字数,那也没关系,只要确认不要
将写操作一方计算的字数用于读操作一方即可,反之亦然。
这种保守的报告机构在被同步的值中能很好的处理错误。事实上,即使取样的读指针值将处于亚稳态一段时间,其影响只是阻止写操作,使FIFO暂停写操作,而不
会引起数据错误。同理适于读操作。

使用特权

评论回复
18
GoldSunMonkey|  楼主 | 2011-10-18 18:44 | 只看该作者
3 结构1
3.1产生空/满标志的条件
记得上篇**中,我们提到了指针不是影响空/满标志唯一的条件。空标志的条件是由读操作引起的读写指针相等,满标志的条件是写操作引起的读写指针相等。
换句话说,要正确地产生空/满标志信号,需要用写时钟对读信号进行取样,同时用读时钟对写信号进行取样。这不同于球的游戏,因为我们不希望对时钟的频
率做出假设。设想一个10ns的写信号(100MHz)被一个1KHz的读时钟取样,若无脉冲宽度延展的话就不能这样做,而且这也意味着已知(或假设已知)两个
时钟之间具有某种关联。
当然,我们也不希望假设时钟之间有任何的关系。这就引起了分别围绕三种方法的问题,并且,它将引出我们即将讨论的三种不同的结构。第一种结构相当不错,
在下面描述。第二种结构也还行,但不是很好,第三种结构性能超强,但在在面积占用方面没有优势。选择哪种结构要根据自己的需求。

使用特权

评论回复
19
GoldSunMonkey|  楼主 | 2011-10-18 18:45 | 只看该作者
3.2 第一个方案
由于不可能设计出一个不考虑频率的满足脉冲采样的电路,通过对读/写指针的编码我们绕过了这个问题。构造一个指针宽度为N+1,深度为2N字节的FIFO。
为便方比较还可以将格雷码指针转换为二进制指针。
当(正被讨论的已被时钟同步的)指针的二进制码中最高位不一致而其它N位都相等时,FIFO为满。当(已经过二进制转换的)指针完全相等时,FIFO为空。
这也许不容易看出,因此让我们举个例子来分析一下。

使用特权

评论回复
20
GoldSunMonkey|  楼主 | 2011-10-18 18:46 | 只看该作者
思考一下一个深度为8字节的FIFO怎样工作(使用已转换为二进制的指针)
4
(译者注:FIFO_WIDTH=8,FIFO_DEPTH= 2N = 8,N = 3,指针宽度为N+1=4 )。起初rd_ptr_bin和wr_ptr_bin均为“0000”。
此时FIFO中写入8个字节的数据。wr_ptr_bin =“1000”,rd_ptr_bin=“0000”。当然,这就是满条件。现在,假设执行了8次的读操作
,使得rd_ptr_bin =“1000”,这就是空条件。另外的8次写操作将使wr_ptr_bin 等于“0000”,但rd_ptr_bin 仍然等于“1000”,因此
FIFO为满条件。
显然起始指针无需为“0000”。假设它为“0100”,并且FIFO为空,那么8个字节会使wr_ptr_bin =“1100”,, rd_ptr_bin 仍然为“0100”。
这又说明FIFO为满。
这个例子的意义就在它生动地说明了读/写指针怎样产生空/满标志的。我曾说过第一个方案是最好的?你到不如将其这种技术与同步FIFO一
起使用。它可以避免算数运算,提高FIFO的速度。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:                     2014, 追逐梦想

264

主题

17215

帖子

523

粉丝