01signal.com

FPGA上的Resets : 同步、异步还是根本不同步?

这是 FPGAs中关于 resets的系列文章的第二页。在解释了为什么 asynchronous resets 不是很多人在上一页中所想的之后,本页讨论了 resets 的不同选项和FPGA的初始化

首先也是最重要的: 什么是 reset?

就像,来吧,我们都知道 reset 是什么!就像 PC上的那个按钮,你按下它,一切就从干净开始。正是这个信号进入 chip ,确保无论以前发生什么,从现在开始一切都会好起来的。有些人将 reset 称为将系统带入已知状态的信号。

对于 FPGA designers, reset 通常只是添加到每个 module 并在重复代码模式中使用的额外 input 。我们只是做的事情,不一定要注意这个 reset 信号实际上确保了什么,或者是否可以完全省略。

最常见的误解是关注 reset 信号将系统带入已知状态这一事实。当然,这是真的,但重要的是 reset 停用后会发生什么。必须确保 logic 以可预测的方式开始工作。或者至少,可预测性足以确保其正常工作。如果我们在停用 reset后不确定系统的行为,那么重置系统是没有意义的。

使这个话题特别困难的是,这涉及到运气: 一般来说, reset 被激活时系统的状态是未知的和随机的, reset的停用时间也是如此。因此,对 reset 信号的不当处理可能会导致随机出现的罕见故障,这似乎是完全不同的问题。同样,可以忽略这个问题而不会产生任何明显的后果,除了通常用巫术处理的偶尔出现的问题。

就像正确的 timing constraints、正确处理 clocks 和 clock domains一样,正确处理 FPGA的唤醒和复位是确保 FPGA 可靠工作的必要条件。所有这些主题的共同点是,人们可以忽略它们而侥幸逃脱,相当多的工程师这样做,但不幸的是, FPGA 的行为就像它时不时地闹鬼一样

模拟与硬件

本页重点介绍有关 resets 的决策在加载到 FPGA时如何影响 design 。但显然,这些决定也对 logic的仿真产生了影响。

区分这两种情况很重要。模拟器将 X (未知)初始值分配给所有 registers,特别是在 behavioral simulation中。然后这些 X 值传播到依赖于其 logic function中的 X 值的任何 register 。因此,即使是具有 X 值的单个 register 也会用 X's淹没整个 design ,并且模拟变得毫无用处。

这个问题的一个常见错误解决方案是在 design中的所有 registers 上放置一个 asynchronous reset ,以便摆脱所有 X's。这通常通过在模拟开始时短暂启用 reset 来完成。结果,所有 registers 都获得了一个已知值,一切看起来都很完美。不幸的是,这种不正确的解决方案通常会通过隐藏上一页中讨论的问题来给人一种干净利落的错觉。

即使正确使用 resets ,为了避免在模拟中追逐 X 值的来源,重置所有 registers 仍然是一个懒惰的选择。这不仅浪费资源,还可能使实现 timing constraints变得更加困难: 不必要地重置 registers 也可以隐藏错误,因为大量 X 值可能源自从一个 register 到另一个 register 的无意依赖。因此, X's 的泛滥可能是 design出现问题的警告。

与模拟相比,硬件对不使用 resets的容忍度要高得多。但是,当 resets 使用不当,或者在必要时根本不使用时,硬件可能会表现出非常意外的行为。

总之, resets 应该与硬件一起使用,而不是在模拟过程中摆脱烦人的 X's 。因为重点应该放在硬件上,所以这几句话就是我要说的关于模拟的全部内容。

重置策略

关于是否以及如何在 registers 上应用 resets 的决定需要分别考虑每个 register 的情况。这样做不仅可以避免 reset 信号中出现不必要的高 fan-out 信号,而且还是一个很好的机会来思考 logic 是否能够保证在 reset之后正确启动,无论其先前的状态如何。

原则上,有四种选择:

我的意见

下面讨论了这些选项中的每一个,因此我将首先介绍我认为正确的方法,然后再详细说明:

这些建议与 FPGA 供应商近来的建议非常一致。

我还将添加一个非常通用的准则: 始终明确地重置 control logic 并让 data paths 从其初始 junk data中刷新自己。

现在对每个选项进行冗长的讨论。

选项1: 未知初始值

一些 registers 不需要任何 reset 或初始值。这尤其适用于 shift registers 和其他 delay elements。一般来说, data paths 很可能适合这个选项。

考虑这个片段:

reg [31:0] d0, d1, d2, d3, d4;

always @(posedge clk)
  begin
    d4 <= d3;
    d3 <= d2;
    d2 <= d1;
    d1 <= d0;
    d0 <= orig_data;
  end

这显然是五个 delay registers ,每个 32 bits 。在某些 FPGAs (特别是Xilinx )上,如果仅使用最后一个值(@d4), synthesizer 会将其检测为 shift register ,并且这些 registers中的任何一个都没有 reset 。这样可以大大减少 logic 的消耗。

很显然,这些 delay registers 的 output 所连接的 logic 必须能够容忍一些初始的随机数据。没有问题的典型情况是存在其他 register 或 state machine,它们被正确重置并确保未初始化的值在到达时被忽略。比如这些 delay lines 跟一个 pipeline有关系, pipeline的 control logic 自然会忽略无效数据。

更一般地说,当伴随的标志或状态表明其有效性时,很容易识别不重置或初始化 register 的机会。或者当有一个明确的顺序时,首先为 register 分配一个值,然后使用这个值。简而言之,很容易判断 register的值被忽略,直到它被分配一个适当的值。

另一种类型的未知初始值可能取决于 synthesizer。例如:

reg val;

always @(posedge clk)
  val <= 1;

synthesizer 可能决定 @val 是一个 wire ,它具有一个恒定值 1。另一种可能性是将 register 分配为零作为初始值,因此它在第一个 clock上更改为 1 。实际发生的情况取决于 synthesizer。所以即使 @val 的值总是已知的,除了第一个 clock cycle外,初始值应该被认为是未知的。

选项#2: FPGA的初始值

FPGA (通常为 flip-flops、 shift registers 和存储器)中基本 synchronous elements 的初始值在 configuration bitstream中给出。此功能的众所周知的用途是通过为 block RAM 分配初始值并且从不写入来创建 ROM 。

此功能的另一个众所周知的方面是 FPGA 通常从 configuration 唤醒,显然所有 registers 的值都为零。这是因为 synthesizer 通常将所有 registers 分配为零作为其初始值,但除非明确设置初始值,否则可能会出现意外情况。

一些 synchronous elements,特别是 shift registers 和专用 RAM blocks,可以通过在 Verilog 或 VHDL 中描述它们的行为来创建(通过 inference): 当代码看起来像 delay line时, synthesizer 通常会创建 shift register 。同样,创建 RAM element 以响应 array。但是,如果在这些 registers (synchronous reset 或 asynchronous reset)上使用了 reset ,则 synthesizer 无法以这种方式使用 logic 资源。这是因为 shift registers 和 RAMs 都没有设置内部存储器值的 reset input 。

那么初始值是如何设置的呢?在 configuration 过程中,所有 synchronous elements 都在 FPGA 即将激活之前,即在 synchronous elements 开始响应它们的 clocks 和 asynchronous reset inputs之前,被赋予它们的初始值。对于 Xilinx 设备,这是通过 Global Set Reset (GSR) 信号实现的,它将所有 synchronous elements 置于其初始状态。之后, Global Write Enable (GWE) 被激活,这使得 synchronous elements 正常运行。

由于 configuration 进程的执行与 FPGA的 application logic使用的任何 clocks 无关,因此 FPGA向其操作状态的转换与这些 clocks中的任何一个都是异步的。因此,无论任何 clock是什么, synchronous elements 的行为都与 asynchronous reset 已停用一样。换句话说,有可能某些 synchronous elements 响应在转换到操作状态后到达的第一个 clock edge ,而其他同步元素由于违反 timing而错过了这个 clock edge 。如本系列第一页所述,这可能会导致严重的错误。

需要注意的是,当 FPGA 唤醒时, clocks 不一定稳定。如果是 FPGA自己的 PLLs生成的,可能会大肆侵犯 timing constraints 。如上所述,如果 clock 在稳定之前被忽略(例如通过 clock enable),或者如果在 FPGA 唤醒时已知它是稳定的,这不是问题。此外,如果 design 确保在 clock 稳定之前没有任何 synchronous element 有理由更改其值,那也没关系。否则,设置一个初始值并不能保证太多。

尽管设置初始值有限制,但在很多场景下已经足够好了,并且不需要显式的 reset 。在某些情况下,根本没有选择,因为 reset 信号不可用。例如,在 FPGA 唤醒后立即创建 FPGA的 reset 信号的 logic 。本系列的第三页显示了此类 logic 的示例。

对于大多数 synthesizers,设置 register的初始值非常简单: 使用 Verilog的“initial”:

reg [15:0] counter;
initial counter = 1000;

令人惊讶的是,“initial”可以在 synthesis的 Verilog 代码中使用,但事实证明,这种用法得到了广泛支持。所以如果 synthesizer 支持的话,这个 keyword 绝对是首选方法(换句话说,文档中明确提到了“initial”的这种用法)。此外,替代方法往往是特定于供应商的,有时甚至特定于 FPGA 系列。因此,即使“initial”并不总是便携,它可能仍然是最便携的选择。

设置初始值的替代方法取决于所使用的 FPGA 。这种方法通常包括将 synchronous element 的 instantiation 作为 primitive,将初始值赋值为 instantiation parameter。比如一个 Xilinx的 flip-flop:

FDCE myflipflop (
  .C(clk),
  .D(in),
  .Q(out),
  .CLR(1'b0),
  .CE(1'b1)
);

defparam myflipflop.INIT = 1;

使用“initial”更好,不是吗?

选项#3: Asynchronous reset

如果您还没有阅读为什么 asynchronous resets 经常被错误使用的页面,我建议您先阅读。除非你无论如何都不打算使用这种 reset 。

出于某种原因,很多人认为 asynchronous reset 是适合所有用途的解决方案。也许是因为它经常出现在代码示例中,也许是因为一种简单的方法可以实现一个 global reset 达到所有 synchronous elements的错觉。也许是因为在旧的 ASIC 世界中, asynchronous reset 在制造过程中对 chip test 很有用,因为它允许重置整个 chip 并开始应用 test vectors。

所以现实: 使用 asynchronous reset 的正确和干净的方法是在关闭 clocks 的情况下执行此操作。这就是“asynchronous”部分的真正含义。在实际的 design中,这包括以下阶段:

这个序列不难实现,但可能更难确保每个 clock 的第一个 edge 正确形成,因此没有 glitches: clock buffers 的一个常见问题是,在激活 clock buffer的 output enable 和第一个通过 clock buffer的 clock edge 之间存在时间要求。如果违反了这个时序要求, clock buffer 可能会输出一个 glitch (一个短的 pulse ,它违反了 FPGA对 clock的要求)。这可能会导致依赖此 clock的所有 synchronous elements 出现不可预测的行为。

不幸的是, FPGA 供应商提供的文档并不总是提供有关如何确保此时序要求的说明。因此,可能无法确保 reset 之后的第一个 clock edge 行为正确。如果没有对第一台 clock edge的保证, reset 毫无意义。

如果您使用此方法,请确保没有在与 asynchronous reset相关的 paths 上强制执行 timing constraints 。这种强制执行在这种情况下是不必要的,它可以默认激活。

有一种可靠地应用 asynchronous reset 的替代方法,这是通常建议的。此方法不涉及 clock gating,因此不依赖于 clock buffers: 想法是添加一些 flip-flops ,直接让 asynchronous reset 信号的激活通过,但是这些 flip-flops 会同步去激活 reset 。换句话说, synchronized asynchronous reset

例如,如果原始 asynchronous reset 是 @external_resetn,则生成这样的 reset :

reg pre_rstn1, pre_rstn2;
   reg resetn;

   always @(posedge clk or negedge external_resetn)
     if (!external_resetn)
       begin
         resetn <= 0;
         pre_rstn2 <= 0;
         pre_rstn1 <= 0;
       end
     else
       begin
         resetn <= pre_rstn2;
         pre_rstn2 <= pre_rstn1;
         pre_rstn1 <= 1;
       end

请注意, @clk 是由 @resetn重置的 synchronous elements 使用的 clock 。

当 @external_resetn 有效(即低电平)时,所有三个 registers 异步变为有效(即零)。但是,当 @external_resetn 被停用时,只有 @pre_rstn1 在下一个 clock edge上变为非活动状态,这会传播到随后的 clocks 上的 @pre_rstn2 和 @resetn 。

两个额外的 registers 的目的是防止 metastability,以便 @resetn 以安全的方式停用。如果 @external_resetn 变得不活动且相对于 @clk的时序错误,这可能会导致 metastable condition 在 @pre_rstn1 上出现(此页面解释 metastability),这是必要的。

这个 synchronizer 的好处是 @external_resetn 可以作为 asynchronous reset使用: 即使没有 clock 处于活动状态,它也可以工作。不过, synchronous elements 接收到 reset 信号同步变为无效,因此 timing 可以得到保证。

几乎不用说,每个 clock 都需要自己的 synchronized asynchronous reset。

请务必注意,仅生成如上所示的 @resetn 是不够的。从 @resetn 到 synchronous elements, @clk 的 timing constraints 必须在 paths 上强制执行。某些 FPGA 工具的默认设置是忽略以 synchronous element的 asynchronous reset input结尾的 paths 的 timing 。为此,可能需要更改工具的设置。

因此,如果 @resetn 用作常规 asynchronous reset,例如

always @(posedge clk or negedge resetn)
    if (!resetn) // Are you sure this path is timed?
      the_register <= 0;
    else
      [ ... ]

那么上面显示的 synchronizer 不足以确保从 reset可靠恢复。您有责任验证以 @resetn 开始并以 flip-flops的 asynchronous reset inputs 结束的 paths 确实是定时的。

还需要注意的是,这个 synchronizer 对 @external_resetn上的 glitches 没有帮助: 如果 @external_resetn的 active pulse 的长度比 FPGA的 flip-flops的规格短,任何事情都可能发生。所以 @external_resetn 必须由一些 logic 或确保长 pulse的外部电子设备生成。如果这不可行,唯一的解决方案是完全同步 reset ,就像 @sync_resetn的示例一样:

reg pre_rstn1, pre_rstn2;
   reg sync_resetn;

   always @(posedge clk)
     if (!external_resetn)
       begin
         sync_resetn <= 0;
         pre_rstn2 <= 0;
         pre_rstn1 <= 0;
       end
     else
       begin
         sync_resetn <= pre_rstn2;
         pre_rstn2 <= pre_rstn1;
         pre_rstn1 <= 1;
       end

但如果 @clk 处于非活动状态,此同步器将忽略 @external_resetn 。如果 logic 应该像对待 asynchronous reset一样对待 @external_resetn ,这是有问题的。换句话说,即使 clocks 不活动,它也应该工作。

让我们回到第一台 synchronizer。将 @resetn 用作普通 synchronous reset怎么样?像这样的东西:

always @(posedge clk) // @resetn not in sensitivity list!
    if (!resetn)
      the_register <= 0;
    else
      [ ... ]

这或多或少都很好,因为 @resetn的停用肯定是通过 timing constraints计时的。但是, @resetn 的异步激活是不定时的,所以在 reset 生效之前,相关的 synchronous elements 可能会随机运行。为此目的,最好使用完全同步的 reset ,例如上面定义的 @sync_resetn 。

我将用一个一般性评论来结束这个话题: 在上面的示例中,我选择使用 active-low resets ,主要是因为 reset 信号是通过 capacitor 生成的传统,而 capacitor 通过 resistor连接到 power supply voltage 。由于 capacitor 最初没有 voltage ,所以 reset input 是 '0'。这个 capacitor 很快就建立了足够的 charge ,因此 reset input 变成了 '1'。这种古老的 power-up reset 是许多 resets 到今天仍然是 active low 的原因。

总之,可以可靠地使用 asynchronous reset ,但实现这一点绝对不像许多人认为的那么简单。我提出了两种方法来确保可靠的 asynchronous reset: 通过暂时关闭 clocks 以避免 timing出现问题,或使用 synchronizer 以确保 timing。与 FPGAs一样, timing 是游戏的名称。

在现实世界中大多数依赖 asynchronous reset 的 designs 中,都没有使用这些方法。因此, FPGA design 的可靠性完全取决于运气。

选项#4: Synchronous reset

synchronous reset 最为人所知的是以下代码模式:

always @(posedge clk)
    if (reset)
      the_register <= 0;
    else
      [ ... ]

我会进一步建议我认为更好的代码模式,但现在我们将坚持使用这个。无论如何,请注意我在这里选择了 active-high reset ,因为这是 synchronous resets更常见的选择。或者我的印象是这样。

synchronous reset 在几乎所有方面都优于 asynchronous reset ,除了以下几点:

由于 FPGAs 很少在没有活动 clock 的情况下使用(与 ASICs不同, ASICs传统上需要这个来进行测试),而且还不清楚 routing 的专用资源问题是否存在,我将专注于主要主题: Fan-out。幸运的是,这很容易解决。

还值得一提的是,如果使用上面建议的 synchronizer ,同样的 fan-out 问题对 asynchronous reset 的影响是一样的。因此,唯一可以真正说 fan-out 是 synchronous reset 的劣势的是那些在 clocks 关闭的情况下使用 asynchronous reset 的人(即 gated)。

解决 fan-out 问题首先想到的是用 synthesizer constraint 或 attribute 来限制 fan-out。然而,这是不太受欢迎的方式,因为 synthesizer 只是在达到限制时复制 flip-flop 。因此,经常会发生复制的 flip-flops 的 output 去到 modules 的目的完全不同的情况,因此这个 output 的目的地可以分散在 FPGA的各处。这导致了长 routing 和显着的 propagation delay。

一个简单有效的解决方案是为 logic的每个重要部分创建一个本地 reset 。就像是

module medium_sized_module (
  input clk,
  input reset,
  input [15:0] in_data
  output [15:0] out_data
);

  (* dont_touch = "true" *) reg local_reset;
  reg the_register;

  always @(posedge clk)
    local_reset <= reset;

  always (@posedge clk)
    if (local_reset)
      the_register <= 0;
    else
      [ ... ]

这个想法是 @local_reset 是 @reset 的本地副本(延迟一个 clock)。使用 @local_reset 而不是从 module 开始的 @reset 和向下将 fan-out 保持在合理的水平。由于预计这款本地 reset 的消费者无论如何都会紧密相连,因此很有可能将它们放置在 FPGA的某个区域中。因此,本地 reset 不需要长途穿越 logic fabric。

重要的是要防止 synthesizer 为了 logic optimization而移除本地 reset registers 。这是 synthesizer 通常在存在具有相同行为的 registers 时所做的事情,即使它们属于不同的 modules。在上面的例子中, Vivado的 synthesis attribute 被显示,即“dont_touch”。每个 synthesizer 都有自己的方式来做到这一点(对于 Quartus,使用“dont_merge”和 synthesis attribute实现相同)。

要验证 synthesizer 确实保留了所有 registers,给所有这些 registers 赋予相同的名称(例如,上面建议的 local_reset )然后在 implemented design中搜索具有此名称的 registers 很有用。

当然没有必要为每个 module创建一个本地 reset 。作为一个近似数字,50-100 的 fan-out 对于本地 reset来说是合理的,特别是当它在 FPGA上的一个小物理区域内达到 logic elements 时。

减少 fan-out 的主题也在 timing closure的上下文中讨论

更多关于 synchronous resets

关于 synchronous resets 的一个常见误区是,如果 synthesizer 遇到与 synchronous reset匹配的 Verilog 代码模式,它会将 reset 信号连接到 flip-flop的 synchronous reset input。情况可能是这样,但通常情况并非如此。

这与 asynchronous reset不同, asynchronous reset必须连接到 flip-flop的 asynchronous reset input。否则,如果没有 clock, reset 将无法工作。

Synthesizers 往往没有给编码模式赋予特殊含义,而是计算从 Verilog 代码派生的 logic equation 。考虑这个例子:

always @(posedge clk)
    if (reset)
      the_register <= 0;
    else if (some_condition)
      the_register <= !the_register;
    else if (some_other_condition)
      the_register <= 0;

阅读此代码的一种方法是 always statement 以要求 synchronous reset的标准代码模式开始,然后是 register行为的特定定义。因此,可以预期 @reset 将连接到相关 flip-flop的 synchronous reset input ,并且 logic function 的 output (实现为 LUT)将连接到 flip-flop的 data input。

实际上, synthesizers 通常会尽可能简洁地在下一个 clock 上实现 @the_register 的值。例如, flip-flop的 reset input 可以连接到实现表达式 (reset || (some_other_condition && !some_condition) )的 logic function (即 LUT)。

但还有一个更有趣的可能性: flip-flop的 reset input 可能根本不用。而是只使用了 data input , logic function 使用 @reset 信号作为其 inputs之一。因此,如果 @reset 为高电平,则 logic function的 output 为零。这样 @reset 确实将 @the_register 归零,但它与任何其他信号的处理方式并没有区别。

所以重申: 即使 flip-flop 有 synchronous reset input, synthesizers 也不会特别对待 synchronous reset的码型,也不会将 reset 信号与任何其他信号区别对待。 flip-flop的 reset input 以最佳方式用于实现 Verilog 代码所需的行为。这有时意味着将 reset input 直接连接到 reset 信号,有时连接到可能涉及 reset 信号的某些 logic function ,有时根本不使用 reset input 。 synthesizer 尽一切可能帮助它更好地实现其性能目标,仅此而已。

Xilinx的 Vivado 用户可以通过两个 synthesis attributes更好地控制这个问题,即 DIRECT_RESET 和 EXTRACT_RESET。

我将总结一下 asynchronous resets的另一个缺点: 在大多数 FPGAs中,一个 flip-flop 只有一个 reset/set input。此 input 可以同步或异步运行。如果 reset 是同步的,则 synthesizer 可能会找到使用此 input 的技巧来使用更少的 LUTs 来实现所请求的行为。当有 asynchronous reset时,这种捷径是不可能的。所以一个 asynchronous reset 束缚了 synthesizer的手,迫使它浪费更多的 logic resources。

避免 registers意外冻结

实现 resets的常用代码模式存在一个缺陷,如下代码示例所示:

always @(posedge clk or negedge resetn)
     if (!resetn)
       begin
         reg1 <= 0;
         reg2 <= 0;
         // Ayeee! Forgot to reset reg3 !
       end
     else
       begin
         reg1 <= [ ... ];
         reg2 <= [ ... ];
         reg3 <= [ ... ];
       end

正如评论所暗示的, @reg3 没有出现在活动 @resetn的 begin-end 子句中。因此,上面的 Verilog 代码要求只要 @resetn 处于活动状态, @reg3 就不会改变值。这与定义 @reg3 相同

always @(posedge clk)
     if (resetn)
       reg3 <= [ ... ];

或者换句话说, @resetn 作为 @reg3的 clock enable : clock 仅在 @resetn 为高电平时有效。

synchronous resets也是如此:

always @(posedge clk)
     if (reset)
       begin
         reg1 <= 0;
         reg2 <= 0;
         // Ayeee! Forgot to reset reg3 !
       end
     else
       begin
         reg1 <= [ ... ];
         reg2 <= [ ... ];
         reg3 <= [ ... ];
       end

这实际上更容易理解,因为这只是一对 begin-end 子句,第二个子句只有在 @reset 处于非活动状态时才生效。所以这个例子中 @reg3 的定义很明确

always @(posedge clk)
     if (!reset)
       reg3 <= [ ... ];

因此,显而易见的(不一定是聪明的)结论是不要忘记 reset的 begin-end 子句中的任何 register 。事实上,无论是否需要,许多 FPGA designers 都会重置所有 registers,因为他们认为这是唯一的方法。或者,他们采用编码风格,每个 register 都有自己的“always”语句。

但是,如果您有意重置某些 registers 而不是其他的怎么办?

对于 asynchronous reset,唯一的选择是将这些 registers 放在单独的“always”语句中。但是对于 synchronous reset,有一个简单的方法可以解决这个问题:

always @(posedge clk)
     begin
       reg1 <= [ ... ];
       reg2 <= [ ... ];
       reg3 <= [ ... ];

       if (reset)
         begin
           reg1 <= 0;
           reg2 <= 0;
           // I don't want to reset reg3, and that's fine!
         end
     end

代替开头的“if (reset)”句子,并且在“else”语句下有有趣的部分,“if (reset)”放在最后,因此 reset 的分配覆盖了之前的所有内容。

请注意,这等同于上述任何示例: @reg1 和 @reg2 在 @reset 有效时复位,但 @reg3 完全不受 reset 的影响。

如果您对这种应用 synchronous reset的替代方式感到不舒服,我可以理解,这有几个原因: 首先,坚持使用 FPGA design中常用的编码模式通常是一种很好的做法。否则, synthesizer 可能会暴露一个奇异的错误,这在完善的代码模式中不太可能发生(参见黄金法则 #4 )。因此,即使 Verilog 标准明确要求此方法有效,人们仍可能认为依赖此功能不一定是个好主意。

这是一个强有力的论点,但就其价值而言,我在这里告诉你,十多年来我一直在大量使用这样的代码模式,并使用了大量的 synthesizers。特别是,这就是我在自己的代码中定义 synchronous resets 的方式。我从来没有遇到过这个问题。

不喜欢这种方法的另一个可能原因是认为 synthesizer 可能会错过需要 synchronous reset 的提示,因为它不是常规代码模式。然而,正如上面已经提到的,大多数 synthesizers 无论如何都不会接受提示,并将 synchronous reset 视为 logic所需行为的另一种定义。所以这个理由是没有根据的。

所以如果你想相信我的话,它是安全的,你会省去一些头痛。

asynchronous reset也可以做同样的事情吗?例如,这会做什么?

always @(posedge clk or negedge resetn)
      begin
        reg1 <= [ ... ];
        reg2 <= [ ... ];

        if (!resetn)
          reg1 <= 0;
      end

这当然是对 asynchronous reset通用编码模式的一种转移。对 Vivado的 synthesizer 的轶事测试表明它得到了提示,并为 @reg1分配了异步 reset 。

但是 @reg2 所需的行为在 FPGA上无法实现: 如上所述,这意味着 @clk 和 @resetn 都是 clocks,并且 @reg2 分别在其 rising edges 和 falling edges上采样一个新值。由于我所知道的任何 FPGA 中都没有带有两个 clock inputs 的 flip-flops ,因此 @reg2的定义中没有 synthesis 的可能性。

Vivado的 synthesizer 对此做出了反应,忽略了“negedge resetn”部分,并创建了一个仅使用 @clk 作为 clock的 flip-flop 。 syntheses的结果中没有这种奇怪的痕迹, synthesizer 也没有发出任何 warning 或其他抱怨。尽管 synthesizer 创建的 logic 的行为与 Verilog 代码所定义的不同。

因此,特别是对于 Vivado的 synthesizer,相同的代码模式实际上甚至适用于 asynchronous reset,但不应依赖以下内容: Verilog 代码应该说明您希望 logic 做什么。否则, synthesizer 完全有权根据自己的喜好对其进行曲解。

本系列关于 resets的第二页到此结束。下一页介绍了在 powerup 和外部 reset之后启动 FPGA 的不同方面。

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