01signal.com

logic 用于正确启动和重置 FPGA

这是 FPGAs中关于 resets系列的第三页,也是最后一页。建议在此之前阅读前两篇。

概述

花时间设置适当的 logic 以创建 resets总是一个好主意。即使 design 没有明显的问题,在 design 中跳过这个阶段也可能会造成损失: 稍后,您可能会花费数天时间尝试解决一些不稳定问题,但没有意识到这是因为 logic 没有正确初始化。随着时间的推移,在尝试解决此类问题的过程中, design 积累了在不了解问题根源的情况下做出的丑陋变通办法。此页面上有关奇怪的不稳定性的更多信息。

从项目一开始就考虑和设计 reset 信号也很重要,以确保它随着新功能的添加而正常增长。当 FPGA 项目从头开始时,通常首先实现其核心功能,然后随着时间的推移添加功能。由于不同的 modules 通常需要单独的 clocks 和 resets,因此很容易陷入为每个部分快速拼凑一些东西的陷阱。这通常会使项目随着项目的进展而变得越来越混乱。

通过使用从第一天开始就正确编写的用于包含 resets 和 clocks的中央 module可以更容易地避免混乱的项目。如下文进一步解释, reset controller 和 clock resources (PLLs 和 clock buffers 是必要的)相互影响,因此将它们放在同一个 module 中是值得的。我通常给这个 module 取名为 clkrst.v。

然而,通常不可能将所有这些都集中在一个 module中,因为某些 IP cores、子系统或 design blocks 可能会生成它们自己的一组 resets 和 clocks。在这种情况下,需要仔细考虑什么取决于什么,以及整个系统应该如何响应来自不同来源的 resets请求。例如, reset 到 PCIe block 几乎总是从 bus 本身到达,并且 block 产生一个 reset 信号供连接到此 block的 logic 使用。在这种情况下,有必要考虑,例如,如果 reset 从 PCIe bus 到达,系统通常应该如何响应(包括根本不响应的可能性)。

由于每个项目都有自己的故事,因此没有适用于所有情况的单一解决方案。此页面描述概念并提出想法和代码片段,它们可用作您自己的 reset controller的构建块。但是,这些代码片段并非用于直接剪切和粘贴到项目中,而应被视为演示。

为了简单起见,我假设系统中没有其他 block 既不生成 clocks 也不生成 resets,但是将 controller 扩展到一般情况是相当简单的。

reset state machine

在大多数 designs中,主要有两种情况需要重置:

此外,当 watchdog timers 到期或通过其他方式检测到重大系统故障时,可能需要重置。

对所有这些情况的预期反应是从根本上重新启动,以确保无论出现什么问题,它都会得到纠正。这通常最好使用某种 state machine来完成,以确保 reset sequence 是一致且可重复的,无论它是出于什么原因启动的。

话虽如此,在某些情况下,本地可能会重复重置是有意义的。例如,处理 video image frames 的 logic 可以在每帧开始之前复位。这通常需要一种轻量级机制,归结为在激活本地 synchronous reset 时为多个 registers 分配初始值。这是一种与任何其他机制一样的重置机制,并且通常是一种确保稳健运行的简洁明了的方法。

但是由于没有更多关于这种可能性的补充,本页的其余部分将重点介绍基本重置,它涵盖了整个 FPGA。

不稳定的 clocks 和对 reset的需求

使用 FPGA自己的 PLLs 来生成 clocks是很常见的(通常推荐)。但是这些 PLLs 通常在 FPGA 上的所有其他 logic 激活的同时开始运行。结果,依赖于这些 clocks 的 logic 被喂给了不稳定的 clock,其频率可能明显高于正常值。

出现这种情况时,相关 logic paths 的 timing 不保。因此,任何依赖于由 PLLs 生成的 clocks 的 logic 都应被视为在 PLL 被锁定之前未能实现 timing constraints 。

适当且常见的解决方案是将所有此类 logic 保存在 reset 中,直到相关的 PLL 被锁定。或者,可以将此 logic 设置为忽略 clock ,直到 PLL 被锁定,例如通过确保 flip-flops的 Clock Enable input (CE) 处于非活动状态。

如果外部 clock 直接从 FPGA的 pin 连接到其 logic elements,那么问题是 clock generator (通常是 PLL)是否比 FPGA 加载 bitstream更快。确定这一点并不总是那么容易。

因此,大多数 designs中的许多 synchronous elements 都需要重置。或者更准确地说,在 clock不稳定是否需要 reset 的问题上,可以将这些 elements 分为三组:

值得一提的是,部分 FPGAs 允许设置 configuration 进程(即加载 bitstream 和初始化 FPGA的进程),使得 FPGA的唤醒延迟到所有 PLLs 都被锁定。这可能是解决此问题的一种方法。这种方案的缺点是,如果外接 clock出现问题, FPGA 根本不会启动。像这样的情况可能非常令人困惑。

一个简单的 state machine

由于基本启动或重新启动是非常罕见的事件,因此它是否需要多于几微秒并不重要。在许多情况下,甚至高达 100 ms 左右都可以,并且可以利用这一点。因此,普通的 counter 是实现一系列事件的简单方法。

以最简单的形式,它可以归结为:

reg [4:0] reset_count;
reg       rst_src_pll, rst_src_shreg, rst_src_debounce;
reg       clear_counter;
reg       master_reset;

initial reset_count = 0;
initial master_reset = 1;
initial clear_counter = 1;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || rst_src_shreg || rst_src_debounce;

    master_reset <= (reset_count != 31);

    if (clear_counter)
      reset_count <= 0;
    else if (reset_count != 31)
      reset_count <= reset_count + 1;
  end

@rst_src_pll、 @rst_src_shreg 和 @rst_src_debounce 代表不同的复位系统原因。这些 registers 由其他一些 logic给定值。我将介绍一些这样的 logic示例,但现在重要的是这些 registers 与 @wakeup_clk 同步(因此不需要 clock domain crossing)。

@clear_counter 就是 logic OR 的 reset这些原因。此 register 将 @reset_count 更改为零。否则,此计数器从 0 变为 31(在此示例中)并随后停止。

最后,只要 @reset_count 还没有完成计数, @master_reset 就处于活动状态。这是 reset state machine的最简单形式,数到 31 也相当适中。

因此,如果任何 @rst_src_N 信号处于活动状态,即使对于单个 clock cycle, synchronous reset 也会对 31 个 clock cycles处于活动状态。

长 reset pulse有两个优点: 首先,如果相关的 @rst_src_N 随机开启和关闭(例如,摆动 PLL lock detectors、 push buttons、多次请求 resets 的软件),这些多次激活不会传播到可见的 synchronous reset。虽然这样的多次激活通常是无害的,但它们可能会导致 output pins的不必要活动。此类活动可能会产生负面影响,例如使测试电子设备的人误认为有问题。

从这个意义上说,31 clock cycles 的例子非常简约。如果这样的延迟是可以接受的,最好算到对应于 10-100 ms的值。通过这样做, reset controller隐藏了任何比此更短的摆动。

长 reset pulse 的第二个原因是将原始 synchronous reset 分发到本地副本(如前所述)涉及一个 clock cycle的延迟。如果 reset 在整个 logic 中的分布需要多次复制信号,那么总延迟不仅会变长,而且可能还会不均匀。长 reset pulse 确保在某个时间点,所有 logic 都暴露于活动 synchronous reset。在 reset path中出现不均匀延迟仍然是一个坏主意,因为 reset 的停用也会变得不均匀,但有时这没有问题。对于这个问题, reset pulse 的 31 clock cycles 比可能需要的要长得多,但它不会受到伤害。

Resets 用于其他 clock domains

@master_reset 是普通的 synchronous reset,但它与 clock 同步,这可能与 application logic使用的 clocks 不同。要为其他 clocks生产 synchronous resets ,应该对每个 clock执行以下操作:

reg reset_clk_pre1, reset_clk_pre2;
reg reset reset_clk;

always @(posedge clk)
  begin
    reset_clk <= reset_clk_pre2;
    reset_clk_pre2 <= reset_clk_pre1;
    reset_clk_pre1 <= master_reset;
  end

这只是一个普通的三级 clock domain crossing ,生产 @reset_clk。该信号是与 @clk配套的 synchronous reset 。两个阶段实际上就足够了,但因为它是一个重要的信号,所以我用额外的 register放纵了它,以更加安全。

reset controller的 clock

clocks 原则上可以作为 @wakeup_clk使用的三种,即 reset state machine:

第一个选项最容易使用。由于几乎总是有一个 reference clock 驱动 FPGA的 PLLs,所以这个 reference clock 通常可以直接用作 wakeup clock。然而,重要的是要验证当 FPGA 唤醒时 clock 确实是稳定的,即 FPGA 加载 bitstream 所需的时间比外部 oscillator 产生有效 clock所需的时间长。在查看 datasheets时,这通常是具有较大余量的情况。但是,如果板子的 powerup sequence 没有正确规划,很可能在 oscillator 的 supply voltage 达到正确水平之前很久, FPGA 就被允许读取其 bitstream 。

第二种选择是使用由 PLL 在 FPGA 本身上生成的 clock 。明显的优势是这个 clock 可能也用于 application logic ,因此它使用 clock 资源和 power更有效。此选择需要将 reset state machine 保持在其初始状态,直到 clock 稳定,这可以通过以下方式完成:

reg rst_src_pll;
reg rst_src_pll_pre;

initial rst_src_pll = 1;
initial rst_src_pll_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_pll <= rst_src_pll_pre;
    rst_src_pll_pre <= !pll_locked;
  end

@pll_locked 是生成 @wakeup_clk (active high) 的 PLL 的 lock detector output 。这个信号是异步的,所以先和 @wakeup_clk同步,然后作为激活 @clear_counter的原因之一,如上图(即 @rst_src_pll)。

此选项的另一个优点是,如果 reference clock 暂时不稳定或不存在(尤其是在板子上电后), lock detector 很可能也会不稳定。因此,如果在停用 @master_reset之前 @reset_count 计数很大(肯定远高于 31),那么在 reference clock 可以很好地使用之前, FPGA 有可能会牢牢地保持在 reset 中。然而,这不能依赖。

无论如何, @wakeup_clk 是 PLL 的 output 的事实必然意味着它在 clock 稳定之前喂给 logic 。因此,尚不清楚 state machine 在这段时间内是否正常工作。可以说这无关紧要,因为在某些时候 clock 会变得足够好,然后会生成适当的 resets 。如果在 reset之后一切都成为过去,谁在乎之前发生了什么?

更严格的方法是 resets 必须保持稳定活动,直到 wakeup clock 被锁定,因此 FPGA 在 bitstream configuration之后不久就不会出现奇怪的行为。这需要注意仅将非常简单的 logic 与此 clock一起使用。换句话说,如果 clock 的频率比预期的高, logic 应该不会严重失败。

要分析如果 @wakeup_clk 临时频率过高会发生什么情况,请注意 @reset_count 是 reset state machine 中唯一一个 vector的 register 。这意味着所有其他 flip-flops 可能在最坏的情况下采样他们计算的“next value”一个 clock 为时已晚,因为违反了 timing。特别是因为 @pll_locked 低而 PLL 未锁定, @rst_src_pll 很快就稳定高,因此 @clear_counter 也稳定高。当 flip-flop 的 D input (下一个 value)没有改变时, clock 的速度有多快并不重要。

因此,唯一可能的问题是 @reset_count,它可能会错误地计数,直到其计算的 next value 由于 @clear_counter而稳定地保持为零。例如,如果其当前值为 3 (binary 011),则其下一个计算值为 4 (binary 100)。但是如果两个 LSBs 因为 timing没有被采样,而第三个 bit 仍然被采样,那么计数器的值可以跳转到 7 (111 binary)。

为了防止这种情况发生,从 @pll_locked 到 @clear_counter 的 registers 链的初始值都分配有利于保持 @reset_count 稳定为零。因此,如果 @pll_locked 保持稳定低(应该如此)直到 @wakeup_clk 稳定, @reset_count 不会远离零,并且 @master_reset 保持稳定活动。

至于最后一个选项,将 FPGA的 ring oscillator 用于 reset controller: 我自己没有尝试过,所以我不确定它有多好。但是,如果它可以拯救那些没有其他选择的人,这里或多或少是如何使用 Xilinx的 FPGAs做到这一点的: 在 FPGA 的 Configuration User Guide 中查找名为 STARTUPE2 (或类似名称)的 primitive 以获取 .它应该有一个名为 CFGMCLK的 output ,它是来自 FPGA 本身的不准确 ring oscillator 的 clock 。它的频率在 50-65 MHz左右。当 FPGA 唤醒时,这个 clock 保证是稳定的,我会将 timing constraint 设置为相当高的频率(比如 100 MHz)。

但如果真的没有其他选择,我会这样做。例如,如果 FPGA 唤醒时外部 reference clock 不稳定,则需要在 logic中实现额外的延迟。

重置 PLLs

将 PLLs 作为 reset sequence的一部分进行重置通常是个好主意。这可确保在已知 reference clock 有效时重置它们。此外,如果 reset 是由用户启动的(例如按下 reset pushbutton),这可能是由于 PLL锁定不良引起的问题。不应该发生,但以防万一。

@clear_counter 不能用于重置 PLL,因为一旦 PLL 停止锁定,它就会变为活动状态。如果使用它, PLL 将保持在复位状态,永远不会被锁定, reset 永远不会被释放。同理, PLLs的 resets 也不能衍生自 @reset_count: 除了所有 PLLs 都被锁定时,它保持为零。

解决方案是为 PLL单独创建一个 reset register ,与 @clear_counter类似。因此,使用上面的符号,未锁定的 PLLs 由 @rst_src_pll反映,它归结为如下所示:

reg clear_counter;
reg reset_plls;

initial clear_counter = 1;
initial reset_plls = 1;

assign reset_sources = rst_src_shreg || rst_src_debounce;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || reset_sources;
    reset_plls <= reset_sources;

[ ... ]

在此代码片段中, @rst_src_pll 已从 @reset_sources中排除,仅用于 @clear_counter。结果, PLLs 与整个 FPGA一起被重置,除非它们本身没有被锁定。

请注意,如果 @reset_sources 随机变低和变高,那么 @reset_plls 也会根据此处显示的代码。这通常是无害的,因为当他们的 reset 疯狂地开关时, PLLs 不会发生任何不好的事情。尽管如此,也有一种方法可以避免这种情况,如下所述。

更复杂的启动序列

使用普通的 counter (@reset_count) 作为 state variable 可以轻松实现更复杂的 startup sequences。例如,生成正确的 asynchronous resets(在 clocks 关闭时处于活动状态)非常简单: 它通过简单的 logic expressions 完成,定义了 clocks 何时关闭和 resets 何时处于活动状态的时间段。

因此,这种简单的 counter 方法是一个很好的起点,即使对于在项目开始时似乎对 reset state machine 有简单需求的 designs 也是如此。如果后来发现需要复杂的 startup sequence ,很容易扩展现有的 logic 以实现这一目标。

无论如何,规划 startup sequence 归结为定义 sequence 中每个阶段的有效时间段。每个这样的周期都被转换为 @reset_count 应该具有的值范围。

例如,如果 design 有两个或多个不相关的 clocks,则可以为每个 clock domain 创建 reset 信号,如上所示(参见“其他 clock domains的Resets ”)。但是如果采用这种方法,每个 clock domains 都以有效的随机顺序从 reset 中出来。这通常不是问题,但如果是,每个 clock domain 都可以根据 @reset_count的进度在定义的时间停用其 reset 。

也可以实现多个 counter。当 reset sequence 需要等待满足某些条件才能继续时,这很有用。例如,如果 reset sequence 涉及重置 PLLs,等待它们锁定,然后继续 reset sequence,那么让一个 counter 保持为零直到所有 PLLs 都被锁定是有意义的。第二个计数器保持为零,直到第一个 counter 完成计数。

请注意,如果 PLL 丢失了 lock, PLLs 本身不会被重置,但依赖于它们的一切都会被重置。这可能不是所期望的行为,因为丢失其 lock 的 PLL 在大多数 designs中是一个严重故障。要在 lock丢失的情况下请求完整的 reset ,请将下面定义的 @pll_restart 添加到与 OR 合并以获得 @reset_sources的信号中:

assign pll_restart = rst_src_pll && !master_reset;

这简单地说: 如果在 master reset 处于非活动状态后 PLL 解锁,请再次重置所有内容,包括 PLLs。为此, reset count 必须足够长以承受 lock detectors可能的摆动。换句话说, reset count 必须比 PLL的 lock detector 可以高的时间更长,即使 PLL 还没有被锁定。不要将这与 PLL 获取 lock需要多长时间相混淆。一些 lock detectors 根本不摆动,而且这些摆动很可能比 PLL的 lock time 短得多(如 datasheet中所指定)。

wakeup shift register

它可能有点像 overkill,但我通常会在我的 designs中添加这样的 wakeup shift register :

reg [15:0]   wakeup_shift;
reg          rst_src_shreg;

initial rst_src_shreg = 1;
initial wakeup_shift = 0;

always @(posedge wakeup_clk)
  begin
    rst_src_shreg <= !wakeup_shift[15];
    wakeup_shift <= { wakeup_shift, 1'b1 };
  end

因为大多数 FPGAs 将 @wakeup_shift 实现为 shift register primitive,其消耗相当于 LUT,所以它的资源很便宜,并且提供了另一种机制来确保在上电期间发生 reset 。这在没有 PLLs的 design 中是必需的,因为没有其他东西会激活 @clear_counter。但即使有 PLLs,也有可能在 FPGA 唤醒时它们已经被锁定,因为这是某些 FPGAs上 bitstream configuration 的可能选项。

所以无论如何,这是一个推荐的附加组件。安全总比后悔好。

外接 reset button

Reset buttons 很常见。他们所做的与一台 design 完全不同。一种可能性是用户认为“reset”的 button 连接到 FPGA pin ,从而启动了将 bitstream 加载到 FPGA的过程。它也可以连接到一个 processor的 reset pin,在 FPGAs 上有一个 embedded processor。

并且这个 reset button 可以在 FPGA上连接一个通用的 I/O pin ,用于对 FPGA logic进行复位。在这种情况下,这是激活 @clear_counter的另一个原因。

如果 @reset_count 计数到对应于 10 ms 或更多的数字,则不需要 debounce 来自 input pin的信号,因为摆动将被 counter 本身吸收。在这种情况下,这就足够了:

reg rst_src_debounce;
reg rst_src_debounce_pre;

initial rst_src_debounce = 1;
initial rst_src_debounce_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_debounce <= rst_src_debounce_pre;
    rst_src_debounce_pre <= reset_button_pin;
  end

但是,如果 counter 快速完成(如上例所示,仅达到 31),则 pushbutton 需要 debouncing。有几种方法可以做到这一点,例如:

reg [17:0] debounce_count = 0;
reg        reset_button_d, reset_button_d2, reset_button_d3;

wire       debounce_reached = (debounce_count == 250000);

initial debounce_count = 0;
initial rst_src_debounce = 1;

always @(posedge wakeup_clk)
  begin
    reset_button_d3 <= reset_button_d2;
    reset_button_d2 <= reset_button_d;
    reset_button_d <= reset_button_pin;

    if (reset_button_d2 != reset_button_d3)
      debounce_count <= 0;
    else if (!debounce_reached)
      debounce_count <= debounce_count + 1;

    if (debounce_reached)
      rst_src_debounce <= reset_button_d3;
  end

这是一款相对严格的 debouncer,它不适用于嘈杂的输入信号。这是优点还是缺点,取决于您的需求。

此代码示例是为 25 MHz clock编写的。如果 @reset_button_pin 在 10 ms期间具有相同的值,则 @reset_button_pin 的值被复制到 @rst_src_debounce 。请注意,当 @reset_button_d3 更改值时, @debounce_count 在同一 clock cycle 上更改为零。因此,当 @reset_button_d3 改变值时,它不会立即复制到 @rst_src_debounce 中,而是在长时间保持相同的值之后。

概括

在设计 FPGA的初始化时需要考虑很多事情。本页介绍了一些概念和想法,但重要的是要记住,真正的任务是正确识别需要启动操作的事件。为了使 logic 可靠地进入工作状态,定义对每个此类事件的正确响应也很重要。

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