01signal.com

FPGA上的复位(Resets): 同步、异步还是根本不同步?

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

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

比如,拜托,我们都知道复位是什么!它就像电脑上的按钮,按下它,一切就从干净开始。正是这个信号进入芯片,确保无论之前发生了什么,从现在开始一切都会好起来。有些人将复位称为使系统进入已知状态的信号。

对于 FPGA 设计师,复位通常只是添加到每个模块并在重复代码模式中使用的额外输入。我们只是这样做,不一定关注这个复位信号(reset signal)实际上确保了什么,如果有的话,或者是否可以完全省略它。

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

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

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

模拟与硬件

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

区分这两种情况很重要。模拟器将 X (未知)初始值分配给所有寄存器(registers),特别是 behavioral 模拟。然后这些 X 值会传播到任何依赖于其逻辑函数中的 X 值的寄存器。因此,即使一个具有 X 值的寄存器也会用 X's淹没整个设计,而模拟(simulation)则变得毫无用处。

解决此问题的一个常见错误方法是将异步复位放在设计中的所有寄存器上,以便摆脱所有 X's。这通常是通过在模拟开始时短暂启用复位来实现的。结果,所有寄存器都获得一个已知值,一切看起来都很完美。不幸的是,这种错误的解决方案通常会给人一种干净开始的假象,因为它隐藏了上一页讨论的问题。

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

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

综上所述,复位应该以硬件为中心,而不是为了在模拟期间摆脱烦人的 X's 。而且因为重点应该放在硬件上,所以我对模拟的评价就这么多。

重置策略

是否以及如何在寄存器上应用复位的决定需要分别考虑每个寄存器的情况。这样做不仅可以避免复位信号出现不必要的高扇出(fan-out),而且还是思考逻辑是否保证在复位之后正确启动的好机会,无论其之前的状态如何。

原则上,有四种选择:

我的意见

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

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

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

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

选项1: 未知初始值

有些寄存器不需要任何复位或初始值。这尤其适用于 shift 寄存器和类似的逻辑单元。一般来说,数据路径很可能适合此选项。

考虑这个片段:

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 比特。在某些 FPGAs (特别是Xilinx )上,如果仅使用最后一个值(@d4),综合工具会将其检测为 shift 寄存器,并且这些寄存器中的任何一个都没有复位。这样可以大大减少逻辑的消耗。

很显然,这些时延寄存器(delay registers)的输出所连接的逻辑必须能够容忍一些初始的随机数据。没有问题的典型情况是存在其他寄存器或状态机,它们被正确重置并确保未初始化的值在到达时被忽略。比如这些时延 lines (delay lines)跟一个流水线(pipeline)有关系,流水线(pipeline)的 control 逻辑自然会忽略无效数据。

更一般地说,当有伴随的标志或状态指示其有效性时,很容易识别不重置或初始化寄存器的机会。或者当有一个明确的顺序,即首先为寄存器分配一个值,然后使用该值时。简而言之,当很容易看出寄存器的值被忽略,直到它被分配了一个正确的值时。

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

reg val;

always @(posedge clk)
  val <= 1;

综合工具可能决定 @val 是一根线(wire),它具有一个恒定值 1。另一种可能性是将寄存器分配为零作为初始值,因此它在第一个时钟上更改为 1 。实际发生的情况取决于综合工具。所以即使 @val 的值总是已知的,除了第一个时钟周期(clock cycle)外,初始值应该被认为是未知的。

选项#2: FPGA的初始值

FPGA (通常为触发器(flip-flops)、 shift 寄存器和存储器)中基本同步逻辑单元的初始值在 configuration 比特流中给出。此功能的众所周知的用途是通过为 block RAM 分配初始值并且从不写入来创建 ROM 。

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

一些同步逻辑单元,特别是 shift 寄存器和专用 RAM blocks,可以通过在 Verilog 或 VHDL 中描述它们的行为来创建(通过 inference): 当代码看起来像时延 line(delay line)时,综合工具通常会创建 shift 寄存器。同样,创建 RAM 逻辑单元以响应 array。但是,如果在这些寄存器(同步复位或异步复位)上使用了复位,则综合工具无法以这种方式使用逻辑资源。这是因为 shift 寄存器和 RAMs 都没有设置内部存储器值的复位输入。

那么初始值是如何设置的呢?在 configuration 过程中,所有同步逻辑单元都在 FPGA 即将激活之前,即在同步逻辑单元开始响应它们的时钟和异步复位输入(asynchronous reset inputs)之前,被赋予它们的初始值。对于 Xilinx 设备,这是通过全局 Set Reset (Global Set Reset)(GSR) 信号实现的,它将所有同步逻辑单元置于其初始状态。之后,全局写使能(Global Write Enable)(GWE) 被激活,这使得同步逻辑单元正常运行。

由于 configuration 进程的执行与 FPGA的应用逻辑所使用的任何时钟无关,因此 FPGA向其运行状态的转换与这些时钟中的任何一个都是异步的。因此,无论时钟是什么,同步逻辑单元的行为都与异步复位被停用一样。换句话说,有可能某个同步逻辑单元(synchronous elements)响应在转换到运行状态后到达的第一个时钟边沿(clock edge),而其他同步逻辑单元不会响应这个时钟边沿,因为违反了时序(timing)。这可能会导致严重的错误,正如本系列第一页所讨论的那样。

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

尽管设置初始值存在局限性,但在许多情况下它已经足够好了,并且不需要显式的复位。在某些情况下,根本没有选择,因为没有可用的复位信号。例如,在 FPGA 唤醒后立即创建 FPGA的复位信号的逻辑。本系列的第三页显示了此类逻辑的一个示例。

对于大多数综合工具,设置寄存器的初始值非常简单: 使用 Verilog的“initial”:

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

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

设置初始值的替代方法取决于所使用的 FPGA 。这种方法通常包括将同步逻辑单元的例化(instantiation)作为 primitive,将初始值赋值为例化参数(instantiation parameter)。比如一 Xilinx的触发器:

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

defparam myflipflop.INIT = 1;

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

选项#3: 异步复位

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

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

所以现实: 使用异步复位的正确和干净的方法是在关闭时钟的情况下执行此操作。这就是“异步”部分的真正含义。在实际的设计中,这包括以下阶段:

这个序列不难实现,但可能更难确保每个时钟的第一个边沿(edge)正确形成,因此没有 glitches: 时钟缓冲器(clock buffers)的一个常见问题是,在时钟缓冲器的输出使能(output enable)激活和第一个时钟边沿通过时钟缓冲器之间的时间上有要求。如果违反了此时序要求(timing requirement),时钟缓冲器可能会输出 glitch (违反了 FPGA对时钟的要求的短脉冲)。这可能会导致依赖此时钟的所有同步逻辑单元出现不可预测的行为。

不幸的是, FPGA 供应商提供的文档并不总是解释如何确保这个时序要求(timing requirement)。因此,可能无法确保复位之后的第一个时钟边沿正常运行。如果没有对第一个时钟边沿的保证,复位就毫无意义。

如果您使用此方法,请确保没有在与异步复位相关的路径(paths)上强制执行时序约束。这种强制执行在这种情况下是不必要的,它可以默认激活。

有一种可靠地应用异步复位的替代方法,这是通常建议的。此方法不涉及时钟 gating,因此不依赖于时钟缓冲器: 这个想法是添加几个触发器(flip-flops),让异步复位信号直接激活,但这些触发器会同步停用复位。换句话说,就是 synchronized asynchronous reset

例如,如果原始异步复位是 @external_resetn,则生成这样的复位:

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重置的同步逻辑单元使用的时钟。

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

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

这 synchronizer 的好处是 @external_resetn 可以作为异步复位使用: 即使没有时钟处于活动状态,它也能正常工作。不过,同步逻辑单元会收到同步变为非活动状态的复位信号,因此可以确保时序的安全。

几乎不用说,每个时钟都需要自己的 synchronized 异步复位。

请务必注意,仅生成如上所示的 @resetn 是不够的。从 @resetn 到同步逻辑单元, @clk 的时序约束必须在路径上强制执行。某些 FPGA 工具的默认设置是忽略以同步逻辑单元的异步复位输入结尾的路径的时序。为此,可能需要更改工具的设置。

因此,如果 @resetn 用作常规异步复位,例如

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

那么上面显示的 synchronizer 不足以确保从复位可靠恢复。您有责任验证以 @resetn 开始并以触发器的异步复位输入结束的路径确实是定时的。

还需要注意的是,这 synchronizer 对 @external_resetn上的 glitches 没有帮助: 如果 @external_resetn的 active 脉冲的长度比 FPGA的触发器的规格短,任何事情都可能发生。所以 @external_resetn 必须由一些逻辑或确保长脉冲的外部电子设备生成。如果这不可行,唯一的解决方案是完全同步复位,就像 @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 。如果逻辑应该像对待异步复位一样对待 @external_resetn ,这是有问题的。换句话说,即使时钟不活动,它也应该工作。

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

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

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

我将用一个一般性评论来结束这个话题: 我选择在上面的例子中使用 active-low 复位,主要是因为一个传统,这个传统起源于复位信号是由 capacitor 生成的, capacitor 通过电阻连接到 power supply 电压。由于 capacitor 最初没有电压,因此复位输入是 '0'。这 capacitor 很快就积累了足够的 charge ,因此复位输入变成了 '1'。这种古老的 power-up 复位类型是许多复位至今仍是 active 低的原因。

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

在现实世界中大多数依赖异步复位的设计中,都没有使用这些方法。因此, FPGA 设计的可靠性完全取决于运气。

选项#4: 同步复位

同步复位最为人所知的是以下代码模式:

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

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

同步复位在几乎所有方面都优于异步复位,除了以下几点:

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

还值得一提的是,如果使用上面建议的 synchronizer ,同样的扇出问题对异步复位的影响是一样的。因此,唯一可以真正说扇出是同步复位的劣势的是那些在时钟关闭的情况下使用异步复位的人(即 gated)。

解决扇出问题首先想到的是用综合工具约束(synthesizer constraint)或属性(attribute)来限制扇出。然而,这是不太受欢迎的方式,因为综合工具只是在达到限制时复制触发器。因此,经常会发生复制的触发器的输出去到模块的目的完全不同的情况,因此这个输出(output)的目的地可以分散在 FPGA的各处。这导致了长布线和显着的传播延迟(propagation delay)。

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

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 的本地副本(延迟一个时钟)。使用 @local_reset 而不是从模块开始的 @reset 和向下将扇出保持在合理的水平。由于预计这款本地复位的消费者无论如何都会紧密相连,因此很有可能将它们放置在 FPGA的某个区域中。因此,本地复位不需要长途穿越逻辑阵列(logic fabric)。

为了优化逻辑,防止综合工具移除本地复位寄存器(reset registers)非常重要。综合工具通常在存在具有相同行为的寄存器时执行此操作,即使它们属于不同的模块。在上面的示例中,显示了 Vivado的综合属性(synthesis attribute),即“dont_touch”。每个综合工具(synthesizer)都有自己的方法来执行此操作(对于 Quartus,使用“dont_merge”和综合属性实现相同的操作)。

要验证综合工具确实保留了所有寄存器,给所有这些寄存器赋予相同的名称(例如,上面建议的 local_reset )然后在 implemented 设计中搜索具有此名称的寄存器很有用。

当然没有必要为每个模块创建一个本地复位。作为一个近似数字,50-100 的扇出对于本地复位来说是合理的,特别是当它在 FPGA上的一个小物理区域内达到逻辑单元时。

减少扇出的主题也在时序收敛(timing closure)的上下文中讨论

更多关于同步复位

关于同步复位的一个常见误解是,如果综合工具遇到与同步复位匹配的 Verilog 代码模式,它会将复位信号连接到触发器的同步复位输入(synchronous reset input)。情况可能确实如此,但通常并非如此。

这与异步复位不同,异步复位必须连接到触发器的异步复位输入。否则,如果没有时钟,复位将无法工作。

综合工具往往没有给编码模式赋予特殊含义,而是计算从 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 以要求同步复位的标准代码模式开始,然后是寄存器行为的特定定义。因此,可以预期 @reset 将连接到相关触发器的同步复位输入,并且逻辑函数的输出(实现为 LUT)将连接到触发器的数据输入(data input)。

实际上,综合工具通常会尽可能简洁地在下一个时钟上实现 @the_register 的值。例如,触发器的复位输入可以连接到实现表达式 (reset || (some_other_condition && !some_condition) )的逻辑函数(即 LUT)。

但还有一个更有趣的可能性: 触发器的复位输入可能根本不使用。相反,只使用数据输入,而逻辑函数使用 @reset 信号作为其输入之一。因此,如果 @reset 为高,则逻辑函数的输出为零。这样, @reset 确实将 @the_register 归零,但它与其他信号没有区别对待。

所以重申: 尽管触发器有同步复位输入,但综合工具往往不会对同步复位的代码模式进行特殊处理,也不会将复位信号与任何其他信号区别对待。触发器的复位输入以最佳方式用于实现 Verilog 代码所需的行为。这有时意味着将复位输入直接连接到复位信号,有时连接到可能涉及复位信号的某些逻辑函数,有时根本不使用复位输入。综合工具会尽一切努力帮助它更好地实现其性能目标,仅此而已。

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

我将总结一下异步复位的另一个缺点: 在大多数 FPGAs中,一个触发器只有一个 reset/set 输入。此输入可以同步或异步运行。如果复位是同步的,则综合工具可能会找到使用此输入的技巧来使用更少的 LUTs 来实现所请求的行为。当有异步复位时,这种捷径是不可能的。所以一个异步复位(asynchronous reset)束缚了综合工具的手,迫使它浪费更多的逻辑资源(logic resources)。

避免寄存器意外冻结

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

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的时钟使能: 时钟仅在 @resetn 为高电平时有效。

同步复位也是如此:

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 <= [ ... ];

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

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

对于异步复位,唯一的选择是将这些寄存器放在单独的“always”语句中。但是对于同步复位,有一个简单的方法可以解决这个问题:

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)”放在最后,因此复位的分配覆盖了之前的所有内容。

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

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

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

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

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

异步复位也可以做同样的事情吗?例如,这会做什么?

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

        if (!resetn)
          reg1 <= 0;
      end

这当然是异步复位常见编码模式的偏离。对 Vivado的综合工具进行的轶事测试表明它得到了提示,并为 @reg1分配了异步复位。

但是 @reg2 所需的行为在 FPGA上无法实现: 如上所述,这意味着 @clk 和 @resetn 都是时钟,并且 @reg2 分别在其上升沿(rising edges)和下降沿(falling edges)上采样一个新值。由于我所知道的任何 FPGA 中都没有带有两个时钟输入的触发器,因此 @reg2的定义中没有综合的可能性。

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

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

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

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