01signal.com

FPGA FIFOs简介

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

概述

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

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

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

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

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

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

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

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

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

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

baseline FIFO

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

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

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
);

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

FIFO module的 ports 分为三组: 一个 reset 信号 (@rst),我稍后再讨论。不出所料,有一个写接口和一个读接口,每个接口由四个 ports组成。

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

Clocks

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

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

写接口

写接口很简单: @wr_clk、 @wr_en 和 @din 是 inputs 到 FIFO, @full 是 output。

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

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

Example waveform for writing words to a FIFO

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

application logic 可能已经开始在 @full 更改为低的 clock cycle 上写入,但是它开始这样做稍晚(在此特定示例中)。如 waveform所示,额外写入了三个字。

在 waveform中, @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 port 连接到 @the_real_wr_en,定义如下:

assign the_real_wr_en = wr_en && !full;

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

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

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

读取界面

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

当 @rd_en 在 @rd_clk的 rising edge 上为高电平时,从 FIFO的内存中读取一个新字,并在 rising edge之后,即在下一个 clock cycle上更新 @dout 的值。 FIFO 为空时 @empty port 为高电平。

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

Example waveform for reading words from a FIFO

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

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

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

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

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

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

@empty 只能在 read cycle之后才可以由低变高,即 @rd_en 在 clock的 rising edge 上 @rd_en 为高之后。唯一的例外是 FIFO 复位时。

举个例子,这是一个简化的 Verilog 代码片段(没有 reset),它从 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 信号,表示 logic 要读取。只有当 FIFO 不为空时, @rd_en 才为高。

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

同步和 latency

因为我把上面的例子 waveforms 分别画了写和读,所以漏掉了一个重点: 从将第一个字写入空的 FIFO 直到 @empty 端口变为低电平需要几个 clock cycles 。同样,从完整的 FIFO 读取第一个字直到 @full 端口变为低电平需要几个 clock cycles 。

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

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

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

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

reset input

所有 FPGA FIFOs 都有一个 reset 信号。由于 FIFO 使用了两个 clocks,因此这个 reset 信号不会与其中任何一个同步,因此它是异步的。 FIFO的内部 logic 确保为两个 clock domains中的每一个在内部同步 reset 。

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

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

需要注意的是,从 reset 信号激活到 @empty port 和 @full port 变为高电平需要几个 clocks cycles 。这是因为 FIFO的 synchronization logic。因此,围绕 reset激活的少数 clock cycles 期间,情况有些模糊。确保 application logic 在 reset周围的少数 clock cycles 期间不会尝试既不写入也不读取 FIFO 。

即使 reset 信号是异步的,它也应该是 FPGA的 register (flip-flop) 的 output 。 reset 不应该是 combinatorial logic的 output ,因为 FIFO 可能会因为 glitches而收到意外的 resets 。

事实上,许多 FPGA engineers 错误地认为将几乎任何东西连接到 reset port 都可以工作。然而, FPGA 供应商可能对 reset 信号有意外的规范。例如,这取自 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 clock cycles...

(Chapter 3, "Resets")

所以 Xilinx 建议 reset 至少激活三个 clock cycles。我不确定有多少人知道该建议。请务必阅读供应商 FIFO 的用户指南,了解如何正确生成此 reset 信号。

FIFO 是如何实现的

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

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

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

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