01signal.com

FPGA FIFOs简介

本页是有关 FIFOs的五页系列中的第一页。

概述

FPGA FIFO 是一个具有简单概念的存储元件: 应用逻辑的一部分在 FIFO的一侧写入数据字。在 FIFO的另一侧,应用逻辑的另一部分以相同的顺序(FIFO = First In First Out)从中读取这些单词。

该数据存储在 FIFO内部。 FIFO的depth是它能够存储多少字的数据。宽度(即每个字的位数)和 depth 是用户为每 FIFO配置的参数。

FIFOs 大概是 FPGA 设计中最常用的 IP 了。每当逻辑的一部分生成数据而另一部分消耗数据时,想到的直接解决方案就是在它们之间放置一 FIFO (这并不是说 FIFO 总是正确的解决方案,当然......)。

对于熟悉 command-line 接口(特别是 UNIX / Linux)的人来说,可以将 FIFOs 与 pipes 和命令之间的使用进行比较: 一个程序的输出变成另一个程序的输入,其余的由它们之间的魔法机器处理。

由于它们的普遍使用,对于 FPGA FIFO 的行为方式存在事实上的协议。每 FPGA 开发软件都提供了一种生成 FIFO IP 模块以供应用程序的设计使用的方法。不仅如此,这款 FIFO 模块很可能拥有一组端口,其行为与任何其他 FPGA FIFO相同。

FPGA供应商提供的软件允许创建适合您特定需求的 FIFOs 。只需在某些图形用户界面(GUI)工具中配置其属性(宽度、 depth 和我将讨论的其他属性),其余的由工具负责。剩下的就是在设计中制作模块的例化(instantiation)。与 FPGA 世界中的许多其他任务不同,这个任务真的就这么简单。

由于每 FPGA 供应商都展示了自己的 FIFO IP,因此阅读文档至细则当然很重要。不同的 FPGA 供应商使用略有不同的术语来描述 FIFOs。模块的端口的名称也略有不同。此外,每个供应商都提供了一组略有不同的额外功能和配置选项。

也就是说,默认设置很可能与我所说的“baseline FIFO”相对应。最重要的是,肯定有一组始终可用的额外功能。

但是, FIFO 在逻辑阵列(logic fabric)中的实现因供应商而异,因此了解 FIFO属性的含义对于充分利用 FPGA的资源非常重要。

总而言之,了解和了解 FPGA FIFOs 是一次性的。一旦您学会了如何在一台 FPGA上使用它们,就很容易在另一台 FPGA上做同样的事情。这本身就是它们无处不在的原因。

baseline FIFO

FPGA FIFOs没有书面标准,但对于它们的行为方式仍有广泛的共识。

所有 FIFOs 都有两个接口,一个是写字,一个是读字。让我们看看我称之为“baseline FIFO”的例化。它有重要的变化,我稍后会谈到。

myfifo myfifo_ins
  (
   .rst(rst),       // Asynchronous reset input

   // Write interface ports
   .wr_clk(wr_clk), // Write clock input
   .wr_en(wr_en),   // Write Enable input
   .din(din),       // Write word input 
   .full(full),     // Full output

   // Read interface ports
   .rd_clk(rd_clk), // Read Clock input
   .rd_en(rd_en),   // Read Enable input
   .dout(dout),     // Read word output
   .empty(empty)    // Empty output
);

端口的名称是 Xilinx的工具使用的名称,但其他 FPGA 供应商使用类似的名称。

FIFO 模块的端口分为三组: 复位信号(reset signal)(@rst),稍后我会再谈。正如预期的那样,有一个写入接口和一个读取接口,每个接口由四个端口组成。

关于 @din 和 @dout,这是两个 vector 端口,它们携带进出 FIFO 的数据字。这些词的宽度是您在设置 FIFO时使用相关软件工具决定的。您还将设置 FIFO的 depth ,即它可以包含多少个单词。这两个参数会影响 FIFO 消耗的 FPGA内存资源量。

时钟

值得注意的是,这两个接口中的每一个都有自己的时钟: @wr_clk 和 @rd_clk。每个接口中的其他端口都与这两个时钟中的每一个同步。

FIFOs 通常用于将数据从一个时钟域(clock domain)移动到另一个时钟域,因为它们有两个时钟: 如果你的设计中的某个逻辑与 clk_A同步,而另一部分与 clk_B同步,如何使它们协同工作?任何 FPGA 工程师的第一个想法是在它们之间放置一 FIFO 。这主要是因为 crossing 时钟域的任务让人头疼,而使用 FIFO 可以轻松安全地解决问题。

写接口

写接口很简单: @wr_clk、 @wr_en 和 @din 是输入到 FIFO, @full 是输出(output)。

当 @wr_clk的上升沿(rising edge)上的 @wr_en 为高电平时, @din 中的数据被推入 FIFO。 FIFO 满时 @full 端口为高电平。

例如,这是向 FIFO写入五个字的波形:

Example waveform for writing words to a FIFO

在这个波形中,应用逻辑先写了 D0 和 D1这两个字。 FIFO 提升其 @full 输出以通知 FIFO 在成功写入 D1后已满。应用逻辑通过在相同的时钟周期(clock cycle)期间降低 @wr_en 来对此做出响应。在几次时钟周期之后, FIFO 将 @full 更改为低电平,以表明可以再次写入。这很可能是由于另一端的活动(即已从 FIFO读取数据)。

应用逻辑可能已经开始在 @full 更改为低的时钟周期上写入,但是它开始这样做稍晚(在此特定示例中)。如波形所示,额外写入了三个字。

在波形中, @din 标有“Dx”值,这意味着该值被忽略,因此那里的值无关紧要。例如,在 D1 和 D2之间带有“Dx”的段中, @din 可能保留在 D1上,比显示的更早更改为 D2 ,或者完全不同。结果是一样的。

对于一个简单的编码示例,假设我想尽可能用计数的单词填充 FIFO :

assign wr_en = !full;

always @(posedge wr_clk)
  if (wr_en)
    din <= din + 1;

这体现了 @full 和 @wr_en之间的正确关系: 如果 @full 为高,则 @wr_en 必须在同一个时钟周期(clock cycle)上为低。如果不是呢?如果我们忽略 @full 信号会怎么样?在这种情况下, FIFO 很可能会忽略 wr_en 。因此,它可能会表现得好像其 wr_en 端口连接到 @the_real_wr_en,其定义如下:

assign the_real_wr_en = wr_en && !full;

然而,一些 FPGA 工具允许在没有这种安全机制的情况下配置 FIFO 。如果是这样,如果在 FIFO 已满时尝试将数据写入 FIFO ,则几乎会发生任何事情。

这样或那样, @full 应该被尊重,否则它会看起来好像数据已经泄漏了。考虑上面的例子: 如果 @wr_en 一直处于高电平,那么无论数据是否写入 FIFO , @din 都会继续计数。因此,当读取另一侧的数据时,向上计数将是不连续的。

请注意, @full 只能作为写 cycle(write cycle)的结果从低变为高,即当 @wr_en 为高时立即在上升沿之后。除非 FIFO 被重置,如下文进一步讨论。

读取界面

读取接口非常相似,但不完全相同。 @rd_clk 和 @rd_en 是输入到 FIFO, @dout 和 @empty 是输出。

当 @rd_en 在 @rd_clk的上升沿上为高电平时,从 FIFO的内存中读取一个新字,并在上升沿之后,即在下一个时钟周期上更新 @dout 的值。 FIFO 为空时 @empty 端口为高电平。

在本例波形中,从 FIFO读取五个字:

Example waveform for reading words from a FIFO

在这个波形中,应用逻辑从读取三个单词开始。为响应 @empty 变为高电平(连同出现在 @dout上的 D2 ),应用逻辑在同一个时钟周期中将 @rd_en 变为低电平。和以前一样,可以在 @empty 更改为低的同一时钟周期上再次将 @rd_en 更改回高(由于数据被写入另一侧的 FIFO )。相反,它等了几句时钟周期,然后又读了两个字。

现在要注意一点如果将此波形与上面的进行比较,您可能会注意到写了五个字,读了五个字。那么为什么 @empty 没有随着 D4 的出现而走高呢?好吧,因为我想证明即使 FIFO 不是空的也可以停止阅读。所以为了这个假想的例子,有额外的字写到 FIFO,因此 FIFO 在读取 D4后没有变空。

请注意,当 @rd_en 为低时, @dout 保持其值。应用逻辑可能依赖于此: @dout 始终包含从 FIFO 读取的最后一个字的值(复位(reset)之后除外)。

更重要的是,请注意,当 @rd_en 为高时, @dout 的新值出现上升沿之后。因此 FIFO 的行为类似于以下 Verilog 代码:

always @(posedge rd_clk)
  if (rd_en && !empty)
    dout <= next_word_to_show;

这个伪造的 Verilog 代码也证明了当 @empty 在同一个时钟周期上为高时,大多数 FIFOs 忽略 @rd_en 。与写接口一样,如果 @empty 在时钟周期上为高电平,则 @rd_en 不应为高电平。再次重申,有时 FIFO 可以配置为不具有此保护机制,因此请不要破坏此规则。

@empty 只能在读 cycle(read cycle)之后才可以由低变高,即 @rd_en 在时钟的上升沿上 @rd_en 为高之后。唯一的例外是 FIFO 复位时。

举个例子,这是一个简化的 Verilog 代码片段(没有复位),它从 FIFO读取单词,并计算累积和。

assign rd_en = want_to_read_now && !empty;

always @(posedge rd_clk)
  begin
    rd_en_d <= rd_en;

    if (rd_en_d)
      sum <= sum + dout; // Don't try this at home: @sum is never reset.
  end

为了演示,我添加了一个 @want_to_read_now 信号,表示逻辑想要读取。只有当 FIFO 不为空时, @rd_en 才为高。

注意 @rd_en_d,其中包含 @rd_en 的值,延迟一个时钟周期。因此,当 @dout中有一个新的有效值时, @rd_en_d 为高电平。这就是为什么 @rd_en_d 被用作消耗 @dout价值的条件。 @rd_en 和 @dout 之间的延迟使事情变得有点困难,如本例所示。

同步和延迟(latency)

因为我把上面的例子波形分别画了写和读,所以漏掉了一个重点: 从将第一个字写入空的 FIFO 到 @empty 端口变为低电平需要几个时钟周期。同样,从读取满的 FIFO 中的第一个字到 @full 端口变为低电平需要几个时钟周期。

发生这种情况是因为有关写入 FIFO 的信息需要在两个时钟域之间传播,然后才能到达 FIFO的另一端。穿越时钟域(clock domains)所需的逻辑会导致一些时钟周期的延迟。因此 @empty 端口的响应稍晚一些。 @full 端口也是如此。

那么这个延迟是多少时钟周期呢?这取决于很多事情,其中包括两个时钟的边沿(edges)在特定时刻之间的时间关系。简而言之,很难说。

影响此延迟的因素之一是 synchronization stages的数量,这通常是可以为 FIFO设置的参数。两 stages 是常见的选择,但可以选择更大的数量。这有助于提高 FIFO的可靠性,但代价是使用更多逻辑资源。它还增加了 @empty 端口和 @full 端口的延迟,就像刚才讨论的那样。

因此,如果您真的想沉迷于您的 FIFO,请将 synchronization stages 增加到三个,以确保绝对安全。

复位输入(reset input)

所有 FPGA FIFOs 都有一个复位信号(reset signal)。由于 FIFO 使用两个时钟,因此这个复位信号预计不会与其中任何一个同步,因此它是异步的。 FIFO的内部逻辑确保在内部同步两个时钟域中的每一个的复位。

那么复位有什么作用呢?好吧,首先,清空 FIFO 并将 @empty 设置为高电平。如果 FIFO中有任何数据,则该数据将丢失。

至于 @full 输出,通常(并且推荐) FIFOs 由于复位将输出保持在高电平,直到 FIFO准备好接收数据(即使用写 cycles(write cycles))。但是,此行为可以是可选的,因此建议使用 FIFO的文档检查此主题。毕竟,在复位之后, FIFO 并没有真正充满。此外,由于复位将 @full 更改为高会破坏上述规则: @full 应仅因写入数据而变为高电平。

重要的是要注意,从复位信号激活到 @empty 端口和 @full 端口变为高电平需要几个时钟 cycles (clocks cycles)。这是因为 FIFO的 synchronization 逻辑。因此,在复位激活期间的几个时钟周期期间,情况有点模糊。确保应用逻辑在复位周围的几个时钟周期期间不会尝试写入或读取 FIFO 。

尽管复位信号是异步的,但它应该是 FPGA的寄存器(register)(触发器(flip-flop)) 的输出。复位不应该是组合逻辑(combinatorial logic)的输出,因为 FIFO 可能会因 glitches而收到意外的复位。

事实上,许多 FPGA 工程师错误地认为几乎任何东西连接到复位端口(reset port)都可以工作。然而, FPGA 供应商可能对复位信号有意外的规格。例如,以下取自 Xilinx的 FIFO (PG057) 产品指南:

If the asynchronous reset is one slowest clock wide and the assertion happens very close to the rising edge of slowest clock, then the reset detection may not happen properly causing unexpected behavior. To avoid such situations, it is always recommended to have the asynchronous reset asserted for at least 3 [ ... ] slowest 时钟周期...

(Chapter 3, "Resets")

因此, Xilinx 建议复位至少保持三个时钟周期处于活动状态。我不确定有多少人知道这个建议。无论如何,请阅读供应商的 FIFO 的用户指南,了解如何正确生成此复位信号。

FIFO 是如何实现的

尽管供应商的软件工具可以确保 FIFO 正常运行,但最好了解使用了 FPGA 的哪些资源,尤其是避免某些类型的资源短缺。

每 FPGA 都有其选项,但我将简要提及一些常见的选项:

本系列关于 FIFOs的第一页到此结束。下一页讨论 FIFOs 的常见变体和附加功能。

此页面由英文自动翻译。 如果有不清楚的地方,请参考原始页面
Copyright © 2021-2024. All rights reserved. (b4b9813f)