此页面属于关于时序的一系列页面。
关于本页
如果不了解逻辑设计中的一些基本主题,就不可能正确完成时序。此页面解释了一些基本概念,这些概念是本系列页面其余部分的基础。
下面写的所有内容都是在有关逻辑设计的学术课程中教授的。然而,并不是所有使用 FPGAs 的人都上过这样的课程,即使上过这样的课程的人也不一定能记住所有的事情。此页面旨在填补缺失的部分(如果存在)。
随意跳过此页面上您已经知道的部分,但仍然建议通读所有内容,即使这种阅读是肤浅的。本页的最后几节对于理解本系列的后续页面特别重要。
简要回顾
在进入与时序相关的概念之前,我将快速回顾一下逻辑设计中的一些相关术语。如果您还不知道这些术语中的任何一个,我建议您先熟悉一下,然后再继续浏览此页面。
首先,组合逻辑(combinatorial logic)。该术语用于描述逻辑门等元素: 与门(AND gate)、或门(OR gate)、非门(NOT gate)等。这些逻辑单元(logic elements)因为没有内存所以被认为是组合逻辑。换句话说,他们的行为不取决于过去发生的事情。这些逻辑单元的输出(output)处的值仅取决于输入处的值。
组合逻辑的一个重要类型是 look-up table (LUT)。这是一个多用途的逻辑单元,它实现了依赖于多个输入的任何逻辑函数。 LUT 很重要,因为它用于 FPGA内的几乎所有组合逻辑。 FPGA 中的 LUTs 实现为异步 ROMs (asynchronous ROMs)(即没有时钟的 ROMs ): 输入处理地址(address)到 LUT里面的数据。输出通常由一个或两个比特(bits)组成。
输入到这些 LUTs 的数量在市面上几乎所有的 FPGAs 中都是 4 或 6 ,所以每 LUT 中的存储单元数量要么是 16 要么是 64。
下一个重要术语是时序逻辑(sequential logic)。这包括触发器(flip-flops)、同步 RAMs(synchronous RAMs)和许多其他需要时钟才能运行的构建块。作为对时钟特定变化的反应,所有这些逻辑单元都会变得活跃。在这些活动时刻之间,时序逻辑元件(sequential elements)忽略它们的输入,保留它们的内部状态,并且不改变它们的输出。
例如,当时钟从低电平变为高电平时,上升沿触发逻辑单元(positive edge triggered logic elements)有效,即上升沿(rising clock edge)。同样,当时钟从高电平变为低电平时,下降沿触发逻辑单元(negative edge triggered logic elements)处于活动状态,即下降沿(falling clock edge)。也有逻辑单元在两个时钟边沿(clock edges)上都处于活动状态,但是这样的逻辑单元几乎只用于对 I/O 信号进行采样或生产。
所有时序逻辑元件都有某种内存。这是一个直接的结论,因为他们在时钟边沿(clock edge)到货之前不会更换输出。有的时序逻辑元件内存是最小的,正好是一个比特内存配一个输出(output)。其他时序逻辑元件具有更多内存,例如 shift 寄存器和 RAMs。
为了讨论时序,只有两个重要事实: 输入仅与时钟边沿一起采样,而输出几乎仅因时钟边沿而发生变化。只有一个例外,就是有些时序逻辑元件有一个异步复位输入(asynchronous reset input)。当此输入处于活动状态(高电平或低电平,取决于逻辑单元)时,时序逻辑元件的内部状态立即更改为预定义值,而与时钟无关。结果,此元素的部分或全部输出也立即更改为已知值。
在 FPGA中,一些时序逻辑元件具有异步复位(asynchronous reset),而另一些则没有。异步复位是 FPGA上几乎所有时序逻辑元件中唯一可能的异步输入。即使逻辑设计理论包括多 elements 和多个异步输入,例如 S-R 触发器,也是如此。
下面我来说说触发器
为了简单起见,我假设所有时序逻辑元件都是上升沿触发触发器(positive edge triggered flip-flops),即触发器对时钟的上升沿有反应。也就是说,所有的时序逻辑元件都在这里用触发器表示。只有当时钟从低变高时,这些触发器才会响应它们的输入并更新它们的输出。
这样更容易理解时序,关于触发器的讨论也很容易推广到任何其他时序逻辑元件。
Setup 和 hold
为了确保触发器正确可靠地运行,其输入必须根据两个时序要求(timing requirements)保持稳定(即不改变其值):
- 输入必须在时钟边沿之前的一段时间内保持稳定。这段时间称为 setup time,并标记为 tsu。
- 输入必须在 clock's 边沿之后的一段时间内保持稳定。这段时间称为 hold time,并标记为 thold。
上图显示了 tsu 和 thold的含义: D 输入在黄色标记的时间段内不允许更改值。在本例中, D 随合法的时序从高变为低,即在这个黄色时间段之外。
理解这两个时序参数(timing parameters)的一种方法是这样的: 很明显,如果输入到触发器与时钟边沿完全一起变化,则不清楚触发器是否会将输入视为高电平或低电平。它应该选择什么?时钟边沿之前或之后的状态?
然后我们可以问一下,输入与时钟边沿“完全同时” 更改是什么意思。这有多精确?时序参数(timing parameters)、 tsu 和 thold 这两个参数定义了距离时钟边沿多远时可以安全地更改输入的值。这些参数针对所有时序逻辑元件定义,含义相同。
需要注意的是,如果违反了这个时序要求,后果可能比时钟边沿之后的触发器的输出是随机的要严重得多。主要有两个原因:
- 在某些情况下, FPGA 工具在项目的实现(implementation)期间会自动复制逻辑设计的触发器,特别是为了减少扇出(fan-out)。通过这样做,依赖于此触发器值的逻辑的一部分连接到一个触发器(flip-flop),而逻辑的另一部分连接到第二个触发器。只要两个触发器始终具有相同的输出,这就可以了。但是,如果时序要求(timing requirements)(tsu 和 thold)受到侵犯,则这两个触发器中的每一个在其输出上都可以具有不同的值。
- 如果时序要求被破坏,触发器可能会进入metastability状态。在此状态下,触发器的输出会在短时间内既不高也不低。有关 metastability 及其不良后果的更多信息,请参阅此页面。
Clock-to-output
为所有时序逻辑元件定义的第三个时序参数是 clock-to-output 时间,它有几个常用符号,例如 tcko、 tco、 tC_Q 等。此参数不是时序要求。相反,此参数表示 the 时序逻辑元件的输出(Q)何时保证有效。更准确地说,时钟边沿之后多少时间输出有效(参见上面的时序图(timing diagram))。
其实这件事有两个参数:
- 最大 clock-to-output: 时钟边沿之后到输出生效需要多少时间。
- 最小的 clock-to-output: 时钟边沿后保证输出多久不变。
大多数时候,只有最大的 clock-to-output 是有趣的,所以当在数据手册(datasheet)中给出这个参数时,它几乎肯定是最大值。
请注意,如果违反了触发器的时序要求(tsu 和 thold),则无法保证输出何时稳定。在这种情况下,最大的 clock-to-output 参数毫无意义,因为触发器可能会在短时间内处于未定义状态(metastability)。
tsu 和 thold 可以是负的
尽管上图显示的 tsu 和 thold为正值,但这些参数中的一个或两个可能为负值。事实上, FPGAs 内部的触发器通常两个参数都是负数,尽管负数非常小。
例如,负的 tsu 意味着时钟边沿到货时数据(data)不必稳定。相反,数据必须在时钟边沿之后略微达到其稳定值。但是 tsu 在时钟边沿之后还是有限制的。
同样,负 thold 允许数据在时钟边沿之前改变。再一次,在允许多少之前仍然存在限制, thold反映了这一点。
由于 tsu 和 thold 都可以为负数,因此理论上可以定义毫无意义的时序要求。例如,如果 thold 允许输入在 tsu 要求其稳定之前发生变化,那么这是毫无意义的。对于任何真正的时序规范,这种情况当然永远不会发生: 规范必须定义一定的时间段,在此期间要求输入保持稳定。如果没有这样的时间段,触发器什么时候对其输入进行采样?
clock-to-output 参数始终为正极: 触发器在到达之前不可能对时钟边沿做出反应。
传播延迟(Propagation delay)
上图显示了两个触发器与中间的 LUT 之间的简单连接。为了简单起见,我们假设 LUT的输出只依赖于 I1。例如,此逻辑可能是以下 Verilog 代码的结果,因此 LUT 实现了非门:
always @(posedge clk)
begin
foo_reg <= foo; // FF1 = foo_reg
bar <= !foo_reg; // FF2 = bar
end
请注意,两个触发器都连接到同一个时钟。这个时钟的最大频率是多少?
要回答这个问题,缺少一条信息: 从 FF1的输出有稳定值到 FF2的输入有稳定值需要多少时间?我们将时间标记为 tpd (传播延迟)。
请注意,术语传播延迟始终与组合逻辑的特定段相关。明确定义它与哪个段相关很重要。例如,也可以将 LUT的 I1 到同一 LUT的 O (从输入到输出)的时间定义为传播延迟。这个时延(delay)很可能和之前定义的 tpd不一样。
特别是在 FPGA上, FF1 的输出和 LUT的输入之间有一个布线时延(routing delay)。所以在现实生活中, FF1的 Q 和 LUT的 I1不是同一个点,信号在这两个点之间传播需要一定的时间。
由于这种歧义,很少在数据手册中看到 tpd 符号用于 FPGAs。而在这样的数据手册中给出一个传播延迟(propagation delay)的时候,这个参数的准确含义通常是写明的。
稍后我会回到时钟的最大频率。
路径(path)
很难给出路径的简洁定义,但我们已经看到了它的示例。上面我把 tpd 定义为介于 FF1的 Q 和 FF2的 D之间的传播延迟。这 tpd 涉及到一个具体的场景: FF1 改变它的值,然后更新的值到达 LUT,之后 LUT 改变它的输出,最后更新的值到达 FF2。这一系列事件从仅改变一个信号(FF1的输出)开始,到不同点的信号(FF2的输入)稳定时结束。
所以 tpd 从 FF1的 Q 到 FF2的 D定义为路径的传播延迟。或者简称为从 FF1 到 FF2的路径。
路径包含从该序列的开头到结尾导致时延(delay)的所有元素。路径中有两种类型的元件:
- 组合逻辑的元素,它们对时延有贡献,因为电子电路在其输出上更新信号需要一些时间。这通常称为逻辑时延(logic delay)。在上面的示例中, LUT 是此类元素。
- 布线(Routing)。这只是逻辑单元之间的电线。这些段对时延有贡献,因为信号在空间中移动需要时间,而且电压的变化涉及沿途为 capacitors 充电。
路径的目的是计算其传播延迟。接下来显示如何使用此计算结果。
通常一条路径(path)代表一个理论实验,其中一个触发器的输出发生变化,我们遵循特定的路线导致另一个触发器的输入。在这个理论实验中,假想的秒表在第一个触发器的输出发生变化时启动。当第二个触发器的输入发生变化时,此秒表停止。
这个理论实验有助于回答这个秒表显示的时间是否过长,这意味着违反了 tsu 的要求。第二个问题是这个时间是不是太短了,所以违反了 thold 。
请注意,路径的路由仅包括布线和组合逻辑。因此,信号在目的地稳定所需的时间仅取决于组合逻辑和布线以及路径的元素。不管这个理论实验什么时候做,结果总是一样的。
在一个真正的 FPGA 设计中,每个触发器通常有许多到达其输入的路径,以及许多从其输出开始的路径。事实上,一对触发器之间也可以有多条路径。然而,时序的计算始终假设只有一个触发器更改了其输出,而 FPGA的逻辑中发生的一切都是该更改的直接结果。在 FPGA 设计中计算的路径的数量可能是巨大的,但这当然是由软件自动完成的。
一个简单的静态时序分析(static timing analysis)
为了演示,我将用两个触发器制作一个上面示例的简单时序分析(timing analysis)。时序约束(timing constraints)的话题后面再说,暂时先假设 @clk 的频率是 250 MHz (4 ns),直接接逻辑(即没有锁相环(PLL),真设计不推荐这样) ,但这简化了时序分析)。时序约束( SDC 风格)可能是这样的:
create_clock -period 4.000 -name clk [get_ports clk]
本系列的下一页展示了一个真实的时序分析示例,但该分析是准确的,因此包含了很多难以理解的细节。所以这是一个简单的分析,只是演示了原理。
分析执行上述理论实验: 一个假想的秒表与 @clk的上升沿一起开始。这是事件链,以及每个事件所贡献的(虚构的)时延。
- FF1 (0.2 ns)的Clock-to-output : FF1的输出(Q) 更新为触发器的输入(D) 的值之前花费的时间。
- 布线时延(Routing delay)到 I1 (0.3 ns): 信号从 FF1的输出到 LUT的输入(I1) 所需的时间。
- LUT的传播延迟(0.3 ns): LUT 需要在其任何输入(本例中为I1 )更改后更新其输出(O)。
- 布线时延到 FF2的输入(0.4 ns): 信号从 LUT的输出到 FF2的输入(D) 所需的时间
这条路径的传播延迟(tpd) 是所有这些时延的总和: 0.2 + 0.3 + 0.3 + 0.4 = 1.2 ns。为了示例,我们假设 FF2的 tsu 是 0.1 ns。这意味着 FF2的输入(D)在 @clk的下一个上升沿(rising edge)之前必须是稳定的 0.1 ns 。换句话说,允许的最大 tpd 是 4 - 0.1 = 3.9 ns。
但是 tpd 只是 1.2 ns,所以按照这个计算,路径以较大的优势实现了时序约束。此边距称为 slack,在本例中为 3.9 - 1.2 = 2.7 ns。当此数字出现在软件的时序计算中时,表明工具是否难以实现时序约束: 如果 slack 接近于零,通常表明软件必须努力工作才能使路径满足时序要求。
传播延迟还允许我们计算路径可以达到时序的 @clk 的最大频率。 tpd 就是 1.2 ns, tsu 的要求就是下一个上升沿可以晚点到 0.1 ns 。也就是说,上升沿之间至少要有 1.3 ns 。这意味着大约 769 MHz的频率。这是一个非常高的频率,但这是一个现实的结果,因为路径只包含一 LUT。现实生活中的逻辑通常比这更复杂,这就是为什么现实生活中的频率通常要低得多。
真正的静态时序分析可以精确地进行此计算,但这只是故事的一部分。这里计算的路径在实际计算中称为数据路径(data path) 。但是,真正的静态时序分析还考虑到时钟边沿不会同时到达两个触发器。这是因为时钟缓冲器(clock buffer)到每个触发器的时延略有不同。这些时延之间的差异称为时钟偏移(clock skew)。此外,由于时钟抖动(clock jitter),每个时钟边沿之间的时间并不完全相同。时钟的这些问题使精确计算更加复杂,如下页所示。
Recovery 和 Removal
如果触发器有异步复位输入(asynchronous reset input)(您确定要那个吗?),则此输入何时变为非活动状态有要求。请注意,复位(reset)何时激活并不重要,因为触发器无论如何都会更改为已知状态。
但是当复位变为非活动状态时,触发器开始对时钟敏感。如果复位的停用发生在靠近时钟边沿的地方,则不清楚触发器是否应该对此做出响应。就像 tsetup 和 thold一样,复位必须在时钟边沿前后的一段时间内保持稳定。或者,更具体地说:
- 在时钟边沿之前的一段时间内,复位不得从活动状态变为非活动状态。这段时间称为 recovery time。这是触发器从复位恢复并为时钟边沿做好准备所需的时间。
- 在 clock's 边沿之后的一段时间内,复位不得从活动状态变为非活动状态。这段时间称为removal time 。
这些定义类似于 tsetup 和 thold的定义。这不是巧合: recovery time 是一种特殊的 setup time。时序分析以相同的方式完成。唯一的区别是无论数据信号(data signal)的值如何, setup time 都是相关的。相比之下,当异步复位信号(asynchronous reset signal)更改为活动时,不会强制执行 recovery time 。 removal time 和 hold time 之间的关系是一样的。
由于这种相似性, Recovery 和 Removal 将不再讨论。另请注意,上述内容适用于所有异步输入,而不仅仅是异步复位。
RTL 范式和时序
上面显示的带有两个触发器的示例很简单,但它代表了使用 RTL 范式创建的所有逻辑: 每条路径都从触发器开始,到触发器结束,并具有相同的时钟(或 related 时钟)。路径本身由组合逻辑和布线组成。在这个例子中,组合逻辑只是一 LUT,但这和路径中的几个逻辑单元(logic elements)没有本质区别。结构是一样的。
RTL 范式之所以如此重要,是因为当使用这种方法时,几乎所有的数据路径都具有相同的简单结构。由于逻辑设计中的路径数量通常很大,因此时序分析的简单性有助于防止错误。其中,时序分析具有特定模式这一事实使得读取时序报告(timing report)并询问它是否有意义成为可能。
所以在可能的范围内,一切都应该以时序逻辑元件开始,以时序逻辑元件结束。在编写 Verilog 代码以及总体规划逻辑的结构时,这是一个有用的指南。
时序约束背后的理论简介到此结束。时钟周期约束(clock period constraint)和相关的时序报告将在下一页进行说明。