![](https://csdnimg.cn/release/download_crawler_static/86556975/bg1.jpg)
异 步 FIFO 结 构
(第一部分)
作者: Vijay A.Nebhrajani
翻译: Adam Luo
2006 年 7 月
设计一个 FIFO 是 ASIC 设计者遇到的最普遍的问题之一。本文着重介绍怎
样设计 FIFO——这是一个看似简单却很复杂的任务。
一开始,要注意,FIFO 通常用于时钟域的过渡,是双时钟设计。换句话说,
设计工程要处理(work off)两个时钟,因此在大多数情况下,FIFO 工作于独立
的两个时钟之间。然而,我们不从这样的结构开始介绍—我们将从工作在单时钟
的一个 FIFO 特例开始。虽然工作在同一时钟的 FIFO 在实际应用中很少用到,
但它为更多的复杂设计搭建一个平台,这是非常有用的。然后再从特例推广到更
为普通的 FIFO,该系列文章包括以下内容:
1.单时钟结构
2.双时钟结构——双钟结构 1
3.双时钟结构——双钟结构 2
4.双时钟结构——双钟结构 3
5.脉冲模式 FIFO
![](https://csdnimg.cn/release/download_crawler_static/86556975/bg2.jpg)
单时钟 FIFO 特例
FIFO 有很多种结构,包括波浪型(ripple)FIFO,移位寄存器型以及其他一
些我们并不关心的结构类型。我们将集中讨论包含 RAM 存储器的结构类型。其
结构如图 1 所示。
通过分析,我们看到图中有一个具有独立的读端口和独立的写端口的 RAM
存储器。这样选择是为了分析方便。如果是一个单端口的存储器,还应包含一个
仲裁器保证同一时刻只能进行一项操作(读或写 ),我们选择双口 RAM(无需
真正的双口 RAM,因为我们只是希望有一个简单的相互独立的读写端口)是因
为这些实例非常接近实际情况。
读、写端口拥有又两个计数器产生的宽度为 log
2
(array_size)的互相独立的读、
写地址。数据宽度是一个非常重要的参数将在在稍后的结构选择时予以介绍,而
现在我们不必过分的关心它。为了一致,我们称这些计数器为“读指针”(read
pointer)和“写指针”(write pointer)。写指针指向下一个将要写入的位置,读指
针指向下一个将要读取的位置。每次写操作使写指针加 1,读操作使读指针加 1。
我们看到最下面的模块为“状态”(stauts) 模块。这个模块的任务实给 FIFO
提供“空”(empty)和“满”(full)信号。这些信号告诉外部电路 FIFO 已经达
到了临界条件:如果出现“满”信号,那么 FIFO 为写操作的临界状态,如果出
现“空”信号,则 FIFO 为读操作的临界状态。写操作的临界状态(“full is active”)
表示 FIFO 已经没有空间来存储更多的数据,读操作的临界表示 FIFO 没有更多
![](https://csdnimg.cn/release/download_crawler_static/86556975/bg3.jpg)
的数据可以读出。status 模块还可告诉 FIFO 中“满”或“空”位置的数值。这
是由指针的算术运算来完成了。
实际的“满”或“空”位置计算并不是为 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 不是空的情
![](https://csdnimg.cn/release/download_crawler_static/86556975/bg4.jpg)
况下就开始了(读操作逻辑不需要等待 FIFO 变满),因此这些条件不得不修改
来存储读指针和写指针的每一个值。
有这样一个想法,那就是我们可以将存储器组织成一个环形列表。因此,如
果写指针与读指针差值大于 1 或更多,就进行读操作,FIFO 为空,这种工作方
式对于用无符号(n-bit)结构来描述的临界状态非常适合。同样的,如果读指针
与写指针的差值大于 1,就进行写操作,直到 FIFO 为满。
这将带来如下的条件:
写操作无条件的清除空标志。
write_pointer=(read_pointer+1),读操作置空。
读操作无条件的清除满标志,
read_pointer= (write_pointer+1),写操作置满。
注意,读操作和写操作同时都在使其指针增加,但不改变空标志和满标志的
状态。在空或满的临界状态同时读操作和写操作都是不允许的。
综上所述,我们现在能够定义 FIFO 的 status 模块,这里提供了用 VHDL 编
写的代码,由于是同步的,很容易转换成 Verilog HDL 代码。
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)
![](https://csdnimg.cn/release/download_crawler_static/86556975/bg5.jpg)
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;
评论0